diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 000000000..7b737155f --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,20 @@ +{ + "name": "Javascript SDK", + "image": "mcr.microsoft.com/devcontainers/typescript-node:1-18-bookworm", + + "postCreateCommand": "cd /workspaces/javascript-sdk && npm install -g npm && npm install", + + "customizations": { + "vscode": { + "extensions": [ + "dbaeumer.vscode-eslint", + "eamodio.gitlens", + "esbenp.prettier-vscode", + "Gruntfuggly.todo-tree", + "github.vscode-github-actions", + "ms-vscode.test-adapter-converter", + "vitest.explorer" + ] + } + } +} diff --git a/packages/optimizely-sdk/.eslintignore b/.eslintignore similarity index 100% rename from packages/optimizely-sdk/.eslintignore rename to .eslintignore diff --git a/packages/optimizely-sdk/.eslintrc.js b/.eslintrc.js similarity index 100% rename from packages/optimizely-sdk/.eslintrc.js rename to .eslintrc.js diff --git a/.github/ISSUE_TEMPLATE/BUG-REPORT.yml b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml new file mode 100644 index 000000000..855cdf50d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/BUG-REPORT.yml @@ -0,0 +1,106 @@ +name: 🐞 Bug +description: File a bug/issue +title: "[BUG] " +labels: ["bug", "needs-triage"] +body: +- type: checkboxes + attributes: + label: Is there an existing issue for this? + description: Please search to see if an issue already exists for the bug you encountered. + options: + - label: I have searched the existing issues + required: true +- type: textarea + attributes: + label: SDK Version + description: Version of the SDK in use? + validations: + required: true +- type: textarea + attributes: + label: Current Behavior + description: A concise description of what you're experiencing. + validations: + required: true +- type: textarea + attributes: + label: Expected Behavior + description: A concise description of what you expected to happen. + validations: + required: true +- type: textarea + attributes: + label: Steps To Reproduce + description: Steps to reproduce the behavior. + placeholder: | + 1. In this environment... + 1. With this config... + 1. Run '...' + 1. See error... + validations: + required: true +- type: dropdown + attributes: + label: SDK Type + description: Please select the type of JS SDK. + multiple: false + options: + - Browser + - Node + - React Native + - Edge/Lite + validations: + required: true +- type: textarea + attributes: + label: Node Version + description: What version of Node are you using? + validations: + required: false +- type: textarea + attributes: + label: Browsers impacted + description: What browsers are impacted? + validations: + required: false +- type: textarea + attributes: + label: Link + description: Link to code demonstrating the problem. + validations: + required: false +- type: textarea + attributes: + label: Logs + description: Logs/stack traces related to the problem (⚠️do not include sensitive information). + validations: + required: false +- type: dropdown + attributes: + label: Severity + description: What is the severity of the problem? + multiple: true + options: + - Blocking development + - Affecting users + - Minor issue + validations: + required: false +- type: textarea + attributes: + label: Workaround/Solution + description: Do you have any workaround or solution in mind for the problem? + validations: + required: false +- type: textarea + attributes: + label: "Recent Change" + description: Has this issue started happening after an update or experiment change? + validations: + required: false +- type: textarea + attributes: + label: Conflicts + description: Are there other libraries/dependencies potentially in conflict? + validations: + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/ENHANCEMENT.yml b/.github/ISSUE_TEMPLATE/ENHANCEMENT.yml new file mode 100644 index 000000000..79c53247b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/ENHANCEMENT.yml @@ -0,0 +1,45 @@ +name: ✨Enhancement +description: Create a new ticket for a Enhancement/Tech-initiative for the benefit of the SDK which would be considered for a minor version update. +title: "[ENHANCEMENT] <title>" +labels: ["enhancement"] +body: + - type: textarea + id: description + attributes: + label: "Description" + description: Briefly describe the enhancement in a few sentences. + placeholder: Short description... + validations: + required: true + - type: textarea + id: benefits + attributes: + label: "Benefits" + description: How would the enhancement benefit to your product or usage? + placeholder: Benefits... + validations: + required: true + - type: textarea + id: detail + attributes: + label: "Detail" + description: How would you like the enhancement to work? Please provide as much detail as possible + placeholder: Detailed description... + validations: + required: false + - type: textarea + id: examples + attributes: + label: "Examples" + description: Are there any examples of this enhancement in other products/services? If so, please provide links or references. + placeholder: Links/References... + validations: + required: false + - type: textarea + id: risks + attributes: + label: "Risks/Downsides" + description: Do you think this enhancement could have any potential downsides or risks? + placeholder: Risks/Downsides... + validations: + required: false \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.md b/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.md new file mode 100644 index 000000000..a061f3356 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/FEATURE-REQUEST.md @@ -0,0 +1,4 @@ +<!-- + Thanks for filing in issue! Are you requesting a new feature? If so, please share your feedback with us on the following link. +--> +## Feedback requesting a new feature can be shared [here.](https://feedback.optimizely.com/) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..d28ef3dd4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: 💡Feature Requests + url: https://feedback.optimizely.com/ + about: Feedback requesting a new feature can be shared here. \ No newline at end of file diff --git a/.github/issue_template.md b/.github/issue_template.md deleted file mode 100644 index 01f3e9b9e..000000000 --- a/.github/issue_template.md +++ /dev/null @@ -1,39 +0,0 @@ -<!-- - Thanks for filing in issue! Are you proposing an enhancement or reporting a bug? - - If proposing an enhancement, please describe your use case in as much detail as you think is needed to convey the value of the enhancement. ---> -## How would the enhancement work? - -## When would the enhancement be useful? - -<!-- - If reporting a bug, please include the following info: ---> - -## What I wanted to do - -## What I expected to happen - -## What actually happened - -## Steps to reproduce -Link to repository that can reproduce the issue: <link> - -<!-- - OR provide the following. - If possible, whittle down your issue into a [short, self-contained, correct example](http://sscce.org/). ---> - -**`@optimizely/optimizely-sdk` version:** - -<!-- ...and whichever of the following are applicable: --> - -**Browser and version:** - -**`node` version:** - -**`npm` version:** - -Versions of any other relevant tools (like module bundlers, transpilers, etc.): - diff --git a/.github/workflows/integration_test.yml b/.github/workflows/integration_test.yml index 6b73ba748..70b391e18 100644 --- a/.github/workflows/integration_test.yml +++ b/.github/workflows/integration_test.yml @@ -23,15 +23,19 @@ jobs: path: 'home/runner/travisci-tools' ref: 'master' - name: set SDK Branch if PR + env: + HEAD_REF: ${{ github.head_ref }} if: ${{ github.event_name == 'pull_request' }} run: | - echo "SDK_BRANCH=${{ github.head_ref }}" >> $GITHUB_ENV - echo "TRAVIS_BRANCH=${{ github.head_ref }}" >> $GITHUB_ENV + echo "SDK_BRANCH=$HEAD_REF" >> $GITHUB_ENV + echo "TRAVIS_BRANCH=$HEAD_REF" >> $GITHUB_ENV - name: set SDK Branch if not pull request + env: + REF_NAME: ${{ github.ref_name }} if: ${{ github.event_name != 'pull_request' }} run: | - echo "SDK_BRANCH=${{ github.ref_name }}" >> $GITHUB_ENV - echo "TRAVIS_BRANCH=${{ github.ref_name }}" >> $GITHUB_ENV + echo "SDK_BRANCH=$REF_NAME" >> $GITHUB_ENV + echo "TRAVIS_BRANCH=$REF_NAME" >> $GITHUB_ENV - name: Trigger build env: SDK: javascript diff --git a/.github/workflows/javascript.yml b/.github/workflows/javascript.yml index 18c063677..c097ff585 100644 --- a/.github/workflows/javascript.yml +++ b/.github/workflows/javascript.yml @@ -17,11 +17,11 @@ jobs: - name: Set up Node uses: actions/setup-node@v3 with: - node-version: 12 - cache-dependency-path: packages/optimizely-sdk/package-lock.json + node-version: 16 + cache-dependency-path: ./package-lock.json cache: 'npm' - name: Run linting - working-directory: ./packages/optimizely-sdk + working-directory: . run: | npm install npm run lint @@ -40,33 +40,30 @@ jobs: CI_USER_TOKEN: ${{ secrets.CI_USER_TOKEN }} TRAVIS_COM_TOKEN: ${{ secrets.TRAVIS_COM_TOKEN }} - crossbrowser_and_umd_unit_tests: - runs-on: ubuntu-latest - env: - BROWSER_STACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} - BROWSER_STACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} - steps: - - uses: actions/checkout@v3 - - name: Move to package - run: | - cd packages/optimizely-sdk - - name: Set up Node - uses: actions/setup-node@v3 - with: - node-version: 14 - cache: 'npm' - cache-dependency-path: packages/optimizely-sdk/package-lock.json - - name: Cross-browser and umd unit tests - working-directory: ./packages/optimizely-sdk - run: | - npm install - npm run test-ci + # crossbrowser_and_umd_unit_tests: + # runs-on: ubuntu-latest + # env: + # BROWSER_STACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + # BROWSER_STACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + # steps: + # - uses: actions/checkout@v3 + # - name: Set up Node + # uses: actions/setup-node@v3 + # with: + # node-version: 16 + # cache: 'npm' + # cache-dependency-path: ./package-lock.json + # - name: Cross-browser and umd unit tests + # working-directory: . + # run: | + # npm install + # npm run test-ci unit_tests: runs-on: ubuntu-latest strategy: matrix: - node: ['14', '16', '18' ] + node: ['18', '20', '22', '24'] steps: - uses: actions/checkout@v3 - name: Set up Node ${{ matrix.node }} @@ -74,9 +71,9 @@ jobs: with: node-version: ${{ matrix.node }} cache: 'npm' - cache-dependency-path: packages/optimizely-sdk/package-lock.json + cache-dependency-path: ./package-lock.json - name: Unit tests - working-directory: ./packages/optimizely-sdk + working-directory: . run: | npm install npm run coveralls @@ -84,11 +81,11 @@ jobs: uses: coverallsapp/github-action@master with: github-token: ${{ secrets.GITHUB_TOKEN }} - path-to-lcov: ./packages/optimizely-sdk/coverage/lcov.info + path-to-lcov: ./coverage/lcov.info flag-name: run-${{ matrix.node }} # This is a parallel build so need this parallel: true - base-path: ./packages/optimizely-sdk + base-path: . # As testing against multiple versions need this to # finish the parallel build @@ -101,28 +98,7 @@ jobs: uses: coverallsapp/github-action@master with: github-token: ${{ secrets.github_token }} - path-to-lcov: ./packages/optimizely-sdk/coverage/lcov.info + path-to-lcov: ./coverage/lcov.info parallel-finished: true - base-path: ./packages/optimizely-sdk - - test_sub_packages: - runs-on: ubuntu-latest - strategy: - matrix: - package: [ 'packages/utils', 'packages/event-processor', 'packages/logging', 'packages/datafile-manager'] - steps: - - uses: actions/checkout@v3 - - name: Move to package ${{ matrix.package }} - run: | - cd ${{ matrix.package }} - - name: Set up Node - uses: actions/setup-node@v3 - with: - node-version: 12 - cache: 'npm' - cache-dependency-path: ${{ matrix.package }}/package-lock.json - - name: Test sub packages - working-directory: ./${{ matrix.package }} - run: | - npm install - npm test + base-path: . + \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 000000000..f3e710a44 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,81 @@ +name: Publish SDK to NPM + +on: + release: + types: [published, edited] + workflow_dispatch: {} + +jobs: + publish: + name: Publish to NPM + runs-on: ubuntu-latest + if: ${{ github.event_name == 'workflow_dispatch' || !github.event.release.draft }} + steps: + - name: Checkout branch + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 18 + registry-url: "https://registry.npmjs.org/" + always-auth: "true" + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + + - name: Install dependencies + run: npm install + + - id: latest-release + name: Export latest release git tag + run: | + echo "latest-release-tag=$(curl -qsSL \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN }}" \ + "$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/releases/latest" \ + | jq -r .tag_name)" >> $GITHUB_OUTPUT + + - id: npm-tag + name: Determine NPM tag + env: + GITHUB_RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + VERSION=$(jq -r '.version' package.json) + LATEST_RELEASE_TAG="${{ steps.latest-release.outputs['latest-release-tag']}}" + + if [[ ${{ github.event_name }} == "workflow_dispatch" ]]; then + RELEASE_TAG=${GITHUB_REF#refs/tags/} + else + RELEASE_TAG=$GITHUB_RELEASE_TAG + fi + + if [[ $RELEASE_TAG == $LATEST_RELEASE_TAG ]]; then + echo "npm-tag=latest" >> "$GITHUB_OUTPUT" + elif [[ "$VERSION" == *"-beta"* ]]; then + echo "npm-tag=beta" >> "$GITHUB_OUTPUT" + elif [[ "$VERSION" == *"-alpha"* ]]; then + echo "npm-tag=alpha" >> "$GITHUB_OUTPUT" + elif [[ "$VERSION" == *"-rc"* ]]; then + echo "npm-tag=rc" >> "$GITHUB_OUTPUT" + else + echo "npm-tag=v$(echo $VERSION | awk -F. '{print $1}')-latest" >> "$GITHUB_OUTPUT" + fi + + - id: release + name: Test, build and publish to npm + env: + BROWSERSTACK_USERNAME: ${{ secrets.BROWSERSTACK_USERNAME }} + BROWSERSTACK_ACCESS_KEY: ${{ secrets.BROWSERSTACK_ACCESS_KEY }} + NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} + run: | + if [[ ${{ github.event_name }} == "workflow_dispatch" ]]; then + DRY_RUN="--dry-run" + fi + npm publish --tag=${{ steps.npm-tag.outputs['npm-tag'] }} $DRY_RUN + + # - name: Report results to Jellyfish + # uses: optimizely/jellyfish-deployment-reporter-action@main + # if: ${{ always() && github.event_name == 'release' && (steps.release.outcome == 'success' || steps.release.outcome == 'failure') }} + # with: + # jellyfish_api_token: ${{ secrets.JELLYFISH_API_TOKEN }} + # is_successful: ${{ steps.release.outcome == 'success' }} diff --git a/.gitignore b/.gitignore index 702b61d6d..4ab687ed5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ node_modules/ -/packages/*/optimizely-*.tgz +optimizely-*.tgz npm-debug.log lerna-debug.log @@ -10,3 +10,8 @@ dist/ # user-specific ignores ought to be defined in user's `core.excludesfile` .idea/* .DS_STORE + +browserstack.err +local.log + +**/*.gen.ts diff --git a/.prettierrc b/.prettierrc index 95d49510c..62301e2e3 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,10 +1,10 @@ { - "printWidth": 89, + "printWidth": 120, "tabWidth": 2, "useTabs": false, - "semi": false, + "semi": true, "singleQuote": true, - "trailingComma": "all", + "trailingComma": "es5", "bracketSpacing": true, "jsxBracketSameLine": false } diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..ce072c82c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.tabSize": 2 +} diff --git a/packages/optimizely-sdk/CHANGELOG.md b/CHANGELOG.md similarity index 73% rename from packages/optimizely-sdk/CHANGELOG.md rename to CHANGELOG.md index ea8ba3e82..4c3bcba29 100644 --- a/packages/optimizely-sdk/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,9 +5,315 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [6.2.0] - October 23, 2025 + +### New Features + +- **Added support for Contextual Multi-Armed Bandit (CMAB)**: Added support for CMAB experiments(Contextual Bandits rules) with new configuration options and cache control. To get decision from CMAB rules, `decideAsync` and related methods must be used. The sync `decide` method does not support CMABs and will just skip CMAB rules while making decision for a flag. + + #### CMAB Configuration Options + + The following new options have been added to configure the cmab cache: + + ```js + import { createInstance } from '@optimizely/optimizely-sdk' + + const optimizely = createInstance({ + // ... other config options + cmab: { + cacheSize: 1000, // Optional: Set CMAB cache size (default: 1000) + cacheTtl: 30 * 60 * 1000, // Optional: Set CMAB cache TTL in milliseconds (default: 30 * 60 * 1000) + cache: customCache // Optional: Custom cache implementation, instance of CacheWithRemove interface + } + }); + ``` + + #### CMAB-Related OptimizelyDecideOptions + + New decide options are available to control CMAB caching behavior: + + - `OptimizelyDecideOption.IGNORE_CMAB_CACHE`: Bypass CMAB cache for fresh decisions + - `OptimizelyDecideOption.RESET_CMAB_CACHE`: Clear and reset CMAB cache before making decisions + - `OptimizelyDecideOption.INVALIDATE_USER_CMAB_CACHE`: Invalidate CMAB cache for the particular user and experiment + + ```js + + // Example usage with CMAB decide options + const decision = await userContext.decideAsync('feature-flag-key', [ + optimizelySdk.enums.OptimizelyDecideOption.IGNORE_CMAB_CACHE + ]); + ``` + +### Bug Fixes +- Flush events without closing client on page unload which causes event processing to stop working when page is loaded from bfcache ([#1087](https://github.com/optimizely/javascript-sdk/pull/1087)) +- Fixed typo in clientEngine option ([#1095](https://github.com/optimizely/javascript-sdk/pull/1095)) + +## [5.4.0] - Oct 13, 2025 + +### New Features +- Added `customHeaders` option to `datafileOptions` for passing custom HTTP headers in datafile requests ([#1092](https://github.com/optimizely/javascript-sdk/pull/1092)) +### Bug Fixes +- Fix the EventTags type to allow event properties ([#1040](https://github.com/optimizely/javascript-sdk/pull/1040)) +- Fix typo in event.experimentIds field in project config ([#1088](https://github.com/optimizely/javascript-sdk/pull/1088)) + +## [6.1.0] - September 8, 2025 + +### New Features + +- Added multi-region support for logx events ([#1072](https://github.com/optimizely/javascript-sdk/pull/1072)) + +### Performance Improvements + +- Improved performance of variations parsing from datafile ([#1080](https://github.com/optimizely/javascript-sdk/pull/1080)) +- General cleanups and improvements in event processing ([#1073](https://github.com/optimizely/javascript-sdk/pull/1073)) + +## [6.0.0] - May 29, 2025 ### Breaking Changes + +- Modularized SDK architecture: The monolithic `createInstance` call has been split into multiple factory functions for greater flexibility and control. +- Core functionalities (project configuration, event processing, ODP, VUID, logging, and error handling) are now configured through dedicated components created via factory functions, giving you greater flexibility and control in enabling/disabling certain components and allowing optimizing the bundle size for frontend projects. +- `onReady` Promise behavior changed: It now resolves only when the SDK is ready and rejects on initialization errors. +- event processing is disabled by default and must be explicitly enabled by passing a `eventProcessor` to the client. +- Event dispatcher interface updated to use Promises instead of callbacks. +- Logging is disabled by default and must be explicitly enabled using a logger created via a factory function. +- VUID tracking is disabled by default and must be explicitly enabled by passing a `vuidManager` to the client instance. +- ODP functionality is no longer enabled by default. You must explicitly pass an `odpManager` to enable it. +- Dropped support for older browser versions and Node.js versions earlier than 18.0.0. + +### New Features +- Added support for async user profile service and async decide methods (see dcoumentation for [User Profile Service](https://docs.developers.optimizely.com/feature-experimentation/docs/implement-a-user-profile-service-for-the-javascript-sdk) and [Decide methods](https://docs.developers.optimizely.com/feature-experimentation/docs/decide-methods-for-the-javascript-sdk)) + +### Migration Guide + +For detailed migration instructions, refer to the [Migration Guide](MIGRATION.md). + +### Documentation + +For more details, see the official documentation: [JavaScript SDK](https://docs.developers.optimizely.com/feature-experimentation/docs/javascript-sdk). + +## [5.3.5] - Jan 29, 2025 + +### Bug Fixes + +- Rollout experiment key exclusion from activate method([#949](https://github.com/optimizely/javascript-sdk/pull/949)) +- Using optimizely.readyPromise instead of optimizely.onReady to avoid setTimeout call in edge environments. ([#995](https://github.com/optimizely/javascript-sdk/pull/995)) + +## [4.10.1] - November 18, 2024 + +### Changed +- update uuid module improt and usage ([#961](https://github.com/optimizely/javascript-sdk/pull/961)) + + +## [5.3.4] - Jun 28, 2024 + +### Changed + +- crypto and text encoder polyfill addition for React native ([#936](https://github.com/optimizely/javascript-sdk/pull/936)) + +## [5.3.3] - Jun 06, 2024 + +### Changed + +- queueMicroTask fallback addition for embedded environments / unsupported platforms ([#933](https://github.com/optimizely/javascript-sdk/pull/933)) + +## [5.3.2] - May 20, 2024 + +### Changed + +- Added public facing API for ODP integration information ([#930](https://github.com/optimizely/javascript-sdk/pull/930)) + + +## [5.3.1] - May 20, 2024 + +### Changed +- Fix Memory Leak: Closed http request after getting response to release memory immediately (node) ([#927](https://github.com/optimizely/javascript-sdk/pull/927)) + +## [5.3.1-rc.1] - May 13, 2024 + +### Changed +- Fix Memory Leak: Closed http request after getting response to release memory immediately (node) ([#927](https://github.com/optimizely/javascript-sdk/pull/927)) + +## [5.3.0] - April 8, 2024 + +### Changed +- Refactor: ODP corrections [#920](https://github.com/optimizely/javascript-sdk/pull/920) including + - ODPManager should not be running and scheduling timer if ODP is not integrated to the project (which causes memory leak if one sdk instance is created per request) + - CreateUserContext should work even when called before the datafile is downloaded and should send the `identify` ODP events after datafile download completes + - Other automatic odp events (vuid registration, client initialized) should also be sent after datafile is available and should not be dropped if batching is disabled. + - [see PR for more] + + +## [5.2.1] - March 25, 2024 + +### Bug fixes +- Fix: empty segments collection is valid ([#916](https://github.com/optimizely/javascript-sdk/pull/916)) +- Update vulnerable dependencies ([#918](https://github.com/optimizely/javascript-sdk/pull/918)) + +## [5.2.0] - March 18, 2024 + +### New Features +- Add `persistentCacheProvider` option to `createInstance` to allow providing custom persistent cache implementation in react native ([#914](https://github.com/optimizely/javascript-sdk/pull/914)) + +## [5.1.0] - March 1, 2024 + +### New Features +- Add explicit entry points for node, browser and react_native, allowing imports like `import optimizelySdk from '@optimizely/optimizely-sdk/node'`, `import optimizelySdk from '@optimizely/optimizely-sdk/browser'`, `import optimizelySdk from '@optimizely/optimizely-sdk/react_native'` ([#905](https://github.com/optimizely/javascript-sdk/pull/905)) + +### Changed +- Log an error in DatafileManager when datafile fetch fails ([#904](https://github.com/optimizely/javascript-sdk/pull/904)) + +## [5.0.1] - February 20, 2024 + +### Bug fixes +- Improved conditional ODP instantiation when `odpOptions.disabled: true` is used ([#902](https://github.com/optimizely/javascript-sdk/pull/902)) + +### Changed +- Updated Dependabot alerts ([#896](https://github.com/optimizely/javascript-sdk/pull/896)) +- Updated several devDependencies ([#898](https://github.com/optimizely/javascript-sdk/pull/898), [#900](https://github.com/optimizely/javascript-sdk/pull/900), [#901](https://github.com/optimizely/javascript-sdk/pull/901)) + + +## [5.0.0] - January 19, 2024 + +### New Features + +The 5.0.0 release introduces a new primary feature, [Advanced Audience Targeting]( https://docs.developers.optimizely.com/feature-experimentation/docs/optimizely-data-platform-advanced-audience-targeting) enabled through integration with [Optimizely Data Platform (ODP)](https://docs.developers.optimizely.com/optimizely-data-platform/docs) ([#765](https://github.com/optimizely/javascript-sdk/pull/765), [#775](https://github.com/optimizely/javascript-sdk/pull/775), [#776](https://github.com/optimizely/javascript-sdk/pull/776), [#777](https://github.com/optimizely/javascript-sdk/pull/777), [#778](https://github.com/optimizely/javascript-sdk/pull/778), [#786](https://github.com/optimizely/javascript-sdk/pull/786), [#789](https://github.com/optimizely/javascript-sdk/pull/789), [#790](https://github.com/optimizely/javascript-sdk/pull/790), [#797](https://github.com/optimizely/javascript-sdk/pull/797), [#799](https://github.com/optimizely/javascript-sdk/pull/799), [#808](https://github.com/optimizely/javascript-sdk/pull/808)). + +You can use ODP, a high-performance [Customer Data Platform (CDP)]( https://www.optimizely.com/optimization-glossary/customer-data-platform/), to easily create complex real-time segments (RTS) using first-party and 50+ third-party data sources out of the box. You can create custom schemas that support the user attributes important for your business, and stitch together user behavior done on different devices to better understand and target your customers for personalized user experiences. ODP can be used as a single source of truth for these segments in any Optimizely or 3rd party tool. + +With ODP accounts integrated into Optimizely projects, you can build audiences using segments pre-defined in ODP. The SDK will fetch the segments for given users and make decisions using the segments. For access to ODP audience targeting in your Feature Experimentation account, please contact your Customer Success Manager. + +This version includes the following changes: + +- New API added to `OptimizelyUserContext`: + + - `fetchQualifiedSegments()`: this API will retrieve user segments from the ODP server. The fetched segments will be used for audience evaluation. The fetched data will be stored in the local cache to avoid repeated network delays. + + - When an `OptimizelyUserContext` is created, the SDK will automatically send an identify request to the ODP server to facilitate observing user activities. + +- New APIs added to `OptimizelyClient`: + + - `sendOdpEvent()`: customers can build/send arbitrary ODP events that will bind user identifiers and data to user profiles in ODP. + + - `createUserContext()` with anonymous user IDs: user-contexts can be created without a userId. The SDK will create and use a persistent `VUID` specific to a device when userId is not provided. + +For details, refer to our documentation pages: + +- [Advanced Audience Targeting](https://docs.developers.optimizely.com/feature-experimentation/docs/optimizely-data-platform-advanced-audience-targeting) + +- [Client SDK Support](https://docs.developers.optimizely.com/feature-experimentation/v1.0/docs/advanced-audience-targeting-for-client-side-sdks) + +- [Initialize JavaScript SDK](https://docs.developers.optimizely.com/feature-experimentation/docs/initialize-sdk-javascript-aat) + +- [OptimizelyUserContext JavaScript SDK](https://docs.developers.optimizely.com/feature-experimentation/docs/optimizelyusercontext-javascript-aat) + +- [Advanced Audience Targeting segment qualification methods](https://docs.developers.optimizely.com/feature-experimentation/docs/advanced-audience-targeting-segment-qualification-methods-javascript) + +- [Send Optimizely Data Platform data using Advanced Audience Targeting](https://docs.developers.optimizely.com/feature-experimentation/docs/send-odp-data-using-advanced-audience-targeting-javascript) + +Additionally, a handful of major package updates are also included in this release including `murmurhash`, `uuid`, and others. For more information, check out the **Breaking Changes** section below. ([#892](https://github.com/optimizely/javascript-sdk/pull/892), [#762](https://github.com/optimizely/javascript-sdk/pull/762)) + +### Breaking Changes +- `ODPManager` in the SDK is enabled by default. Unless an ODP account is integrated into the Optimizely projects, most `ODPManager` functions will be ignored. If needed, `ODPManager` can be disabled when `OptimizelyClient` is instantiated. +- Updated `murmurhash` dependency to version `2.0.1`. +- Updated `uuid` dependency to version `9.0.1`. +- Dropped support for the following browser versions. + - All versions of Microsof Internet Explorer. + - Chrome versions earlier than `102.0`. + - Microsoft Edge versions earlier than `84.0`. + - Firefox versions earlier than `91.0`. + - Opera versions earlier than `76.0`. + - Safari versions earlier than `13.0`. +- Dropped support for Node JS versions earlier than `16`. + +## Changed +- Updated `createUserContext`'s `userId` parameter to be optional due to the Browser variation's use of the new `vuid` field. Note: The Node variation of the SDK does **not** use the new `vuid` field and you should pass in a `userId` when within the context of the Node variant. + + +## [4.10.0] - October 11, 2023 + +### New Features +- Add support for configurable closing event dispatcher, and dispatching events using sendBeacon in the browser on instance close ([#876](https://github.com/optimizely/javascript-sdk/pull/876), [#874](https://github.com/optimizely/javascript-sdk/pull/874), [#873](https://github.com/optimizely/javascript-sdk/pull/873)) + +## [5.0.0-beta5] - September 1, 2023 + +### Changed +- Exported logging related types and values from the package entrypoint ([#858](https://github.com/optimizely/javascript-sdk/pull/858)) +- Removed /lib directory from the published pacakage ([#862](https://github.com/optimizely/javascript-sdk/pull/862)) + +## [5.0.0-beta4] - August 22, 2023 + +### New Features +- Added support for configurable user agent parser for ODP ([#854](https://github.com/optimizely/javascript-sdk/pull/854)) + +### Bug fixes +- Fixed typescript compilation failure due to missing types ([#856](https://github.com/optimizely/javascript-sdk/pull/856)) + +## [5.0.0-beta3] - August 18, 2023 + +### Bug fixes +- Fixed odp event sending not working for Europe and Asia-Pacific regions ([#852](https://github.com/optimizely/javascript-sdk/pull/852)) + +### Changed +- Remove 1 second polling floor to allow datafile polling at any frequency but for intervals under 30 seconds, log a warning ([#841](https://github.com/optimizely/javascript-sdk/pull/841)). + +## [5.0.0-beta2] - July 19, 2023 + +### Performance Improvements +- Improved OptimizelyConfig class instantiation performance from O(n^2) to O(n) where n = number of feature flags ([#828](https://github.com/optimizely/javascript-sdk/pull/828)) + +### Bug fixes + +- Fixed ODP config update issue on datafile update ([#830](https://github.com/optimizely/javascript-sdk/pull/830)) + +## [4.9.4] - June 8, 2023 + +### Performance Improvements +- Improve OptimizelyConfig class instantiation performance from O(n^2) to O(n) where n = number of feature flags ([#829](https://github.com/optimizely/javascript-sdk/pull/829)) + +## 5.0.0-beta +May 4, 2023 + +### New Features + +The 5.0.0-beta release introduces a new primary feature, [Advanced Audience Targeting]( https://docs.developers.optimizely.com/feature-experimentation/docs/optimizely-data-platform-advanced-audience-targeting) enabled through integration with [Optimizely Data Platform (ODP)](https://docs.developers.optimizely.com/optimizely-data-platform/docs) ([#765](https://github.com/optimizely/javascript-sdk/pull/765), [#775](https://github.com/optimizely/javascript-sdk/pull/775), [#776](https://github.com/optimizely/javascript-sdk/pull/776), [#777](https://github.com/optimizely/javascript-sdk/pull/777), [#778](https://github.com/optimizely/javascript-sdk/pull/778), [#786](https://github.com/optimizely/javascript-sdk/pull/786), [#789](https://github.com/optimizely/javascript-sdk/pull/789), [#790](https://github.com/optimizely/javascript-sdk/pull/790), [#797](https://github.com/optimizely/javascript-sdk/pull/797), [#799](https://github.com/optimizely/javascript-sdk/pull/799), [#808](https://github.com/optimizely/javascript-sdk/pull/808)). + +You can use ODP, a high-performance [Customer Data Platform (CDP)]( https://www.optimizely.com/optimization-glossary/customer-data-platform/), to easily create complex real-time segments (RTS) using first-party and 50+ third-party data sources out of the box. You can create custom schemas that support the user attributes important for your business, and stitch together user behavior done on different devices to better understand and target your customers for personalized user experiences. ODP can be used as a single source of truth for these segments in any Optimizely or 3rd party tool. + +With ODP accounts integrated into Optimizely projects, you can build audiences using segments pre-defined in ODP. The SDK will fetch the segments for given users and make decisions using the segments. For access to ODP audience targeting in your Feature Experimentation account, please contact your Customer Success Manager. + +This version includes the following changes: + +- New API added to `OptimizelyUserContext`: + + - `fetchQualifiedSegments()`: this API will retrieve user segments from the ODP server. The fetched segments will be used for audience evaluation. The fetched data will be stored in the local cache to avoid repeated network delays. + + - When an `OptimizelyUserContext` is created, the SDK will automatically send an identify request to the ODP server to facilitate observing user activities. + +- New APIs added to `OptimizelyClient`: + + - `sendOdpEvent()`: customers can build/send arbitrary ODP events that will bind user identifiers and data to user profiles in ODP. + + - `createUserContext()` with anonymous user IDs: user-contexts can be created without a userId. The SDK will create and use a persistent `VUID` specific to a device when userId is not provided. + +For details, refer to our documentation pages: + +- [Advanced Audience Targeting](https://docs.developers.optimizely.com/feature-experimentation/docs/optimizely-data-platform-advanced-audience-targeting) + +- [Client SDK Support](https://docs.developers.optimizely.com/feature-experimentation/v1.0/docs/advanced-audience-targeting-for-client-side-sdks) + +- [Initialize JavaScript SDK](https://docs.developers.optimizely.com/feature-experimentation/docs/initialize-sdk-javascript-aat) + +- [OptimizelyUserContext JavaScript SDK](https://docs.developers.optimizely.com/feature-experimentation/docs/optimizelyusercontext-javascript-aat) + +- [Advanced Audience Targeting segment qualification methods](https://docs.developers.optimizely.com/feature-experimentation/docs/advanced-audience-targeting-segment-qualification-methods-javascript) + +- [Send Optimizely Data Platform data using Advanced Audience Targeting](https://docs.developers.optimizely.com/feature-experimentation/docs/send-odp-data-using-advanced-audience-targeting-javascript) + +Additionally, a handful of major package updates are also included in this release including `murmurhash`, `uuid`, and others. For more information, check out the **Breaking Changes** section below. ([#762](https://github.com/optimizely/javascript-sdk/pull/762)) + +### Breaking Changes +- `ODPManager` in the SDK is enabled by default. Unless an ODP account is integrated into the Optimizely projects, most `ODPManager` functions will be ignored. If needed, `ODPManager` can be disabled when `OptimizelyClient` is instantiated. - Updated `murmurhash` dependency to version `2.0.1`. - Updated `uuid` dependency to version `8.3.2`. - Dropped support for the following browser versions. @@ -19,6 +325,14 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Safari versions earlier than `13.0`. - Dropped support for Node JS versions earlier than `14`. +## Changed +- Updated `createUserContext`'s `userId` parameter to be optional due to the Browser variation's use of the new `vuid` field. Note: The Node variation of the SDK does **not** use the new `vuid` field and you should pass in a `userId` when within the context of the Node variant. + +## [4.9.3] - March 17, 2023 + +### Changed +- Updated README.md and package.json files to reflect that this SDK supports both Optimizely Feature Experimentation and Optimizely Full Stack ([#803](https://github.com/optimizely/javascript-sdk/pull/803)). + ## [4.9.2] - June 27, 2022 ### Changed diff --git a/LICENSE b/LICENSE index b9f80c5bd..e2d144779 100644 --- a/LICENSE +++ b/LICENSE @@ -187,7 +187,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2016-2017, Optimizely, Inc. and contributors + © Optimizely 2016 Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 000000000..8a2173ebf --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,464 @@ +# Migrating v5 to v6 + +This guide will help you migrate your implementation from Optimizely JavaScript SDK v5 to v6. The new version introduces several architectural changes that provide more flexibility and control over SDK components. + +## Table of Contents + +1. [Major Changes](#major-changes) +2. [Client Initialization](#client-initialization) +3. [Project Configuration Management](#project-configuration-management) +4. [Event Processing](#event-processing) +5. [ODP Management](#odp-management) +6. [VUID Management](#vuid-management) +7. [Error Handling](#error-handling) +8. [Logging](#logging) +9. [onReady Promise Behavior](#onready-promise-behavior) +10. [Dispose of Client](#dispose-of-client) +11. [Migration Examples](#migration-examples) + +## Major Changes + +In v6, the SDK architecture has been modularized to give you more control over different components: + +- The monolithic `createInstance` call is now split into multiple factory functions +- Core functionalities (project configuration, event processing, ODP, VUID, logging, and error handling) are now configured through dedicated components created via factory functions, giving you greater flexibility and control in enabling/disabling certain components and allowing optimizing the bundle size for frontend projects. +- Event dispatcher interface has been updated to use Promises +- onReady Promise behavior has changed + +## Client Initialization + +### v5 (Before) + +```javascript +import { createInstance } from '@optimizely/optimizely-sdk'; + +const optimizely = createInstance({ + sdkKey: '<YOUR_SDK_KEY>', + datafile: datafile, // optional + datafileOptions: { + autoUpdate: true, + updateInterval: 300000, // 5 minutes + }, + eventBatchSize: 10, + eventFlushInterval: 1000, + logLevel: LogLevel.DEBUG, + errorHandler: { handleError: (error) => console.error(error) }, + odpOptions: { + disabled: false, + segmentsCacheSize: 100, + segmentsCacheTimeout: 600000, // 10 minutes + } +}); +``` + +### v6 (After) + +```javascript +import { + createInstance, + createPollingProjectConfigManager, + createBatchEventProcessor, + createOdpManager, + createVuidManager, + createLogger, + createErrorNotifier, + DEBUG +} from "@optimizely/optimizely-sdk"; + +// Create a project config manager +const projectConfigManager = createPollingProjectConfigManager({ + sdkKey: '<YOUR_SDK_KEY>', + datafile: datafile, // optional + autoUpdate: true, + updateInterval: 300000, // 5 minutes in milliseconds +}); + +// Create an event processor +const eventProcessor = createBatchEventProcessor({ + batchSize: 10, + flushInterval: 1000, +}); + +// Create an ODP manager +const odpManager = createOdpManager({ + segmentsCacheSize: 100, + segmentsCacheTimeout: 600000, // 10 minutes +}); + +// Create a VUID manager (optional) +const vuidManager = createVuidManager({ + enableVuid: true +}); + +// Create a logger +const logger = createLogger({ + level: DEBUG +}); + +// Create an error notifier +const errorNotifier = createErrorNotifier({ + handleError: (error) => console.error(error) +}); + +// Create the Optimizely client instance +const optimizely = createInstance({ + projectConfigManager, + eventProcessor, + odpManager, + vuidManager, + logger, + errorNotifier +}); +``` + +In case an invalid config is passed to `createInstance`, it returned `null` in v5. In v6, it will throw an error instead of returning null. + +## Project Configuration Management + +In v6, datafile management must be configured by passing in a `projectConfigManager`. Choose either: + +### Polling Project Config Manager + +For automatic datafile updates: + +```javascript +const projectConfigManager = createPollingProjectConfigManager({ + sdkKey: '<YOUR_SDK_KEY>', + datafile: datafileString, // optional + autoUpdate: true, + updateInterval: 60000, // 1 minute + urlTemplate: 'https://custom-cdn.com/datafiles/%s.json' // optional +}); +``` + +### Static Project Config Manager + +When you want to manage datafile updates manually or want to use a fixed datafile: + +```javascript +const projectConfigManager = createStaticProjectConfigManager({ + datafile: datafileString, +}); +``` + +## Event Processing + +In v5, a batch event processor was enabled by default. In v6, an event processor must be instantiated and passed in +explicitly to `createInstance` via the `eventProcessor` option to enable event processing, otherwise no events will +be dispatched. v6 provides two types of event processors: + +### Batch Event Processor + +Queues events and sends them in batches: + +```javascript +const batchEventProcessor = createBatchEventProcessor({ + batchSize: 10, // optional, default is 10 + flushInterval: 1000, // optional, default 1000 for browser +}); +``` + +### Forwarding Event Processor + +Sends events immediately: + +```javascript +const forwardingEventProcessor = createForwardingEventProcessor(); +``` + +### Custom event dispatcher +In both v5 and v6, custom event dispatchers must implement the `EventDispatcher` interface. In v6, the `EventDispatcher` interface has been updated so that the `dispatchEvent` method returns a Promise instead of calling a callback. + +In v5 (Before): + +```javascript +export type EventDispatcherResponse = { + statusCode: number +} + +export type EventDispatcherCallback = (response: EventDispatcherResponse) => void + +export interface EventDispatcher { + dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void +} +``` + +In v6(After): + +```javascript +export type EventDispatcherResponse = { + statusCode?: number +} + +export interface EventDispatcher { + dispatchEvent(event: LogEvent): Promise<EventDispatcherResponse> +} +``` + +## ODP Management + +In v5, ODP functionality was configured via `odpOptions` and enabled by default. In v6, instantiate an OdpManager and pass to `createInstance` to enable ODP: + +### v5 (Before) + +```javascript +const optimizely = createInstance({ + sdkKey: '<YOUR_SDK_KEY>', + odpOptions: { + disabled: false, + segmentsCacheSize: 100, + segmentsCacheTimeout: 600000, // 10 minutes + eventApiTimeout: 1000, + segmentsApiTimeout: 1000, + } +}); +``` + +### v6 (After) + +```javascript +const odpManager = createOdpManager({ + segmentsCacheSize: 100, + segmentsCacheTimeout: 600000, // 10 minutes + eventApiTimeout: 1000, + segmentsApiTimeout: 1000, + eventBatchSize: 5, // Now configurable in browser + eventFlushInterval: 3000, // Now configurable in browser +}); + +const optimizely = createInstance({ + projectConfigManager, + odpManager +}); +``` + +To disable ODP functionality in v6, simply don't provide an ODP Manager to the client instance. + +## VUID Management + +In v6, VUID tracking is disabled by default and must be explicitly enabled by createing a vuidManager with `enableVuid` set to `true` and passing it to `createInstance`: + +```javascript +const vuidManager = createVuidManager({ + enableVuid: true, // Explicitly enable VUID tracking +}); + +const optimizely = createInstance({ + projectConfigManager, + vuidManager +}); +``` + +## Error Handling + +Error handling in v6 uses a new errorNotifier object: + +### v5 (Before) + +```javascript +const optimizely = createInstance({ + errorHandler: { + handleError: (error) => { + console.error("Custom error handler", error); + } + } +}); +``` + +### v6 (After) + +```javascript +const errorNotifier = createErrorNotifier({ + handleError: (error) => { + console.error("Custom error handler", error); + } +}); + +const optimizely = createInstance({ + projectConfigManager, + errorNotifier +}); +``` + +## Logging + +Logging in v6 is disabled by defualt, and must be enabled by passing in a logger created via a factory function: + +### v5 (Before) + +```javascript +const optimizely = createInstance({ + logLevel: LogLevel.DEBUG +}); +``` + +### v6 (After) + +```javascript +import { createLogger, DEBUG } from "@optimizely/optimizely-sdk"; + +const logger = createLogger({ + level: DEBUG +}); + +const optimizely = createInstance({ + projectConfigManager, + logger +}); +``` + +## onReady Promise Behavior + +The `onReady()` method behavior has changed in v6. In v5, onReady() fulfilled with an object that had two fields: `success` and `reason`. If the instance failed to initialize, `success` would be `false` and `reason` will contain an error message. In v6, if onReady() fulfills, that means the instance is ready to use, the fulfillment value is of unknown type and need not to be inspected. If the promise rejects, that means there was an error during initialization. + +### v5 (Before) + +```javascript +optimizely.onReady().then(({ success, reason }) => { + if (success) { + // optimizely is ready to use + } else { + console.log(`initialization unsuccessful: ${reason}`); + } +}); +``` + +### v6 (After) + +```javascript +optimizely + .onReady() + .then(() => { + // optimizely is ready to use + console.log("Client is ready"); + }) + .catch((err) => { + console.error("Error initializing Optimizely client:", err); + }); +``` + +## Migration Examples + +### Basic Example with SDK Key + +#### v5 (Before) + +```javascript +import { createInstance } from '@optimizely/optimizely-sdk'; + +const optimizely = createInstance({ + sdkKey: '<YOUR_SDK_KEY>' +}); + +optimizely.onReady().then(({ success }) => { + if (success) { + // Use the client + } +}); +``` + +#### v6 (After) + +```javascript +import { + createInstance, + createPollingProjectConfigManager +} from '@optimizely/optimizely-sdk'; + +const projectConfigManager = createPollingProjectConfigManager({ + sdkKey: '<YOUR_SDK_KEY>' +}); + +const optimizely = createInstance({ + projectConfigManager +}); + +optimizely + .onReady() + .then(() => { + // Use the client + }) + .catch(err => { + console.error(err); + }); +``` + +### Complete Example with ODP and Event Batching + +#### v5 (Before) + +```javascript +import { createInstance, LogLevel } from '@optimizely/optimizely-sdk'; + +const optimizely = createInstance({ + sdkKey: '<YOUR_SDK_KEY>', + datafileOptions: { + autoUpdate: true, + updateInterval: 60000 // 1 minute + }, + eventBatchSize: 3, + eventFlushInterval: 10000, // 10 seconds + logLevel: LogLevel.DEBUG, + odpOptions: { + segmentsCacheSize: 10, + segmentsCacheTimeout: 60000 // 1 minute + } +}); + +optimizely.notificationCenter.addNotificationListener( + enums.NOTIFICATION_TYPES.TRACK, + (payload) => { + console.log("Track event", payload); + } +); +``` + +#### v6 (After) + +```javascript +import { + createInstance, + createPollingProjectConfigManager, + createBatchEventProcessor, + createOdpManager, + createLogger, + DEBUG, + NOTIFICATION_TYPES +} from '@optimizely/optimizely-sdk'; + +const projectConfigManager = createPollingProjectConfigManager({ + sdkKey: '<YOUR_SDK_KEY>', + autoUpdate: true, + updateInterval: 60000 // 1 minute +}); + +const batchEventProcessor = createBatchEventProcessor({ + batchSize: 3, + flushInterval: 10000, // 10 seconds +}); + +const odpManager = createOdpManager({ + segmentsCacheSize: 10, + segmentsCacheTimeout: 60000 // 1 minute +}); + +const logger = createLogger({ + level: DEBUG +}); + +const optimizely = createInstance({ + projectConfigManager, + eventProcessor: batchEventProcessor, + odpManager, + logger +}); + +optimizely.notificationCenter.addNotificationListener( + NOTIFICATION_TYPES.TRACK, + (payload) => { + console.log("Track event", payload); + } +); +``` + +For complete implementation examples, refer to the [Optimizely JavaScript SDK documentation](https://docs.developers.optimizely.com/feature-experimentation/docs/javascript-browser-sdk-v6). diff --git a/README.md b/README.md index bc81907db..08ab9f5ad 100644 --- a/README.md +++ b/README.md @@ -1,66 +1,280 @@ -<h3 align="center"> - Optimizely JavaScript SDK -</h3> +# Optimizely JavaScript SDK -<p align="center"> - This repository houses the official JavaScript SDK for use with Optimizely Full Stack and Optimizely Rollouts. -</p> +[![npm](https://img.shields.io/npm/v/%40optimizely%2Foptimizely-sdk.svg)](https://www.npmjs.com/package/@optimizely/optimizely-sdk) +[![npm](https://img.shields.io/npm/dm/%40optimizely%2Foptimizely-sdk.svg)](https://www.npmjs.com/package/@optimizely/optimizely-sdk) +[![GitHub Actions](https://img.shields.io/github/actions/workflow/status/optimizely/javascript-sdk/javascript.yml)](https://github.com/optimizely/javascript-sdk/actions) +[![Coveralls](https://img.shields.io/coveralls/optimizely/javascript-sdk.svg)](https://coveralls.io/github/optimizely/javascript-sdk) +[![license](https://img.shields.io/github/license/optimizely/javascript-sdk.svg)](https://choosealicense.com/licenses/apache-2.0/) -Optimizely Full Stack is A/B testing and feature flag management for product development teams. Experiment in any application. Make every feature on your roadmap an opportunity to learn. Learn more at https://www.optimizely.com/platform/full-stack/, or see the [documentation](https://docs.developers.optimizely.com/full-stack/docs). +This is the official JavaScript and TypeScript SDK for use with Optimizely Feature Experimentation and Optimizely Full Stack (legacy). The SDK now features a modular architecture for greater flexibility and control. If you're upgrading from a previous version, see our [Migration Guide](MIGRATION.md). -Optimizely Rollouts is free feature flags for development teams. Easily roll out and roll back features in any application without code deploys. Mitigate risk for every feature on your roadmap. Learn more at https://www.optimizely.com/rollouts/, or see the [documentation](https://docs.developers.optimizely.com/rollouts/docs). +Optimizely Feature Experimentation is an A/B testing and feature management tool for product development teams that enables you to experiment at every step. Using Optimizely Feature Experimentation allows for every feature on your roadmap to be an opportunity to discover hidden insights. Learn more at [Optimizely.com](https://www.optimizely.com/products/experiment/feature-experimentation/), or see the [developer documentation](https://docs.developers.optimizely.com/feature-experimentation/docs/introduction). +Optimizely Rollouts is [free feature flags](https://www.optimizely.com/free-feature-flagging/) for development teams. You can easily roll out and roll back features in any application without code deploys, mitigating risk for every feature on your roadmap. -## Packages +--- -This repository is a monorepo. It houses the main Javascript SDK and its supporting packages. +## Get Started -| Package | Version | Docs | Description | -| ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | -| [`@optimizely/optimizely-sdk`](/packages/optimizely-sdk) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Foptimizely-sdk.svg)](https://www.npmjs.com/package/@optimizely/optimizely-sdk) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/javascript-node-sdk) | The Optimizely SDK | -| [`@optimizely/js-sdk-datafile-manager`](/packages/datafile-manager) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-datafile-manager.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-datafile-manager) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/initialize-sdk-javascript-node#customize-datafile-management-behavior) | Datafile Manager for Optimizely SDK -| [`@optimizely/js-sdk-event-processor`](/packages/event-processor) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-event-processor.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-event-processor) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/event-batching-javascript-node) | Event Batching support for Optimizely SDK -| [`@optimizely/js-sdk-logging`](/packages/logging) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-logging.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-logging) | [![](https://img.shields.io/badge/API%20Docs-site-green.svg?style=flat-square)](https://docs.developers.optimizely.com/full-stack/docs/customize-logger-javascript-node) | Logging Manager for Optimizely SDK -| [`@optimizely/js-sdk-utils`](/packages/utils) | [![npm](https://img.shields.io/npm/v/%40optimizely%2Fjs-sdk-utils.svg)](https://www.npmjs.com/package/@optimizely/js-sdk-utils) | | Utility functions for Optimizely packages +> Refer to the [JavaScript SDK's developer documentation](https://docs.developers.optimizely.com/feature-experimentation/docs/javascript-sdk) for detailed instructions on getting started with using the SDK. -## About -`@optimizely/optimizely-sdk` is developed and maintained by [Optimizely](https://optimizely.com) and many [contributors](https://github.com/optimizely/javascript-sdk/graphs/contributors). If you're interested in learning more about what Optimizely X Full Stack can do for your company, please [get in touch](mailto:eng@optimizely.com)! +> For **Edge Functions**, we provide starter kits that utilize the Optimizely JavaScript SDK for the following platforms: +> +> - [Akamai (Edgeworkers)](https://github.com/optimizely/akamai-edgeworker-starter-kit) +> - [AWS Lambda@Edge](https://github.com/optimizely/aws-lambda-at-edge-starter-kit) +> - [Cloudflare Worker](https://github.com/optimizely/cloudflare-worker-template) +> - [Fastly Compute@Edge](https://github.com/optimizely/fastly-compute-starter-kit) +> - [Vercel Edge Middleware](https://github.com/optimizely/vercel-examples/tree/main/edge-middleware/feature-flag-optimizely) +> +> Note: We recommend using the **Lite** entrypoint (for version < 6) / **Universal** entrypoint (for version >=6) of the sdk for edge platforms. These starter kits also use the **Lite** variant of the JavaScript SDK. +### Prerequisites + +Ensure the SDK supports all of the platforms you're targeting. In particular, the SDK targets modern ES6-compliant JavaScript environments. We officially support: + +- Node.js >= 18.0.0. By extension, environments like AWS Lambda, Google Cloud Functions, and Auth0 Webtasks are supported as well. Older Node.js releases likely work too (try `npm test` to validate for yourself), but are not formally supported. +- Modern Web Browsers, such as Microsoft Edge 84+, Firefox 91+, Safari 13+, and Chrome 102+, Opera 76+ + +In addition, other environments are likely compatible but are not formally supported including: + +- Progressive Web Apps, WebViews, and hybrid mobile apps like those built with React Native and Apache Cordova. +- [Cloudflare Workers](https://developers.cloudflare.com/workers/) and [Fly](https://fly.io/), both of which are powered by recent releases of V8. +- Anywhere else you can think of that might embed a JavaScript engine. The sky is the limit; experiment everywhere! 🚀 + +### Install the SDK + +Once you've validated that the SDK supports the platforms you're targeting, fetch the package from [NPM](https://www.npmjs.com/package/@optimizely/optimizely-sdk): + +Using `npm`: + +```sh +npm install --save @optimizely/optimizely-sdk +``` + +Using `yarn`: + +```sh +yarn add @optimizely/optimizely-sdk +``` + +Using `pnpm`: + +```sh +pnpm add @optimizely/optimizely-sdk +``` + +Using `deno` (no installation required): + +```javascript +import optimizely from 'npm:@optimizely/optimizely-sdk'; +``` + +## Use the JavaScript SDK + +See the [JavaScript SDK's developer documentation](https://docs.developers.optimizely.com/feature-experimentation/docs/javascript-sdk) to learn how to set up your first JavaScript project using the SDK. + +The SDK uses a modular architecture with dedicated components for project configuration, event processing, and more. The examples below demonstrate the recommended initialization pattern. + +### Initialization with Package Managers (npm, yarn, pnpm) + +```javascript +import { + createInstance, + createPollingProjectConfigManager, + createBatchEventProcessor, + createOdpManager, +} from '@optimizely/optimizely-sdk'; + +// 1. Configure your project config manager +const pollingConfigManager = createPollingProjectConfigManager({ + sdkKey: '<YOUR_SDK_KEY>', + autoUpdate: true, // Optional: enable automatic updates + updateInterval: 300000, // Optional: update every 5 minutes (in ms) +}); + +// 2. Create an event processor for analytics +const batchEventProcessor = createBatchEventProcessor({ + batchSize: 10, // Optional: default batch size + flushInterval: 1000, // Optional: flush interval in ms +}); + +// 3. Set up ODP manager for segments and audience targeting +const odpManager = createOdpManager(); + +// 4. Initialize the Optimizely client with the components +const optimizelyClient = createInstance({ + projectConfigManager: pollingConfigManager, + eventProcessor: batchEventProcessor, + odpManager: odpManager, +}); + +optimizelyClient + .onReady() + .then(() => { + console.log('Optimizely client is ready'); + // Your application code using Optimizely goes here + }) + .catch(error => { + console.error('Error initializing Optimizely client:', error); + }); +``` + +### Initialization (Using HTML script tag) + +The package has different entry points for different environments. The browser entry point is an ES module, which can be used with an appropriate bundler like **Webpack** or **Rollup**. Additionally, for ease of use during initial evaluations you can include a standalone umd bundle of the SDK in your web page by fetching it from [unpkg](https://unpkg.com/): + +```html +<script src="https://unpkg.com/@optimizely/optimizely-sdk@6/dist/optimizely.browser.umd.min.js"></script> + +<!-- You can also use the unminified version if necessary --> +<script src="https://unpkg.com/@optimizely/optimizely-sdk@6/dist/optimizely.browser.umd.js"></script> +``` + +> ⚠️ **Warning**: Always include a specific version number (such as @6) when using CDN URLs like the `unpkg` example above. If you use a URL without a version, your application may automatically receive breaking changes when a new major version is released, which can lead to unexpected issues. + +When evaluated, that bundle assigns the SDK's exports to `window.optimizelySdk`. If you wish to use the asset locally (for example, if unpkg is down), you can find it in your local copy of the package at dist/optimizely.browser.umd.min.js. We do not recommend using this method in production settings as it introduces a third-party performance dependency. + +As `window.optimizelySdk` should be a global variable at this point, you can continue to use it like so: + +```html +<script> + // Extract the factory functions from the global SDK + const { + createInstance, + createPollingProjectConfigManager, + createBatchEventProcessor, + createOdpManager, + } = window.optimizelySdk; + + // Initialize components + const pollingConfigManager = createPollingProjectConfigManager({ + sdkKey: '<YOUR_SDK_KEY>', + autoUpdate: true, + }); + + const batchEventProcessor = createBatchEventProcessor(); + + const odpManager = createOdpManager(); + + // Create the Optimizely client + const optimizelyClient = createInstance({ + projectConfigManager: pollingConfigManager, + eventProcessor: batchEventProcessor, + odpManager: odpManager, + }); + + optimizelyClient + .onReady() + .then(() => { + console.log('Optimizely client is ready'); + // Start using the client here + }) + .catch(error => { + console.error('Error initializing Optimizely client:', error); + }); +</script> +``` + +### Closing the SDK Instance + +Depending on the sdk configuration, the client instance might schedule tasks in the background. If the instance has background tasks scheduled, +then the instance will not be garbage collected even though there are no more references to the instance in the code. (Basically, the background tasks will still hold references to the instance). Therefore, it's important to close it to properly clean up resources. + +```javascript +// Close the Optimizely client when you're done using it +optimizelyClient.close() +``` +Using the following settings will cause background tasks to be scheduled + +- Polling Datafile Manager +- Batch Event Processor with batchSize > 1 +- ODP manager with eventBatchSize > 1 + + + +> ⚠️ **Warning**: Failure to close SDK instances when they're no longer needed may result in memory leaks. This is particularly important for applications that create multiple instances over time. For some environment like SSR applications, it might not be convenient to close each instance, in which case, the `disposable` option of `createInstance` can be used to disable all background tasks on the server side, allowing the instance to be garbage collected. + + +## Special Notes + +### Migration Guides + +If you're updating your SDK version, please check the appropriate migration guide: + +- **Migrating from 5.x or lower to 6.x**: See our [Migration Guide](MIGRATION.md) for detailed instructions on updating to the new modular architecture. +- **Migrating from 4.x or lower to 5.x**: Please refer to the [Changelog](CHANGELOG.md#500---january-19-2024) for details on these breaking changes. + +## SDK Development + +### Unit Tests + +There is a mix of testing paradigms used within the JavaScript SDK which include Mocha, Chai, Karma, and Vitest, indicated by their respective `*.tests.js` and `*.spec.ts` filenames. + +When contributing code to the SDK, aim to keep the percentage of code test coverage at the current level ([![Coveralls](https://img.shields.io/coveralls/optimizely/javascript-sdk.svg)](https://coveralls.io/github/optimizely/javascript-sdk)) or above. + +To run unit tests, you can take the following steps: + +1. Ensure that you have run `npm install` to install all project dependencies. +2. Run `npm test` to run all test files. +3. Run `npm run test-vitest` to run only tests written using Vitest. +4. Run `npm run test-mocha` to run only tests written using Mocha. +4. (For cross-browser testing) Run `npm run test-xbrowser` to run tests in many browsers via BrowserStack. +5. Resolve any tests that fail before continuing with your contribution. + +This information is relevant only if you plan on contributing to the SDK itself. + +```sh +# Prerequisite: Install dependencies. +npm install + +# Run unit tests. +npm test + +# Run unit tests in many browsers, currently via BrowserStack. +# For this to work, the following environment variables must be set: +# - BROWSER_STACK_USERNAME +# - BROWSER_STACK_PASSWORD +npm run test-xbrowser +``` + +[/.github/workflows/javascript.yml](/.github/workflows/javascript.yml) contains the definitions for `BROWSER_STACK_USERNAME` and `BROWSER_STACK_ACCESS_KEY` used in the GitHub Actions CI pipeline. When developing locally, you must provide your own credentials in order to run `npm run test-xbrowser`. You can register for an account for free on [the BrowserStack official website here](https://www.browserstack.com/). ### Contributing -Please see [CONTRIBUTING](CONTRIBUTING.md). +For more information regarding contributing to the Optimizely JavaScript SDK, please read [Contributing](CONTRIBUTING.md). + + +### Feature Management access + +To access the Feature Management configuration in the Optimizely dashboard, please contact your Optimizely customer success manager. ## Credits -First-party code (under `packages/optimizely-sdk/lib/`, `packages/datafile-manager/lib`, `packages/datafile-manager/src`, `packages/datafile-manager/__test__`, `packages/event-processor/src`, `packages/event-processor/__tests__`, `packages/logging/src`, `packages/logging/__tests__`, `packages/utils/src`, `packages/utils/__tests__`) is copyright Optimizely, Inc. and contributors, licensed under Apache 2.0. - -## Additional Code - -Prod dependencies are as follows: - -```json -{ - "json-schema@0.4.0": { - "licenses": [ - "AFLv2.1", - "BSD" - ], - "publisher": "Kris Zyp", - "repository": "https://github.com/kriszyp/json-schema" - }, - "murmurhash@0.0.2": { - "licenses": "MIT*", - "repository": "https://github.com/perezd/node-murmurhash" - }, - "uuid@3.3.2": { - "licenses": "MIT", - "repository": "https://github.com/kelektiv/node-uuid" - }, - "decompress-response@4.2.1": { - "licenses": "MIT", - "repository": "https://github.com/sindresorhus/decompress-response" - } -} -``` +`@optimizely/optimizely-sdk` is developed and maintained by [Optimizely](https://optimizely.com) and many [contributors](https://github.com/optimizely/javascript-sdk/graphs/contributors). If you're interested in learning more about what Optimizely Feature Experimentation can do for your company you can visit the [official Optimizely Feature Experimentation product page here](https://www.optimizely.com/products/experiment/feature-experimentation/) to learn more. + +First-party code (under `lib/`) is copyright Optimizely, Inc., licensed under Apache 2.0. + +### Other Optimizely SDKs + +- Agent - https://github.com/optimizely/agent + +- Android - https://github.com/optimizely/android-sdk + +- C# - https://github.com/optimizely/csharp-sdk + +- Flutter - https://github.com/optimizely/optimizely-flutter-sdk + +- Go - https://github.com/optimizely/go-sdk + +- Java - https://github.com/optimizely/java-sdk + +- PHP - https://github.com/optimizely/php-sdk + +- Python - https://github.com/optimizely/python-sdk + +- React - https://github.com/optimizely/react-sdk + +- Ruby - https://github.com/optimizely/ruby-sdk + +- Swift - https://github.com/optimizely/swift-sdk diff --git a/packages/optimizely-sdk/__mocks__/@react-native-async-storage/async-storage-event-processor.ts b/__mocks__/@react-native-async-storage/async-storage-event-processor.ts similarity index 95% rename from packages/optimizely-sdk/__mocks__/@react-native-async-storage/async-storage-event-processor.ts rename to __mocks__/@react-native-async-storage/async-storage-event-processor.ts index 1ba23231b..ad40f0152 100644 --- a/packages/optimizely-sdk/__mocks__/@react-native-async-storage/async-storage-event-processor.ts +++ b/__mocks__/@react-native-async-storage/async-storage-event-processor.ts @@ -36,6 +36,7 @@ export default class AsyncStorage { return new Promise(resolve => { setTimeout(() => { items[key] && delete items[key] + // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore resolve() }, 1) diff --git a/__mocks__/@react-native-async-storage/async-storage.ts b/__mocks__/@react-native-async-storage/async-storage.ts new file mode 100644 index 000000000..36d3cf85d --- /dev/null +++ b/__mocks__/@react-native-async-storage/async-storage.ts @@ -0,0 +1,56 @@ +/** + * Copyright 2020, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export default class AsyncStorage { + private static items: Record<string, string> = {}; + + static getItem( + key: string, + callback?: (error?: Error, result?: string | null) => void + ): Promise<string | null> { + const value = AsyncStorage.items[key] || null; + callback?.(undefined, value); + return Promise.resolve(value); + } + + static setItem( + key: string, + value: string, + callback?: (error?: Error) => void + ): Promise<void> { + AsyncStorage.items[key] = value; + callback?.(undefined); + return Promise.resolve(); + } + + static removeItem( + key: string, + callback?: (error?: Error, result?: string | null) => void + ): Promise<string | null> { + const value = AsyncStorage.items[key] || null; + if (key in AsyncStorage.items) { + delete AsyncStorage.items[key]; + } + callback?.(undefined, value); + return Promise.resolve(value); + } + + static clearStore(): Promise<void> { + AsyncStorage.items = {}; + return Promise.resolve(); + } + +} diff --git a/packages/optimizely-sdk/__mocks__/@react-native-community/netinfo.ts b/__mocks__/@react-native-community/netinfo.ts similarity index 100% rename from packages/optimizely-sdk/__mocks__/@react-native-community/netinfo.ts rename to __mocks__/@react-native-community/netinfo.ts diff --git a/__mocks__/fast-text-encoding.ts b/__mocks__/fast-text-encoding.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/__mocks__/fast-text-encoding.ts @@ -0,0 +1 @@ +export {}; diff --git a/__mocks__/react-native-get-random-values.ts b/__mocks__/react-native-get-random-values.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/__mocks__/react-native-get-random-values.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/optimizely-sdk/karma.base.conf.js b/karma.base.conf.js similarity index 100% rename from packages/optimizely-sdk/karma.base.conf.js rename to karma.base.conf.js diff --git a/packages/optimizely-sdk/karma.bs.conf.js b/karma.bs.conf.js similarity index 100% rename from packages/optimizely-sdk/karma.bs.conf.js rename to karma.bs.conf.js diff --git a/packages/optimizely-sdk/karma.local_chrome.bs.conf.js b/karma.local_chrome.bs.conf.js similarity index 100% rename from packages/optimizely-sdk/karma.local_chrome.bs.conf.js rename to karma.local_chrome.bs.conf.js diff --git a/packages/optimizely-sdk/karma.local_chrome.umd.conf.js b/karma.local_chrome.umd.conf.js similarity index 100% rename from packages/optimizely-sdk/karma.local_chrome.umd.conf.js rename to karma.local_chrome.umd.conf.js diff --git a/packages/optimizely-sdk/karma.umd.conf.js b/karma.umd.conf.js similarity index 100% rename from packages/optimizely-sdk/karma.umd.conf.js rename to karma.umd.conf.js diff --git a/lib/client_factory.spec.ts b/lib/client_factory.spec.ts new file mode 100644 index 000000000..1aa09bda0 --- /dev/null +++ b/lib/client_factory.spec.ts @@ -0,0 +1,61 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect } from 'vitest'; + +import { getOptimizelyInstance } from './client_factory'; +import { createStaticProjectConfigManager } from './project_config/config_manager_factory'; +import Optimizely from './optimizely'; + +describe('getOptimizelyInstance', () => { + it('should throw if the projectConfigManager is not a valid ProjectConfigManager', () => { + expect(() => getOptimizelyInstance({ + projectConfigManager: undefined as any, + requestHandler: {} as any, + })).toThrow('Invalid config manager'); + + expect(() => getOptimizelyInstance({ + projectConfigManager: null as any, + requestHandler: {} as any, + })).toThrow('Invalid config manager'); + + expect(() => getOptimizelyInstance({ + projectConfigManager: 'abc' as any, + requestHandler: {} as any, + })).toThrow('Invalid config manager'); + + expect(() => getOptimizelyInstance({ + projectConfigManager: 123 as any, + requestHandler: {} as any, + })).toThrow('Invalid config manager'); + + expect(() => getOptimizelyInstance({ + projectConfigManager: {} as any, + requestHandler: {} as any, + })).toThrow('Invalid config manager'); + }); + + it('should return an instance of Optimizely if a valid projectConfigManager is provided', () => { + const optimizelyInstance = getOptimizelyInstance({ + projectConfigManager: createStaticProjectConfigManager({ + datafile: '{}', + }), + requestHandler: {} as any, + }); + + expect(optimizelyInstance).toBeInstanceOf(Optimizely); + }); +}); diff --git a/lib/client_factory.ts b/lib/client_factory.ts new file mode 100644 index 000000000..3be99b554 --- /dev/null +++ b/lib/client_factory.ts @@ -0,0 +1,96 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Config } from "./shared_types"; +import { extractLogger } from "./logging/logger_factory"; +import { extractErrorNotifier } from "./error/error_notifier_factory"; +import { extractConfigManager } from "./project_config/config_manager_factory"; +import { extractEventProcessor } from "./event_processor/event_processor_factory"; +import { extractOdpManager } from "./odp/odp_manager_factory"; +import { extractVuidManager } from "./vuid/vuid_manager_factory"; +import { RequestHandler } from "./utils/http_request_handler/http"; +import { CLIENT_VERSION, DEFAULT_CMAB_BACKOFF_MS, DEFAULT_CMAB_CACHE_SIZE, DEFAULT_CMAB_CACHE_TIMEOUT_MS, DEFAULT_CMAB_RETRIES, JAVASCRIPT_CLIENT_ENGINE } from "./utils/enums"; +import Optimizely from "./optimizely"; +import { DefaultCmabClient } from "./core/decision_service/cmab/cmab_client"; +import { CmabCacheValue, DefaultCmabService } from "./core/decision_service/cmab/cmab_service"; +import { InMemoryLruCache } from "./utils/cache/in_memory_lru_cache"; +import { transformCache, CacheWithRemove } from "./utils/cache/cache"; +import { ConstantBackoff } from "./utils/repeater/repeater"; + +export type OptimizelyFactoryConfig = Config & { + requestHandler: RequestHandler; +} + +export const getOptimizelyInstance = (config: OptimizelyFactoryConfig): Optimizely => { + const { + clientEngine, + clientVersion, + jsonSchemaValidator, + userProfileService, + userProfileServiceAsync, + defaultDecideOptions, + disposable, + requestHandler, + } = config; + + const projectConfigManager = extractConfigManager(config.projectConfigManager); + const eventProcessor = extractEventProcessor(config.eventProcessor); + const odpManager = extractOdpManager(config.odpManager); + const vuidManager = extractVuidManager(config.vuidManager); + const errorNotifier = extractErrorNotifier(config.errorNotifier); + const logger = extractLogger(config.logger); + + const cmabClient = new DefaultCmabClient({ + requestHandler, + retryConfig: { + maxRetries: DEFAULT_CMAB_RETRIES, + backoffProvider: () => new ConstantBackoff(DEFAULT_CMAB_BACKOFF_MS), + }, + predictionEndpointTemplate: config.cmab?.predictionEndpointTemplate, + }); + + const cmabCache: CacheWithRemove<CmabCacheValue> = config.cmab?.cache ? + transformCache(config.cmab.cache, (value) => JSON.parse(value), (value) => JSON.stringify(value)) : + (() => { + const cacheSize = config.cmab?.cacheSize || DEFAULT_CMAB_CACHE_SIZE; + const cacheTtl = config.cmab?.cacheTtl || DEFAULT_CMAB_CACHE_TIMEOUT_MS; + return new InMemoryLruCache<CmabCacheValue>(cacheSize, cacheTtl); + })(); + + const cmabService = new DefaultCmabService({ + cmabClient, + cmabCache, + logger: logger?.child() + }); + + const optimizelyOptions = { + cmabService, + clientEngine: clientEngine || JAVASCRIPT_CLIENT_ENGINE, + clientVersion: clientVersion || CLIENT_VERSION, + jsonSchemaValidator, + userProfileService, + userProfileServiceAsync, + defaultDecideOptions, + disposable, + logger, + errorNotifier, + projectConfigManager, + eventProcessor, + odpManager, + vuidManager, + }; + + return new Optimizely(optimizelyOptions); +} diff --git a/lib/common_exports.ts b/lib/common_exports.ts new file mode 100644 index 000000000..801fb7728 --- /dev/null +++ b/lib/common_exports.ts @@ -0,0 +1,37 @@ +/** + * Copyright 2023-2025 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export { createStaticProjectConfigManager } from './project_config/config_manager_factory'; + +export { LogLevel } from './logging/logger'; + +export { + DEBUG, + INFO, + WARN, + ERROR, +} from './logging/logger_factory'; + +export { createLogger } from './logging/logger_factory'; +export { createErrorNotifier } from './error/error_notifier_factory'; + +export { + DECISION_SOURCES, +} from './utils/enums'; + +export { NOTIFICATION_TYPES, DECISION_NOTIFICATION_TYPES } from './notification_center/type'; + +export { OptimizelyDecideOption } from './shared_types'; diff --git a/lib/core/audience_evaluator/index.spec.ts b/lib/core/audience_evaluator/index.spec.ts new file mode 100644 index 000000000..e22654144 --- /dev/null +++ b/lib/core/audience_evaluator/index.spec.ts @@ -0,0 +1,713 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { beforeEach, afterEach, describe, it, vi, expect, afterAll } from 'vitest'; + +import AudienceEvaluator, { createAudienceEvaluator } from './index'; +import * as conditionTreeEvaluator from '../condition_tree_evaluator'; +import * as customAttributeConditionEvaluator from '../custom_attribute_condition_evaluator'; +import { AUDIENCE_EVALUATION_RESULT, EVALUATING_AUDIENCE } from '../../message/log_message'; +import { getMockLogger } from '../../tests/mock/mock_logger'; +import { Audience, OptimizelyDecideOption, OptimizelyDecision } from '../../shared_types'; +import { IOptimizelyUserContext } from '../../optimizely_user_context'; + +let mockLogger = getMockLogger(); + +const getMockUserContext = (attributes?: unknown, segments?: string[]): IOptimizelyUserContext => ({ + getAttributes: () => ({ ...(attributes || {}) }), + isQualifiedFor: segment => segments ? segments.indexOf(segment) > -1 : false, + qualifiedSegments: segments || [], + getUserId: () => 'mockUserId', + setAttribute: (key: string, value: any) => {}, + + decide: (key: string, options?: OptimizelyDecideOption[]): OptimizelyDecision => ({ + variationKey: 'mockVariationKey', + enabled: true, + variables: { mockVariable: 'mockValue' }, + ruleKey: 'mockRuleKey', + reasons: ['mockReason'], + flagKey: 'flagKey', + userContext: getMockUserContext() + }), +}) as IOptimizelyUserContext; + +const chromeUserAudience = { + id: '0', + name: 'chromeUserAudience', + conditions: [ + 'and', + { + name: 'browser_type', + value: 'chrome', + type: 'custom_attribute', + }, + ], +}; +const iphoneUserAudience = { + id: '1', + name: 'iphoneUserAudience', + conditions: [ + 'and', + { + name: 'device_model', + value: 'iphone', + type: 'custom_attribute', + }, + ], +}; +const specialConditionTypeAudience = { + id: '3', + name: 'specialConditionTypeAudience', + conditions: [ + 'and', + { + match: 'interest_level', + value: 'special', + type: 'special_condition_type', + }, + ], +}; +const conditionsPassingWithNoAttrs = [ + 'not', + { + match: 'exists', + name: 'input_value', + type: 'custom_attribute', + }, +]; +const conditionsPassingWithNoAttrsAudience = { + id: '2', + name: 'conditionsPassingWithNoAttrsAudience', + conditions: conditionsPassingWithNoAttrs, +}; + +const audiencesById: { +[id: string]: Audience; +} = { + "0": chromeUserAudience, + "1": iphoneUserAudience, + "2": conditionsPassingWithNoAttrsAudience, + "3": specialConditionTypeAudience, +}; + + +describe('lib/core/audience_evaluator', () => { + let audienceEvaluator: AudienceEvaluator; + + beforeEach(() => { + mockLogger = getMockLogger(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + describe('APIs', () => { + describe('with default condition evaluator', () => { + beforeEach(() => { + audienceEvaluator = createAudienceEvaluator({}); + }); + describe('evaluate', () => { + it('should return true if there are no audiences', () => { + expect(audienceEvaluator.evaluate([], audiencesById, getMockUserContext({}))).toBe(true); + }); + + it('should return false if there are audiences but no attributes', () => { + expect(audienceEvaluator.evaluate(['0'], audiencesById, getMockUserContext({}))).toBe(false); + }); + + it('should return true if any of the audience conditions are met', () => { + const iphoneUsers = { + device_model: 'iphone', + }; + + const chromeUsers = { + browser_type: 'chrome', + }; + + const iphoneChromeUsers = { + browser_type: 'chrome', + device_model: 'iphone', + }; + + expect(audienceEvaluator.evaluate(['0', '1'], audiencesById, getMockUserContext(iphoneUsers))).toBe(true); + expect(audienceEvaluator.evaluate(['0', '1'], audiencesById, getMockUserContext(chromeUsers))).toBe(true); + expect(audienceEvaluator.evaluate(['0', '1'], audiencesById, getMockUserContext(iphoneChromeUsers))).toBe( + true + ); + }); + + it('should return false if none of the audience conditions are met', () => { + const nexusUsers = { + device_model: 'nexus5', + }; + + const safariUsers = { + browser_type: 'safari', + }; + + const nexusSafariUsers = { + browser_type: 'safari', + device_model: 'nexus5', + }; + + expect(audienceEvaluator.evaluate(['0', '1'], audiencesById, getMockUserContext(nexusUsers))).toBe(false); + expect(audienceEvaluator.evaluate(['0', '1'], audiencesById, getMockUserContext(safariUsers))).toBe(false); + expect(audienceEvaluator.evaluate(['0', '1'], audiencesById, getMockUserContext(nexusSafariUsers))).toBe( + false + ); + }); + + it('should return true if no attributes are passed and the audience conditions evaluate to true in the absence of attributes', () => { + expect(audienceEvaluator.evaluate(['2'], audiencesById, getMockUserContext({}))).toBe(true); + }); + + describe('complex audience conditions', () => { + it('should return true if any of the audiences in an "OR" condition pass', () => { + const result = audienceEvaluator.evaluate( + ['or', '0', '1'], + audiencesById, + getMockUserContext({ browser_type: 'chrome' }) + ); + expect(result).toBe(true); + }); + + it('should return true if all of the audiences in an "AND" condition pass', () => { + const result = audienceEvaluator.evaluate( + ['and', '0', '1'], + audiencesById, + getMockUserContext({ + browser_type: 'chrome', + device_model: 'iphone', + }) + ); + expect(result).toBe(true); + }); + + it('should return true if the audience in a "NOT" condition does not pass', () => { + const result = audienceEvaluator.evaluate( + ['not', '1'], + audiencesById, + getMockUserContext({ device_model: 'android' }) + ); + expect(result).toBe(true); + }); + }); + + describe('integration with dependencies', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + afterAll(() => { + vi.resetAllMocks(); + }); + + it('returns true if conditionTreeEvaluator.evaluate returns true', () => { + vi.spyOn(conditionTreeEvaluator, 'evaluate').mockReturnValue(true); + const result = audienceEvaluator.evaluate( + ['or', '0', '1'], + audiencesById, + getMockUserContext({ browser_type: 'chrome' }) + ); + expect(result).toBe(true); + }); + + it('returns false if conditionTreeEvaluator.evaluate returns false', () => { + vi.spyOn(conditionTreeEvaluator, 'evaluate').mockReturnValue(false); + const result = audienceEvaluator.evaluate( + ['or', '0', '1'], + audiencesById, + getMockUserContext({ browser_type: 'safari' }) + ); + expect(result).toBe(false); + }); + + it('returns false if conditionTreeEvaluator.evaluate returns null', () => { + vi.spyOn(conditionTreeEvaluator, 'evaluate').mockReturnValue(null); + const result = audienceEvaluator.evaluate( + ['or', '0', '1'], + audiencesById, + getMockUserContext({ state: 'California' }) + ); + expect(result).toBe(false); + }); + + it('calls customAttributeConditionEvaluator.evaluate in the leaf evaluator for audience conditions', () => { + vi.spyOn(conditionTreeEvaluator, 'evaluate').mockImplementation((conditions: any, leafEvaluator) => { + return leafEvaluator(conditions[1]); + }); + + const mockCustomAttributeConditionEvaluator = vi.fn().mockReturnValue(false); + + vi.spyOn(customAttributeConditionEvaluator, 'getEvaluator').mockReturnValue({ + evaluate: mockCustomAttributeConditionEvaluator, + }); + + const audienceEvaluator = createAudienceEvaluator({}); + + const userAttributes = { device_model: 'android' }; + const user = getMockUserContext(userAttributes); + const result = audienceEvaluator.evaluate(['or', '1'], audiencesById, user); + + expect(mockCustomAttributeConditionEvaluator).toHaveBeenCalledTimes(1); + expect(mockCustomAttributeConditionEvaluator).toHaveBeenCalledWith( + iphoneUserAudience.conditions[1], + user, + ); + + expect(result).toBe(false); + }); + }); + + describe('Audience evaluation logging', () => { + let mockCustomAttributeConditionEvaluator: ReturnType<typeof vi.fn>; + + beforeEach(() => { + mockCustomAttributeConditionEvaluator = vi.fn(); + vi.spyOn(conditionTreeEvaluator, 'evaluate'); + vi.spyOn(customAttributeConditionEvaluator, 'getEvaluator').mockReturnValue({ + evaluate: mockCustomAttributeConditionEvaluator, + }); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('logs correctly when conditionTreeEvaluator.evaluate returns null', () => { + vi.spyOn(conditionTreeEvaluator, 'evaluate').mockImplementationOnce((conditions: any, leafEvaluator) => { + return leafEvaluator(conditions[1]); + }); + + mockCustomAttributeConditionEvaluator.mockReturnValue(null); + const userAttributes = { device_model: 5.5 }; + const user = getMockUserContext(userAttributes); + + const audienceEvaluator = createAudienceEvaluator({}, mockLogger); + + const result = audienceEvaluator.evaluate(['or', '1'], audiencesById, user); + + expect(mockCustomAttributeConditionEvaluator).toHaveBeenCalledTimes(1); + expect(mockCustomAttributeConditionEvaluator).toHaveBeenCalledWith(iphoneUserAudience.conditions[1], user); + expect(result).toBe(false); + expect(mockLogger.debug).toHaveBeenCalledTimes(2); + + expect(mockLogger.debug).toHaveBeenCalledWith( + EVALUATING_AUDIENCE, + '1', + JSON.stringify(['and', iphoneUserAudience.conditions[1]]) + ); + + expect(mockLogger.debug).toHaveBeenCalledWith(AUDIENCE_EVALUATION_RESULT, '1', 'UNKNOWN'); + }); + + it('logs correctly when conditionTreeEvaluator.evaluate returns true', () => { + vi.spyOn(conditionTreeEvaluator, 'evaluate').mockImplementationOnce((conditions: any, leafEvaluator) => { + return leafEvaluator(conditions[1]); + }); + + mockCustomAttributeConditionEvaluator.mockReturnValue(true); + + const userAttributes = { device_model: 'iphone' }; + const user = getMockUserContext(userAttributes); + + const audienceEvaluator = createAudienceEvaluator({}, mockLogger); + + const result = audienceEvaluator.evaluate(['or', '1'], audiencesById, user); + expect(mockCustomAttributeConditionEvaluator).toHaveBeenCalledTimes(1); + expect(mockCustomAttributeConditionEvaluator).toHaveBeenCalledWith(iphoneUserAudience.conditions[1], user); + expect(result).toBe(true); + expect(mockLogger.debug).toHaveBeenCalledTimes(2) + expect(mockLogger.debug).toHaveBeenCalledWith( + EVALUATING_AUDIENCE, + '1', + JSON.stringify(['and', iphoneUserAudience.conditions[1]]) + ); + + expect(mockLogger.debug).toHaveBeenCalledWith(AUDIENCE_EVALUATION_RESULT, '1', 'TRUE'); + }); + + it('logs correctly when conditionTreeEvaluator.evaluate returns false', () => { + vi.spyOn(conditionTreeEvaluator, 'evaluate').mockImplementationOnce((conditions: any, leafEvaluator) => { + return leafEvaluator(conditions[1]); + }); + + mockCustomAttributeConditionEvaluator.mockReturnValue(false); + + const userAttributes = { device_model: 'android' }; + const user = getMockUserContext(userAttributes); + + const audienceEvaluator = createAudienceEvaluator({}, mockLogger); + + const result = audienceEvaluator.evaluate(['or', '1'], audiencesById, user); + expect(mockCustomAttributeConditionEvaluator).toHaveBeenCalledTimes(1); + expect(mockCustomAttributeConditionEvaluator).toHaveBeenCalledWith(iphoneUserAudience.conditions[1], user); + expect(result).toBe(false); + expect(mockLogger.debug).toHaveBeenCalledTimes(2) + expect(mockLogger.debug).toHaveBeenCalledWith( + EVALUATING_AUDIENCE, + '1', + JSON.stringify(['and', iphoneUserAudience.conditions[1]]) + ); + + expect(mockLogger.debug).toHaveBeenCalledWith(AUDIENCE_EVALUATION_RESULT, '1', 'FALSE'); + }); + }); + }); + }); + + describe('with additional custom condition evaluator', () => { + describe('when passing a valid additional evaluator', () => { + beforeEach(() => { + const mockEnvironment = { + special: true, + }; + audienceEvaluator = createAudienceEvaluator({ + special_condition_type: { + evaluate: (condition: any, user: any) => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const result = mockEnvironment[condition.value] && user.getAttributes()[condition.match] > 0; + return result; + }, + }, + }); + }); + + it('should evaluate an audience properly using the custom condition evaluator', () => { + expect(audienceEvaluator.evaluate(['3'], audiencesById, getMockUserContext({ interest_level: 0 }))).toBe( + false + ); + expect(audienceEvaluator.evaluate(['3'], audiencesById, getMockUserContext({ interest_level: 1 }))).toBe( + true + ); + }); + }); + + describe('when passing an invalid additional evaluator', () => { + beforeEach(() => { + audienceEvaluator = createAudienceEvaluator({ + custom_attribute: { + evaluate: () => { + return false; + }, + }, + }); + }); + + it('should not be able to overwrite built in `custom_attribute` evaluator', () => { + expect( + audienceEvaluator.evaluate( + ['0'], + audiencesById, + getMockUserContext({ + browser_type: 'chrome', + }) + ) + ).toBe(true); + }); + }); + }); + + describe('with odp segment evaluator', () => { + describe('Single ODP Audience', () => { + const singleAudience = { + id: '0', + name: 'singleAudience', + conditions: [ + 'and', + { + value: 'odp-segment-1', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + }; + const audiencesById = { + 0: singleAudience, + }; + const audience = new AudienceEvaluator({}); + + it('should evaluate to true if segment is found', () => { + expect(audience.evaluate(['or', '0'], audiencesById, getMockUserContext({}, ['odp-segment-1']))).toBe(true); + }); + + it('should evaluate to false if segment is not found', () => { + expect(audience.evaluate(['or', '0'], audiencesById, getMockUserContext({}, ['odp-segment-2']))).toBe(false); + }); + + it('should evaluate to false if not segments are provided', () => { + expect(audience.evaluate(['or', '0'], audiencesById, getMockUserContext({}))).toBe(false); + }); + }); + + describe('Multiple ODP conditions in one Audience', () => { + const singleAudience = { + id: '0', + name: 'singleAudience', + conditions: [ + 'and', + { + value: 'odp-segment-1', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + { + value: 'odp-segment-2', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + [ + 'or', + { + value: 'odp-segment-3', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + { + value: 'odp-segment-4', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + ], + }; + const audiencesById = { + 0: singleAudience, + }; + const audience = new AudienceEvaluator({}); + + it('should evaluate correctly based on the given segments', () => { + expect( + audience.evaluate( + ['or', '0'], + audiencesById, + getMockUserContext({}, ['odp-segment-1', 'odp-segment-2', 'odp-segment-3']) + ) + ).toBe(true); + expect( + audience.evaluate( + ['or', '0'], + audiencesById, + getMockUserContext({}, ['odp-segment-1', 'odp-segment-2', 'odp-segment-4']) + ) + ).toBe(true); + expect( + audience.evaluate( + ['or', '0'], + audiencesById, + getMockUserContext({}, ['odp-segment-1', 'odp-segment-2', 'odp-segment-3', 'odp-segment-4']) + ) + ).toBe(true); + expect( + audience.evaluate( + ['or', '0'], + audiencesById, + getMockUserContext({}, ['odp-segment-1', 'odp-segment-3', 'odp-segment-4']) + ) + ).toBe(false); + expect( + audience.evaluate( + ['or', '0'], + audiencesById, + getMockUserContext({}, ['odp-segment-2', 'odp-segment-3', 'odp-segment-4']) + ) + ).toBe(false); + }); + }); + + describe('Multiple ODP conditions in multiple Audience', () => { + const audience1And2 = { + id: '0', + name: 'audience1And2', + conditions: [ + 'and', + { + value: 'odp-segment-1', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + { + value: 'odp-segment-2', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + }; + + const audience3And4 = { + id: '1', + name: 'audience3And4', + conditions: [ + 'and', + { + value: 'odp-segment-3', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + { + value: 'odp-segment-4', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + }; + + const audience5And6 = { + id: '2', + name: 'audience5And6', + conditions: [ + 'or', + { + value: 'odp-segment-5', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + { + value: 'odp-segment-6', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + }; + const audiencesById = { + 0: audience1And2, + 1: audience3And4, + 2: audience5And6, + }; + const audience = new AudienceEvaluator({}); + + it('should evaluate correctly based on the given segments', () => { + expect( + audience.evaluate( + ['or', '0', '1', '2'], + audiencesById, + getMockUserContext({}, ['odp-segment-1', 'odp-segment-2']) + ) + ).toBe(true); + expect( + audience.evaluate( + ['and', '0', '1', '2'], + audiencesById, + getMockUserContext({}, ['odp-segment-1', 'odp-segment-2']) + ) + ).toBe(false); + expect( + audience.evaluate( + ['and', '0', '1', '2'], + audiencesById, + getMockUserContext({}, [ + 'odp-segment-1', + 'odp-segment-2', + 'odp-segment-3', + 'odp-segment-4', + 'odp-segment-6', + ]) + ) + ).toBe(true); + expect( + audience.evaluate( + ['and', '0', '1', ['not', '2']], + audiencesById, + getMockUserContext({}, ['odp-segment-1', 'odp-segment-2', 'odp-segment-3', 'odp-segment-4']) + ) + ).toBe(true); + }); + }); + }); + + describe('with multiple types of evaluators', () => { + const audience1And2 = { + id: '0', + name: 'audience1And2', + conditions: [ + 'and', + { + value: 'odp-segment-1', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + { + value: 'odp-segment-2', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + }; + const audience3Or4 = { + id: '', + name: 'audience3And4', + conditions: [ + 'or', + { + value: 'odp-segment-3', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + { + value: 'odp-segment-4', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + }; + + const audiencesById = { + 0: audience1And2, + 1: audience3Or4, + 2: chromeUserAudience, + }; + + const audience = new AudienceEvaluator({}); + + it('should evaluate correctly based on the given segments', () => { + expect( + audience.evaluate( + ['and', '0', '1', '2'], + audiencesById, + getMockUserContext({ browser_type: 'not_chrome' }, ['odp-segment-1', 'odp-segment-2', 'odp-segment-4']) + ) + ).toBe(false); + expect( + audience.evaluate( + ['and', '0', '1', '2'], + audiencesById, + getMockUserContext({ browser_type: 'chrome' }, ['odp-segment-1', 'odp-segment-2', 'odp-segment-4']) + ) + ).toBe(true); + }); + }); + }); +}); diff --git a/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js b/lib/core/audience_evaluator/index.tests.js similarity index 84% rename from packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js rename to lib/core/audience_evaluator/index.tests.js index 6fb545f10..1dc5efd30 100644 --- a/packages/optimizely-sdk/lib/core/audience_evaluator/index.tests.js +++ b/lib/core/audience_evaluator/index.tests.js @@ -16,14 +16,19 @@ import sinon from 'sinon'; import { assert } from 'chai'; import { sprintf } from '../../utils/fns'; -import { getLogger } from '../../modules/logging'; import AudienceEvaluator, { createAudienceEvaluator } from './index'; import * as conditionTreeEvaluator from '../condition_tree_evaluator'; import * as customAttributeConditionEvaluator from '../custom_attribute_condition_evaluator'; +import { AUDIENCE_EVALUATION_RESULT, EVALUATING_AUDIENCE } from 'log_message'; var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); -var mockLogger = getLogger(); +var mockLogger = { + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, +} var getMockUserContext = (attributes, segments) => ({ getAttributes: () => ({ ... (attributes || {})}), @@ -82,11 +87,17 @@ describe('lib/core/audience_evaluator', function() { var audienceEvaluator; beforeEach(function() { - sinon.stub(mockLogger, 'log'); + sinon.stub(mockLogger, 'info'); + sinon.stub(mockLogger, 'debug'); + sinon.stub(mockLogger, 'warn'); + sinon.stub(mockLogger, 'error'); }); afterEach(function() { - mockLogger.log.restore(); + mockLogger.info.restore(); + mockLogger.debug.restore(); + mockLogger.warn.restore(); + mockLogger.error.restore(); }); describe('APIs', function() { @@ -170,7 +181,6 @@ describe('lib/core/audience_evaluator', function() { beforeEach(function() { sandbox.stub(conditionTreeEvaluator, 'evaluate'); - sandbox.stub(customAttributeConditionEvaluator, 'evaluate'); }); afterEach(function() { @@ -199,26 +209,40 @@ describe('lib/core/audience_evaluator', function() { conditionTreeEvaluator.evaluate.callsFake(function(conditions, leafEvaluator) { return leafEvaluator(conditions[1]); }); - customAttributeConditionEvaluator.evaluate.returns(false); + + const mockCustomAttributeConditionEvaluator = sinon.stub().returns(false); + + sinon.stub(customAttributeConditionEvaluator, 'getEvaluator').returns({ + evaluate: mockCustomAttributeConditionEvaluator, + }); + + const audienceEvaluator = createAudienceEvaluator(); + var userAttributes = { device_model: 'android' }; var user = getMockUserContext(userAttributes); var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, user); - sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); + sinon.assert.calledOnce(mockCustomAttributeConditionEvaluator); sinon.assert.calledWithExactly( - customAttributeConditionEvaluator.evaluate, + mockCustomAttributeConditionEvaluator, iphoneUserAudience.conditions[1], user, ); assert.isFalse(result); + + customAttributeConditionEvaluator.getEvaluator.restore(); }); }); describe('Audience evaluation logging', function() { var sandbox = sinon.sandbox.create(); + var mockCustomAttributeConditionEvaluator; beforeEach(function() { + mockCustomAttributeConditionEvaluator = sinon.stub(); sandbox.stub(conditionTreeEvaluator, 'evaluate'); - sandbox.stub(customAttributeConditionEvaluator, 'evaluate'); + sandbox.stub(customAttributeConditionEvaluator, 'getEvaluator').returns({ + evaluate: mockCustomAttributeConditionEvaluator, + }); }); afterEach(function() { @@ -229,69 +253,110 @@ describe('lib/core/audience_evaluator', function() { conditionTreeEvaluator.evaluate.callsFake(function(conditions, leafEvaluator) { return leafEvaluator(conditions[1]); }); - customAttributeConditionEvaluator.evaluate.returns(null); + + mockCustomAttributeConditionEvaluator.returns(null); var userAttributes = { device_model: 5.5 }; var user = getMockUserContext(userAttributes); + + const audienceEvaluator = createAudienceEvaluator({}, mockLogger); + var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, user); - sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); + + sinon.assert.calledOnce(mockCustomAttributeConditionEvaluator); sinon.assert.calledWithExactly( - customAttributeConditionEvaluator.evaluate, + mockCustomAttributeConditionEvaluator, iphoneUserAudience.conditions[1], user ); assert.isFalse(result); - assert.strictEqual(2, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'AUDIENCE_EVALUATOR: Starting to evaluate audience "1" with conditions: ["and",{"name":"device_model","value":"iphone","type":"custom_attribute"}].' - ); - assert.strictEqual(buildLogMessageFromArgs(mockLogger.log.args[1]), 'AUDIENCE_EVALUATOR: Audience "1" evaluated to UNKNOWN.'); + assert.strictEqual(2, mockLogger.debug.callCount); + + sinon.assert.calledWithExactly( + mockLogger.debug, + EVALUATING_AUDIENCE, + '1', + JSON.stringify(['and', iphoneUserAudience.conditions[1]]) + ) + + sinon.assert.calledWithExactly( + mockLogger.debug, + AUDIENCE_EVALUATION_RESULT, + '1', + 'UNKNOWN' + ) }); it('logs correctly when conditionTreeEvaluator.evaluate returns true', function() { conditionTreeEvaluator.evaluate.callsFake(function(conditions, leafEvaluator) { return leafEvaluator(conditions[1]); }); - customAttributeConditionEvaluator.evaluate.returns(true); + + mockCustomAttributeConditionEvaluator.returns(true); + var userAttributes = { device_model: 'iphone' }; var user = getMockUserContext(userAttributes); + + const audienceEvaluator = createAudienceEvaluator({}, mockLogger); + var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, user); - sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); + sinon.assert.calledOnce(mockCustomAttributeConditionEvaluator); sinon.assert.calledWithExactly( - customAttributeConditionEvaluator.evaluate, + mockCustomAttributeConditionEvaluator, iphoneUserAudience.conditions[1], user, ); assert.isTrue(result); - assert.strictEqual(2, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'AUDIENCE_EVALUATOR: Starting to evaluate audience "1" with conditions: ["and",{"name":"device_model","value":"iphone","type":"custom_attribute"}].' - ); - assert.strictEqual(buildLogMessageFromArgs(mockLogger.log.args[1]), 'AUDIENCE_EVALUATOR: Audience "1" evaluated to TRUE.'); + assert.strictEqual(2, mockLogger.debug.callCount); + sinon.assert.calledWithExactly( + mockLogger.debug, + EVALUATING_AUDIENCE, + '1', + JSON.stringify(['and', iphoneUserAudience.conditions[1]]) + ) + + sinon.assert.calledWithExactly( + mockLogger.debug, + AUDIENCE_EVALUATION_RESULT, + '1', + 'TRUE' + ) }); it('logs correctly when conditionTreeEvaluator.evaluate returns false', function() { conditionTreeEvaluator.evaluate.callsFake(function(conditions, leafEvaluator) { return leafEvaluator(conditions[1]); }); - customAttributeConditionEvaluator.evaluate.returns(false); + + mockCustomAttributeConditionEvaluator.returns(false); + var userAttributes = { device_model: 'android' }; var user = getMockUserContext(userAttributes); + + const audienceEvaluator = createAudienceEvaluator({}, mockLogger); + var result = audienceEvaluator.evaluate(['or', '1'], audiencesById, user); - sinon.assert.calledOnce(customAttributeConditionEvaluator.evaluate); + sinon.assert.calledOnce(mockCustomAttributeConditionEvaluator); sinon.assert.calledWithExactly( - customAttributeConditionEvaluator.evaluate, + mockCustomAttributeConditionEvaluator, iphoneUserAudience.conditions[1], user, ); assert.isFalse(result); - assert.strictEqual(2, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'AUDIENCE_EVALUATOR: Starting to evaluate audience "1" with conditions: ["and",{"name":"device_model","value":"iphone","type":"custom_attribute"}].' - ); - assert.strictEqual(buildLogMessageFromArgs(mockLogger.log.args[1]), 'AUDIENCE_EVALUATOR: Audience "1" evaluated to FALSE.'); + assert.strictEqual(2, mockLogger.debug.callCount); + + sinon.assert.calledWithExactly( + mockLogger.debug, + EVALUATING_AUDIENCE, + '1', + JSON.stringify(['and', iphoneUserAudience.conditions[1]]) + ) + + sinon.assert.calledWithExactly( + mockLogger.debug, + AUDIENCE_EVALUATION_RESULT, + '1', + 'FALSE' + ) }); }); }); diff --git a/packages/optimizely-sdk/lib/core/audience_evaluator/index.ts b/lib/core/audience_evaluator/index.ts similarity index 81% rename from packages/optimizely-sdk/lib/core/audience_evaluator/index.ts rename to lib/core/audience_evaluator/index.ts index 550694610..e2b3bce0a 100644 --- a/packages/optimizely-sdk/lib/core/audience_evaluator/index.ts +++ b/lib/core/audience_evaluator/index.ts @@ -13,23 +13,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getLogger } from '../../modules/logging'; - -import fns from '../../utils/fns'; -import { - LOG_LEVEL, - LOG_MESSAGES, - ERROR_MESSAGES, -} from '../../utils/enums'; import * as conditionTreeEvaluator from '../condition_tree_evaluator'; import * as customAttributeConditionEvaluator from '../custom_attribute_condition_evaluator'; import * as odpSegmentsConditionEvaluator from './odp_segment_condition_evaluator'; import { Audience, Condition, OptimizelyUserContext } from '../../shared_types'; - -const logger = getLogger(); -const MODULE_NAME = 'AUDIENCE_EVALUATOR'; +import { CONDITION_EVALUATOR_ERROR, UNKNOWN_CONDITION_TYPE } from 'error_message'; +import { AUDIENCE_EVALUATION_RESULT, EVALUATING_AUDIENCE} from 'log_message'; +import { LoggerFacade } from '../../logging/logger'; export class AudienceEvaluator { + private logger?: LoggerFacade; + private typeToEvaluatorMap: { [key: string]: { [key: string]: (condition: Condition, user: OptimizelyUserContext) => boolean | null @@ -43,11 +37,13 @@ export class AudienceEvaluator { * Optimizely evaluators cannot be overridden. * @constructor */ - constructor(UNSTABLE_conditionEvaluators: unknown) { - this.typeToEvaluatorMap = fns.assign({}, UNSTABLE_conditionEvaluators, { - custom_attribute: customAttributeConditionEvaluator, - third_party_dimension: odpSegmentsConditionEvaluator, - }); + constructor(UNSTABLE_conditionEvaluators: unknown, logger?: LoggerFacade) { + this.logger = logger; + this.typeToEvaluatorMap = { + ...UNSTABLE_conditionEvaluators as any, + custom_attribute: customAttributeConditionEvaluator.getEvaluator(this.logger), + third_party_dimension: odpSegmentsConditionEvaluator.getEvaluator(this.logger), + }; } /** @@ -76,16 +72,15 @@ export class AudienceEvaluator { const evaluateAudience = (audienceId: string) => { const audience = audiencesById[audienceId]; if (audience) { - logger.log( - LOG_LEVEL.DEBUG, - LOG_MESSAGES.EVALUATING_AUDIENCE, MODULE_NAME, audienceId, JSON.stringify(audience.conditions) + this.logger?.debug( + EVALUATING_AUDIENCE, audienceId, JSON.stringify(audience.conditions) ); const result = conditionTreeEvaluator.evaluate( audience.conditions as unknown[] , this.evaluateConditionWithUserAttributes.bind(this, user) ); const resultText = result === null ? 'UNKNOWN' : result.toString().toUpperCase(); - logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT, MODULE_NAME, audienceId, resultText); + this.logger?.debug(AUDIENCE_EVALUATION_RESULT, audienceId, resultText); return result; } return null; @@ -104,15 +99,14 @@ export class AudienceEvaluator { evaluateConditionWithUserAttributes(user: OptimizelyUserContext, condition: Condition): boolean | null { const evaluator = this.typeToEvaluatorMap[condition.type]; if (!evaluator) { - logger.log(LOG_LEVEL.WARNING, LOG_MESSAGES.UNKNOWN_CONDITION_TYPE, MODULE_NAME, JSON.stringify(condition)); + this.logger?.warn(UNKNOWN_CONDITION_TYPE, JSON.stringify(condition)); return null; } try { return evaluator.evaluate(condition, user); } catch (err: any) { - logger.log( - LOG_LEVEL.ERROR, - ERROR_MESSAGES.CONDITION_EVALUATOR_ERROR, MODULE_NAME, condition.type, err.message + this.logger?.error( + CONDITION_EVALUATOR_ERROR, condition.type, err.message ); } @@ -122,6 +116,6 @@ export class AudienceEvaluator { export default AudienceEvaluator; -export const createAudienceEvaluator = function(UNSTABLE_conditionEvaluators: unknown): AudienceEvaluator { - return new AudienceEvaluator(UNSTABLE_conditionEvaluators); +export const createAudienceEvaluator = function(UNSTABLE_conditionEvaluators: unknown, logger?: LoggerFacade): AudienceEvaluator { + return new AudienceEvaluator(UNSTABLE_conditionEvaluators, logger); }; diff --git a/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.spec.ts b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.spec.ts new file mode 100644 index 000000000..f42d07cb4 --- /dev/null +++ b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.spec.ts @@ -0,0 +1,74 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { afterEach, describe, it, vi, expect } from 'vitest'; +import * as odpSegmentEvalutor from '.'; +import { UNKNOWN_MATCH_TYPE } from '../../../message/error_message'; +import { IOptimizelyUserContext } from '../../../optimizely_user_context'; +import { OptimizelyDecideOption, OptimizelyDecision } from '../../../shared_types'; +import { getMockLogger } from '../../../tests/mock/mock_logger'; + +const odpSegment1Condition = { + "value": "odp-segment-1", + "type": "third_party_dimension", + "name": "odp.audiences", + "match": "qualified" +}; + +const getMockUserContext = (attributes?: unknown, segments?: string[]): IOptimizelyUserContext => ({ + getAttributes: () => ({ ...(attributes || {}) }), + isQualifiedFor: segment => segments ? segments.indexOf(segment) > -1 : false, + qualifiedSegments: segments || [], + getUserId: () => 'mockUserId', + setAttribute: (key: string, value: any) => {}, + + decide: (key: string, options?: OptimizelyDecideOption[]): OptimizelyDecision => ({ + variationKey: 'mockVariationKey', + enabled: true, + variables: { mockVariable: 'mockValue' }, + ruleKey: 'mockRuleKey', + reasons: ['mockReason'], + flagKey: 'flagKey', + userContext: getMockUserContext() + }), +}) as IOptimizelyUserContext; + + +describe('lib/core/audience_evaluator/odp_segment_condition_evaluator', function() { + const mockLogger = getMockLogger(); + const { evaluate } = odpSegmentEvalutor.getEvaluator(mockLogger); + + afterEach(function() { + vi.restoreAllMocks(); + }); + + it('should return true when segment qualifies and known match type is provided', () => { + expect(evaluate(odpSegment1Condition, getMockUserContext({}, ['odp-segment-1']))).toBe(true); + }); + + it('should return false when segment does not qualify and known match type is provided', () => { + expect(evaluate(odpSegment1Condition, getMockUserContext({}, ['odp-segment-2']))).toBe(false); + }) + + it('should return null when segment qualifies but unknown match type is provided', () => { + const invalidOdpMatchCondition = { + ... odpSegment1Condition, + "match": 'unknown', + }; + expect(evaluate(invalidOdpMatchCondition, getMockUserContext({}, ['odp-segment-1']))).toBeNull(); + expect(mockLogger.warn).toHaveBeenCalledTimes(1); + expect(mockLogger.warn).toHaveBeenCalledWith(UNKNOWN_MATCH_TYPE, JSON.stringify(invalidOdpMatchCondition)); + }); +}); diff --git a/packages/optimizely-sdk/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.tests.js b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.tests.js similarity index 69% rename from packages/optimizely-sdk/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.tests.js rename to lib/core/audience_evaluator/odp_segment_condition_evaluator/index.tests.js index 503017545..cc9218887 100644 --- a/packages/optimizely-sdk/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.tests.js +++ b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.tests.js @@ -17,12 +17,9 @@ import sinon from 'sinon'; import { assert } from 'chai'; import { sprintf } from '../../../utils/fns'; -import { - LOG_LEVEL, - LOG_MESSAGES, -} from '../../../utils/enums'; -import * as logging from '../../../modules/logging'; +import { LOG_LEVEL } from '../../../utils/enums'; import * as odpSegmentEvalutor from './'; +import { UNKNOWN_MATCH_TYPE } from 'error_message'; var odpSegment1Condition = { "value": "odp-segment-1", @@ -36,27 +33,34 @@ var getMockUserContext = (attributes, segments) => ({ isQualifiedFor: segment => segments.indexOf(segment) > -1 }); +var createLogger = () => ({ + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + child: () => createLogger(), +}) + describe('lib/core/audience_evaluator/odp_segment_condition_evaluator', function() { - var stubLogHandler; + const mockLogger = createLogger(); + const { evaluate } = odpSegmentEvalutor.getEvaluator(mockLogger); beforeEach(function() { - stubLogHandler = { - log: sinon.stub(), - }; - logging.setLogLevel('notset'); - logging.setLogHandler(stubLogHandler); + sinon.stub(mockLogger, 'warn'); + sinon.stub(mockLogger, 'error'); }); afterEach(function() { - logging.resetLogger(); + mockLogger.warn.restore(); + mockLogger.error.restore(); }); it('should return true when segment qualifies and known match type is provided', () => { - assert.isTrue(odpSegmentEvalutor.evaluate(odpSegment1Condition, getMockUserContext({}, ['odp-segment-1']))); + assert.isTrue(evaluate(odpSegment1Condition, getMockUserContext({}, ['odp-segment-1']))); }); it('should return false when segment does not qualify and known match type is provided', () => { - assert.isFalse(odpSegmentEvalutor.evaluate(odpSegment1Condition, getMockUserContext({}, ['odp-segment-2']))); + assert.isFalse(evaluate(odpSegment1Condition, getMockUserContext({}, ['odp-segment-2']))); }) it('should return null when segment qualifies but unknown match type is provided', () => { @@ -64,10 +68,9 @@ describe('lib/core/audience_evaluator/odp_segment_condition_evaluator', function ... odpSegment1Condition, "match": 'unknown', }; - assert.isNull(odpSegmentEvalutor.evaluate(invalidOdpMatchCondition, getMockUserContext({}, ['odp-segment-1']))); - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.UNKNOWN_MATCH_TYPE, 'ODP_SEGMENT_CONDITION_EVALUATOR', JSON.stringify(invalidOdpMatchCondition))); + assert.isNull(evaluate(invalidOdpMatchCondition, getMockUserContext({}, ['odp-segment-1']))); + sinon.assert.calledOnce(mockLogger.warn); + assert.strictEqual(mockLogger.warn.args[0][0], UNKNOWN_MATCH_TYPE); + assert.strictEqual(mockLogger.warn.args[0][1], JSON.stringify(invalidOdpMatchCondition)); }); }); diff --git a/packages/optimizely-sdk/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts similarity index 81% rename from packages/optimizely-sdk/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts rename to lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts index 3098ae2b0..7380c9269 100644 --- a/packages/optimizely-sdk/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts +++ b/lib/core/audience_evaluator/odp_segment_condition_evaluator/index.ts @@ -13,15 +13,10 @@ * See the License for the specific language governing permissions and * * limitations under the License. * ***************************************************************************/ -import { getLogger } from '../../../modules/logging'; +import { UNKNOWN_MATCH_TYPE } from 'error_message'; +import { LoggerFacade } from '../../../logging/logger'; import { Condition, OptimizelyUserContext } from '../../../shared_types'; -import { LOG_MESSAGES } from '../../../utils/enums'; - -const MODULE_NAME = 'ODP_SEGMENT_CONDITION_EVALUATOR'; - -const logger = getLogger(); - const QUALIFIED_MATCH_TYPE = 'qualified'; const MATCH_TYPES = [ @@ -29,10 +24,19 @@ const MATCH_TYPES = [ ]; type ConditionEvaluator = (condition: Condition, user: OptimizelyUserContext) => boolean | null; +type Evaluator = { evaluate: (condition: Condition, user: OptimizelyUserContext) => boolean | null; } const EVALUATORS_BY_MATCH_TYPE: { [conditionType: string]: ConditionEvaluator | undefined } = {}; EVALUATORS_BY_MATCH_TYPE[QUALIFIED_MATCH_TYPE] = qualifiedEvaluator; +export const getEvaluator = (logger?: LoggerFacade): Evaluator => { + return { + evaluate(condition: Condition, user: OptimizelyUserContext): boolean | null { + return evaluate(condition, user, logger); + } + }; +} + /** * Given a custom attribute audience condition and user attributes, evaluate the * condition against the attributes. @@ -42,10 +46,10 @@ EVALUATORS_BY_MATCH_TYPE[QUALIFIED_MATCH_TYPE] = qualifiedEvaluator; * null if the given user attributes and condition can't be evaluated * TODO: Change to accept and object with named properties */ -export function evaluate(condition: Condition, user: OptimizelyUserContext): boolean | null { +function evaluate(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { const conditionMatch = condition.match; if (typeof conditionMatch !== 'undefined' && MATCH_TYPES.indexOf(conditionMatch) === -1) { - logger.warn(LOG_MESSAGES.UNKNOWN_MATCH_TYPE, MODULE_NAME, JSON.stringify(condition)); + logger?.warn(UNKNOWN_MATCH_TYPE, JSON.stringify(condition)); return null; } diff --git a/lib/core/bucketer/bucket_value_generator.spec.ts b/lib/core/bucketer/bucket_value_generator.spec.ts new file mode 100644 index 000000000..e68db6348 --- /dev/null +++ b/lib/core/bucketer/bucket_value_generator.spec.ts @@ -0,0 +1,49 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { expect, describe, it } from 'vitest'; +import { sprintf } from '../../utils/fns'; +import { generateBucketValue } from './bucket_value_generator'; +import { OptimizelyError } from '../../error/optimizly_error'; +import { INVALID_BUCKETING_ID } from 'error_message'; + +describe('generateBucketValue', () => { + it('should return a bucket value for different inputs', () => { + const experimentId = 1886780721; + const bucketingKey1 = sprintf('%s%s', 'ppid1', experimentId); + const bucketingKey2 = sprintf('%s%s', 'ppid2', experimentId); + const bucketingKey3 = sprintf('%s%s', 'ppid2', 1886780722); + const bucketingKey4 = sprintf('%s%s', 'ppid3', experimentId); + + expect(generateBucketValue(bucketingKey1)).toBe(5254); + expect(generateBucketValue(bucketingKey2)).toBe(4299); + expect(generateBucketValue(bucketingKey3)).toBe(2434); + expect(generateBucketValue(bucketingKey4)).toBe(5439); + }); + + it('should return an error if it cannot generate the hash value', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(() => generateBucketValue(null)).toThrow(OptimizelyError); + try { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + generateBucketValue(null); + } catch (err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_BUCKETING_ID); + } + }); +}); diff --git a/lib/core/bucketer/bucket_value_generator.ts b/lib/core/bucketer/bucket_value_generator.ts new file mode 100644 index 000000000..c5f85303b --- /dev/null +++ b/lib/core/bucketer/bucket_value_generator.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import murmurhash from 'murmurhash'; +import { INVALID_BUCKETING_ID } from 'error_message'; +import { OptimizelyError } from '../../error/optimizly_error'; + +const HASH_SEED = 1; +const MAX_HASH_VALUE = Math.pow(2, 32); +const MAX_TRAFFIC_VALUE = 10000; + +/** + * Helper function to generate bucket value in half-closed interval [0, MAX_TRAFFIC_VALUE) + * @param {string} bucketingKey String value for bucketing + * @return {number} The generated bucket value + * @throws If bucketing value is not a valid string + */ +export const generateBucketValue = function(bucketingKey: string): number { + try { + // NOTE: the mmh library already does cast the hash value as an unsigned 32bit int + // https://github.com/perezd/node-murmurhash/blob/master/murmurhash.js#L115 + const hashValue = murmurhash.v3(bucketingKey, HASH_SEED); + const ratio = hashValue / MAX_HASH_VALUE; + return Math.floor(ratio * MAX_TRAFFIC_VALUE); + } catch (ex) { + throw new OptimizelyError(INVALID_BUCKETING_ID, bucketingKey, ex.message); + } +}; diff --git a/lib/core/bucketer/index.spec.ts b/lib/core/bucketer/index.spec.ts new file mode 100644 index 000000000..942295356 --- /dev/null +++ b/lib/core/bucketer/index.spec.ts @@ -0,0 +1,402 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'; +import { sprintf } from '../../utils/fns'; +import projectConfig, { ProjectConfig } from '../../project_config/project_config'; +import { getTestProjectConfig } from '../../tests/test_data'; +import { INVALID_BUCKETING_ID, INVALID_GROUP_ID } from 'error_message'; +import * as bucketer from './'; +import * as bucketValueGenerator from './bucket_value_generator'; + +import { + USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, + USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, + USER_NOT_IN_ANY_EXPERIMENT, + USER_ASSIGNED_TO_EXPERIMENT_BUCKET, +} from '.'; +import { BucketerParams } from '../../shared_types'; +import { OptimizelyError } from '../../error/optimizly_error'; +import { getMockLogger } from '../../tests/mock/mock_logger'; +import { LoggerFacade } from '../../logging/logger'; + +const testData = getTestProjectConfig(); + +function cloneDeep<T>(value: T): T { + if (value === null || typeof value !== 'object') { + return value; + } + + if (Array.isArray(value)) { + return (value.map(cloneDeep) as unknown) as T; + } + + const copy: Record<string, unknown> = {}; + + for (const key in value) { + if (Object.prototype.hasOwnProperty.call(value, key)) { + copy[key] = cloneDeep((value as Record<string, unknown>)[key]); + } + } + + return copy as T; +} + +const setLogSpy = (logger: LoggerFacade) => { + vi.spyOn(logger, 'info'); + vi.spyOn(logger, 'debug'); + vi.spyOn(logger, 'warn'); + vi.spyOn(logger, 'error'); +}; + +describe('excluding groups', () => { + let configObj; + const mockLogger = getMockLogger(); + let bucketerParams: BucketerParams; + + beforeEach(() => { + setLogSpy(mockLogger); + configObj = projectConfig.createProjectConfig(cloneDeep(testData)); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + bucketerParams = { + experimentId: configObj.experiments[0].id, + experimentKey: configObj.experiments[0].key, + trafficAllocationConfig: configObj.experiments[0].trafficAllocation, + variationIdMap: configObj.variationIdMap, + experimentIdMap: configObj.experimentIdMap, + groupIdMap: configObj.groupIdMap, + logger: mockLogger, + validateEntity: true, + }; + + vi.spyOn(bucketValueGenerator, 'generateBucketValue') + .mockReturnValueOnce(50) + .mockReturnValueOnce(50000); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return decision response with correct variation ID when provided bucket value', async () => { + const bucketerParamsTest1 = cloneDeep(bucketerParams); + bucketerParamsTest1.userId = 'ppid1'; + const decisionResponse = bucketer.bucket(bucketerParamsTest1); + + expect(decisionResponse.result).toBe('111128'); + expect(mockLogger.debug).toHaveBeenCalledWith(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, expect.any(Number), 'ppid1'); + + const bucketerParamsTest2 = cloneDeep(bucketerParams); + bucketerParamsTest2.userId = 'ppid2'; + const decisionResponse2 = bucketer.bucket(bucketerParamsTest2); + + expect(decisionResponse2.result).toBeNull(); + expect(mockLogger.debug).toHaveBeenCalledWith(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, expect.any(Number), 'ppid2'); + }); +}); + +describe('including groups: random', () => { + let configObj: ProjectConfig; + const mockLogger = getMockLogger(); + let bucketerParams: BucketerParams; + + beforeEach(() => { + setLogSpy(mockLogger); + configObj = projectConfig.createProjectConfig(cloneDeep(testData)); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + bucketerParams = { + experimentId: configObj.experiments[4].id, + experimentKey: configObj.experiments[4].key, + trafficAllocationConfig: configObj.experiments[4].trafficAllocation, + variationIdMap: configObj.variationIdMap, + experimentIdMap: configObj.experimentIdMap, + groupIdMap: configObj.groupIdMap, + logger: mockLogger, + userId: 'testUser', + validateEntity: true, + }; + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return decision response with the proper variation for a user in a grouped experiment', () => { + vi.spyOn(bucketValueGenerator, 'generateBucketValue') + .mockReturnValueOnce(50) + .mockReturnValueOnce(50); + + const decisionResponse = bucketer.bucket(bucketerParams); + + expect(decisionResponse.result).toBe('551'); + expect(mockLogger.info).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledTimes(2); + expect(mockLogger.debug).toHaveBeenCalledWith(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, expect.any(Number), 'testUser'); + expect(mockLogger.info).toHaveBeenCalledWith( + USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, + 'testUser', + 'groupExperiment1', + '666' + ); + }); + + it('should return decision response with variation null when a user is bucketed into a different grouped experiment than the one speicfied', () => { + vi.spyOn(bucketValueGenerator, 'generateBucketValue').mockReturnValue(5000); + + const decisionResponse = bucketer.bucket(bucketerParams); + + expect(decisionResponse.result).toBeNull(); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.info).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, expect.any(Number), 'testUser'); + expect(mockLogger.info).toHaveBeenCalledWith( + USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, + 'testUser', + 'groupExperiment1', + '666' + ); + }); + + it('should return decision response with variation null when a user is not bucketed into any experiments in the random group', () => { + vi.spyOn(bucketValueGenerator, 'generateBucketValue').mockReturnValue(50000); + + const decisionResponse = bucketer.bucket(bucketerParams); + + expect(decisionResponse.result).toBeNull(); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.info).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, expect.any(Number), 'testUser'); + expect(mockLogger.info).toHaveBeenCalledWith(USER_NOT_IN_ANY_EXPERIMENT, 'testUser', '666'); + }); + + it('should return decision response with variation null when a user is bucketed into traffic space of deleted experiment within a random group', () => { + vi.spyOn(bucketValueGenerator, 'generateBucketValue').mockReturnValueOnce(9000); + + const decisionResponse = bucketer.bucket(bucketerParams); + + expect(decisionResponse.result).toBeNull(); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.info).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, expect.any(Number), 'testUser'); + expect(mockLogger.info).toHaveBeenCalledWith(USER_NOT_IN_ANY_EXPERIMENT, 'testUser', '666'); + }); + + it('should throw an error if group ID is not in the datafile', () => { + const bucketerParamsWithInvalidGroupId = cloneDeep(bucketerParams); + bucketerParamsWithInvalidGroupId.experimentIdMap[configObj.experiments[4].id].groupId = '6969'; + + expect(()=> bucketer.bucket(bucketerParamsWithInvalidGroupId)).toThrow(OptimizelyError); + + try { + bucketer.bucket(bucketerParamsWithInvalidGroupId); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_GROUP_ID); + } + }); +}); + +describe('including groups: overlapping', () => { + let configObj: ProjectConfig; + const mockLogger = getMockLogger(); + let bucketerParams: BucketerParams; + + beforeEach(() => { + setLogSpy(mockLogger); + configObj = projectConfig.createProjectConfig(cloneDeep(testData)); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + bucketerParams = { + experimentId: configObj.experiments[6].id, + experimentKey: configObj.experiments[6].key, + trafficAllocationConfig: configObj.experiments[6].trafficAllocation, + variationIdMap: configObj.variationIdMap, + experimentIdMap: configObj.experimentIdMap, + groupIdMap: configObj.groupIdMap, + logger: mockLogger, + userId: 'testUser', + validateEntity: true, + }; + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return decision response with variation when a user falls into an experiment within an overlapping group', () => { + vi.spyOn(bucketValueGenerator, 'generateBucketValue').mockReturnValueOnce(0); + + const decisionResponse = bucketer.bucket(bucketerParams); + + expect(decisionResponse.result).toBe('553'); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith(USER_ASSIGNED_TO_EXPERIMENT_BUCKET, expect.any(Number), 'testUser'); + }); + + it('should return decision response with variation null when a user does not fall into an experiment within an overlapping group', () => { + vi.spyOn(bucketValueGenerator, 'generateBucketValue').mockReturnValueOnce(3000); + const decisionResponse = bucketer.bucket(bucketerParams); + + expect(decisionResponse.result).toBeNull(); + }); +}); + +describe('bucket value falls into empty traffic allocation ranges', () => { + let configObj: ProjectConfig; + const mockLogger = getMockLogger(); + let bucketerParams: BucketerParams; + + beforeEach(() => { + setLogSpy(mockLogger); + configObj = projectConfig.createProjectConfig(cloneDeep(testData)); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + bucketerParams = { + experimentId: configObj.experiments[0].id, + experimentKey: configObj.experiments[0].key, + trafficAllocationConfig: [ + { + entityId: '', + endOfRange: 5000, + }, + { + entityId: '', + endOfRange: 10000, + }, + ], + variationIdMap: configObj.variationIdMap, + experimentIdMap: configObj.experimentIdMap, + groupIdMap: configObj.groupIdMap, + logger: mockLogger, + validateEntity: true, + }; + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return decision response with variation null', () => { + const bucketerParamsTest1 = cloneDeep(bucketerParams); + bucketerParamsTest1.userId = 'ppid1'; + const decisionResponse = bucketer.bucket(bucketerParamsTest1); + + expect(decisionResponse.result).toBeNull(); + }); + + it('should not log an invalid variation ID warning', () => { + bucketer.bucket(bucketerParams); + + expect(mockLogger.warn).not.toHaveBeenCalled(); + }); +}); + +describe('traffic allocation has invalid variation ids', () => { + let configObj: ProjectConfig; + const mockLogger = getMockLogger(); + let bucketerParams: BucketerParams; + + beforeEach(() => { + setLogSpy(mockLogger); + configObj = projectConfig.createProjectConfig(cloneDeep(testData)); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + //@ts-ignore + bucketerParams = { + experimentId: configObj.experiments[0].id, + experimentKey: configObj.experiments[0].key, + trafficAllocationConfig: [ + { + entityId: '-1', + endOfRange: 5000, + }, + { + entityId: '-2', + endOfRange: 10000, + }, + ], + variationIdMap: configObj.variationIdMap, + experimentIdMap: configObj.experimentIdMap, + groupIdMap: configObj.groupIdMap, + logger: mockLogger, + validateEntity: true, + }; + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return decision response with variation null', () => { + const bucketerParamsTest1 = cloneDeep(bucketerParams); + bucketerParamsTest1.userId = 'ppid1'; + const decisionResponse = bucketer.bucket(bucketerParamsTest1); + + expect(decisionResponse.result).toBeNull(); + }); +}); + + + +describe('testBucketWithBucketingId', () => { + let bucketerParams: BucketerParams; + + beforeEach(() => { + const configObj = projectConfig.createProjectConfig(cloneDeep(testData)); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + bucketerParams = { + trafficAllocationConfig: configObj.experiments[0].trafficAllocation, + variationIdMap: configObj.variationIdMap, + experimentIdMap: configObj.experimentIdMap, + groupIdMap: configObj.groupIdMap, + validateEntity: true, + }; + }); + + it('check that a non null bucketingId buckets a variation different than the one expected with userId', () => { + const bucketerParams1 = cloneDeep(bucketerParams); + bucketerParams1['userId'] = 'testBucketingIdControl'; + bucketerParams1['bucketingId'] = '123456789'; + bucketerParams1['experimentKey'] = 'testExperiment'; + bucketerParams1['experimentId'] = '111127'; + + expect(bucketer.bucket(bucketerParams1).result).toBe('111129'); + }); + + it('check that a null bucketing ID defaults to bucketing with the userId', () => { + const bucketerParams2 = cloneDeep(bucketerParams); + bucketerParams2['userId'] = 'testBucketingIdControl'; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + bucketerParams2['bucketingId'] = null; + bucketerParams2['experimentKey'] = 'testExperiment'; + bucketerParams2['experimentId'] = '111127'; + + expect(bucketer.bucket(bucketerParams2).result).toBe('111128'); + }); + + it('check that bucketing works with an experiment in group', () => { + const bucketerParams4 = cloneDeep(bucketerParams); + bucketerParams4['userId'] = 'testBucketingIdControl'; + bucketerParams4['bucketingId'] = '123456789'; + bucketerParams4['experimentKey'] = 'groupExperiment2'; + bucketerParams4['experimentId'] = '443'; + + expect(bucketer.bucket(bucketerParams4).result).toBe('111128'); + }); +}); diff --git a/packages/optimizely-sdk/lib/core/bucketer/index.tests.js b/lib/core/bucketer/index.tests.js similarity index 72% rename from packages/optimizely-sdk/lib/core/bucketer/index.tests.js rename to lib/core/bucketer/index.tests.js index 97d261c04..a1e046088 100644 --- a/packages/optimizely-sdk/lib/core/bucketer/index.tests.js +++ b/lib/core/bucketer/index.tests.js @@ -1,5 +1,5 @@ /** - * Copyright 2016-2017, 2019-2022, Optimizely + * Copyright 2016-2017, 2019-2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,35 +15,52 @@ */ import sinon from 'sinon'; import { assert, expect } from 'chai'; -import { cloneDeep } from 'lodash'; +import { cloneDeep, create } from 'lodash'; import { sprintf } from '../../utils/fns'; - +import * as bucketValueGenerator from './bucket_value_generator' import * as bucketer from './'; -import { - ERROR_MESSAGES, - LOG_MESSAGES, - LOG_LEVEL, -} from '../../utils/enums'; -import { createLogger } from '../../plugins/logger'; -import projectConfig from '../project_config'; +import { LOG_LEVEL } from '../../utils/enums'; +import projectConfig from '../../project_config/project_config'; import { getTestProjectConfig } from '../../tests/test_data'; +import { INVALID_BUCKETING_ID, INVALID_GROUP_ID } from 'error_message'; +import { + USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, + USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, + USER_NOT_IN_ANY_EXPERIMENT, + USER_ASSIGNED_TO_EXPERIMENT_BUCKET, +} from '.'; +import { OptimizelyError } from '../../error/optimizly_error'; var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); var testData = getTestProjectConfig(); +var createLogger = () => ({ + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + child: () => createLogger(), +}) + describe('lib/core/bucketer', function () { describe('APIs', function () { describe('bucket', function () { var configObj; - var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); + var createdLogger = createLogger(); var bucketerParams; beforeEach(function () { - sinon.stub(createdLogger, 'log'); + sinon.stub(createdLogger, 'info'); + sinon.stub(createdLogger, 'debug'); + sinon.stub(createdLogger, 'warn'); + sinon.stub(createdLogger, 'error'); }); afterEach(function () { - createdLogger.log.restore(); + createdLogger.info.restore(); + createdLogger.debug.restore(); + createdLogger.warn.restore(); + createdLogger.error.restore(); }); describe('return values for bucketing (excluding groups)', function () { @@ -57,9 +74,10 @@ describe('lib/core/bucketer', function () { experimentIdMap: configObj.experimentIdMap, groupIdMap: configObj.groupIdMap, logger: createdLogger, + validateEntity: true, }; sinon - .stub(bucketer, '_generateBucketValue') + .stub(bucketValueGenerator, 'generateBucketValue') .onFirstCall() .returns(50) .onSecondCall() @@ -67,7 +85,7 @@ describe('lib/core/bucketer', function () { }); afterEach(function () { - bucketer._generateBucketValue.restore(); + bucketValueGenerator.generateBucketValue.restore(); }); it('should return decision response with correct variation ID when provided bucket value', function () { @@ -76,20 +94,13 @@ describe('lib/core/bucketer', function () { var decisionResponse = bucketer.bucket(bucketerParamsTest1); expect(decisionResponse.result).to.equal('111128'); - var bucketedUser_log1 = buildLogMessageFromArgs(createdLogger.log.args[0]); - expect(bucketedUser_log1).to.equal( - sprintf(LOG_MESSAGES.USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '50', 'ppid1') - ); + expect(createdLogger.debug.args[0]).to.deep.equal([USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 50, 'ppid1']); var bucketerParamsTest2 = cloneDeep(bucketerParams); bucketerParamsTest2.userId = 'ppid2'; expect(bucketer.bucket(bucketerParamsTest2).result).to.equal(null); - var notBucketedUser_log1 = buildLogMessageFromArgs(createdLogger.log.args[1]); - - expect(notBucketedUser_log1).to.equal( - sprintf(LOG_MESSAGES.USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '50000', 'ppid2') - ); + expect(createdLogger.debug.args[1]).to.deep.equal([USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 50000, 'ppid2']); }); }); @@ -105,12 +116,13 @@ describe('lib/core/bucketer', function () { experimentIdMap: configObj.experimentIdMap, groupIdMap: configObj.groupIdMap, logger: createdLogger, + validateEntity: true, }; - bucketerStub = sinon.stub(bucketer, '_generateBucketValue'); + bucketerStub = sinon.stub(bucketValueGenerator, 'generateBucketValue'); }); afterEach(function () { - bucketer._generateBucketValue.restore(); + bucketValueGenerator.generateBucketValue.restore(); }); describe('random groups', function () { @@ -125,6 +137,7 @@ describe('lib/core/bucketer', function () { groupIdMap: configObj.groupIdMap, logger: createdLogger, userId: 'testUser', + validateEntity: true, }; }); @@ -136,28 +149,14 @@ describe('lib/core/bucketer', function () { expect(decisionResponse.result).to.equal('551'); sinon.assert.calledTwice(bucketerStub); - sinon.assert.callCount(createdLogger.log, 3); - - var log1 = buildLogMessageFromArgs(createdLogger.log.args[0]); - expect(log1).to.equal( - sprintf(LOG_MESSAGES.USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '50', 'testUser') - ); - - var log2 = buildLogMessageFromArgs(createdLogger.log.args[1]); - expect(log2).to.equal( - sprintf( - LOG_MESSAGES.USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, - 'BUCKETER', - 'testUser', - 'groupExperiment1', - '666' - ) - ); - - var log3 = buildLogMessageFromArgs(createdLogger.log.args[2]); - expect(log3).to.equal( - sprintf(LOG_MESSAGES.USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '50', 'testUser') - ); + sinon.assert.callCount(createdLogger.debug, 2); + sinon.assert.callCount(createdLogger.info, 1); + + expect(createdLogger.debug.args[0]).to.deep.equal([USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 50, 'testUser']); + + expect(createdLogger.info.args[0]).to.deep.equal([USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, 'testUser', 'groupExperiment1', '666']); + + expect(createdLogger.debug.args[1]).to.deep.equal([USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 50, 'testUser']); }); it('should return decision response with variation null when a user is bucketed into a different grouped experiment than the one speicfied', function () { @@ -167,22 +166,12 @@ describe('lib/core/bucketer', function () { expect(decisionResponse.result).to.equal(null); sinon.assert.calledOnce(bucketerStub); - sinon.assert.calledTwice(createdLogger.log); - - var log1 = buildLogMessageFromArgs(createdLogger.log.args[0]); - expect(log1).to.equal( - sprintf(LOG_MESSAGES.USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '5000', 'testUser') - ); - var log2 = buildLogMessageFromArgs(createdLogger.log.args[1]); - expect(log2).to.equal( - sprintf( - LOG_MESSAGES.USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, - 'BUCKETER', - 'testUser', - 'groupExperiment1', - '666' - ) - ); + sinon.assert.calledOnce(createdLogger.debug); + sinon.assert.calledOnce(createdLogger.info); + + expect(createdLogger.debug.args[0]).to.deep.equal([USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 5000, 'testUser']); + + expect(createdLogger.info.args[0]).to.deep.equal([USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, 'testUser', 'groupExperiment1', '666']); }); it('should return decision response with variation null when a user is not bucketed into any experiments in the random group', function () { @@ -192,14 +181,12 @@ describe('lib/core/bucketer', function () { expect(decisionResponse.result).to.equal(null); sinon.assert.calledOnce(bucketerStub); - sinon.assert.calledTwice(createdLogger.log); - - var log1 = buildLogMessageFromArgs(createdLogger.log.args[0]); - expect(log1).to.equal( - sprintf(LOG_MESSAGES.USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '50000', 'testUser') - ); - var log2 = buildLogMessageFromArgs(createdLogger.log.args[1]); - expect(log2).to.equal(sprintf(LOG_MESSAGES.USER_NOT_IN_ANY_EXPERIMENT, 'BUCKETER', 'testUser', '666')); + sinon.assert.calledOnce(createdLogger.debug); + sinon.assert.calledOnce(createdLogger.info); + + expect(createdLogger.debug.args[0]).to.deep.equal([USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 50000, 'testUser']); + + expect(createdLogger.info.args[0]).to.deep.equal([USER_NOT_IN_ANY_EXPERIMENT, 'testUser', '666']); }); it('should return decision response with variation null when a user is bucketed into traffic space of deleted experiment within a random group', function () { @@ -209,23 +196,23 @@ describe('lib/core/bucketer', function () { expect(decisionResponse.result).to.equal(null); sinon.assert.calledOnce(bucketerStub); - sinon.assert.calledTwice(createdLogger.log); - - var log1 = buildLogMessageFromArgs(createdLogger.log.args[0]); - expect(log1).to.equal( - sprintf(LOG_MESSAGES.USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '9000', 'testUser') - ); - var log2 = buildLogMessageFromArgs(createdLogger.log.args[1]); - expect(log2).to.equal(sprintf(LOG_MESSAGES.USER_NOT_IN_ANY_EXPERIMENT, 'BUCKETER', 'testUser', '666')); + sinon.assert.calledOnce(createdLogger.debug); + sinon.assert.calledOnce(createdLogger.info); + + expect(createdLogger.debug.args[0]).to.deep.equal([USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 9000, 'testUser']); + + expect(createdLogger.info.args[0]).to.deep.equal([USER_NOT_IN_ANY_EXPERIMENT, 'testUser', '666']); }); it('should throw an error if group ID is not in the datafile', function () { var bucketerParamsWithInvalidGroupId = cloneDeep(bucketerParams); bucketerParamsWithInvalidGroupId.experimentIdMap[configObj.experiments[4].id].groupId = '6969'; - assert.throws(function () { + const ex = assert.throws(function () { bucketer.bucket(bucketerParamsWithInvalidGroupId); - }, sprintf(ERROR_MESSAGES.INVALID_GROUP_ID, 'BUCKETER', '6969')); + }); + assert.equal(ex.baseMessage, INVALID_GROUP_ID); + assert.deepEqual(ex.params, ['6969']); }); }); @@ -241,6 +228,7 @@ describe('lib/core/bucketer', function () { groupIdMap: configObj.groupIdMap, logger: createdLogger, userId: 'testUser', + validateEntity: true, }; }); @@ -251,10 +239,9 @@ describe('lib/core/bucketer', function () { expect(decisionResponse.result).to.equal('553'); sinon.assert.calledOnce(bucketerStub); - sinon.assert.calledOnce(createdLogger.log); + sinon.assert.calledOnce(createdLogger.debug); - var log1 = buildLogMessageFromArgs(createdLogger.log.args[0]); - expect(log1).to.equal(sprintf(LOG_MESSAGES.USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 'BUCKETER', '0', 'testUser')); + expect(createdLogger.debug.args[0]).to.deep.equal([USER_ASSIGNED_TO_EXPERIMENT_BUCKET, 0, 'testUser']); }); it('should return decision response with variation null when a user does not fall into an experiment within an overlapping group', function () { @@ -286,6 +273,7 @@ describe('lib/core/bucketer', function () { experimentIdMap: configObj.experimentIdMap, groupIdMap: configObj.groupIdMap, logger: createdLogger, + validateEntity: true, }; }); @@ -298,8 +286,15 @@ describe('lib/core/bucketer', function () { it('should not log an invalid variation ID warning', function () { bucketer.bucket(bucketerParams) - const foundInvalidVariationWarning = createdLogger.log.getCalls().some((call) => { - const message = call.args[1]; + const calls = [ + ...createdLogger.debug.getCalls(), + ...createdLogger.info.getCalls(), + ...createdLogger.warn.getCalls(), + ...createdLogger.error.getCalls(), + ]; + + const foundInvalidVariationWarning = calls.some((call) => { + const message = call.args[0]; return message.includes('Bucketed into an invalid variation ID') }); expect(foundInvalidVariationWarning).to.equal(false); @@ -326,6 +321,7 @@ describe('lib/core/bucketer', function () { experimentIdMap: configObj.experimentIdMap, groupIdMap: configObj.groupIdMap, logger: createdLogger, + validateEntity: true, }; }); @@ -338,7 +334,7 @@ describe('lib/core/bucketer', function () { }); }); - describe('_generateBucketValue', function () { + describe('generateBucketValue', function () { it('should return a bucket value for different inputs', function () { var experimentId = 1886780721; var bucketingKey1 = sprintf('%s%s', 'ppid1', experimentId); @@ -346,20 +342,17 @@ describe('lib/core/bucketer', function () { var bucketingKey3 = sprintf('%s%s', 'ppid2', 1886780722); var bucketingKey4 = sprintf('%s%s', 'ppid3', experimentId); - expect(bucketer._generateBucketValue(bucketingKey1)).to.equal(5254); - expect(bucketer._generateBucketValue(bucketingKey2)).to.equal(4299); - expect(bucketer._generateBucketValue(bucketingKey3)).to.equal(2434); - expect(bucketer._generateBucketValue(bucketingKey4)).to.equal(5439); + expect(bucketValueGenerator.generateBucketValue(bucketingKey1)).to.equal(5254); + expect(bucketValueGenerator.generateBucketValue(bucketingKey2)).to.equal(4299); + expect(bucketValueGenerator.generateBucketValue(bucketingKey3)).to.equal(2434); + expect(bucketValueGenerator.generateBucketValue(bucketingKey4)).to.equal(5439); }); it('should return an error if it cannot generate the hash value', function() { const response = assert.throws(function() { - bucketer._generateBucketValue(null); + bucketValueGenerator.generateBucketValue(null); } ); - expect([ - sprintf(ERROR_MESSAGES.INVALID_BUCKETING_ID, 'BUCKETER', null, "Cannot read property 'length' of null"), // node v14 - sprintf(ERROR_MESSAGES.INVALID_BUCKETING_ID, 'BUCKETER', null, "Cannot read properties of null (reading \'length\')") // node v16 - ]).contain(response.message); + expect(response.baseMessage).to.equal(INVALID_BUCKETING_ID); }); }); @@ -378,6 +371,7 @@ describe('lib/core/bucketer', function () { experimentIdMap: configObj.experimentIdMap, groupIdMap: configObj.groupIdMap, logger: createdLogger, + validateEntity: true, }; }); diff --git a/packages/optimizely-sdk/lib/core/bucketer/index.ts b/lib/core/bucketer/index.ts similarity index 65% rename from packages/optimizely-sdk/lib/core/bucketer/index.ts rename to lib/core/bucketer/index.ts index c2c6a0235..e31c8df4b 100644 --- a/packages/optimizely-sdk/lib/core/bucketer/index.ts +++ b/lib/core/bucketer/index.ts @@ -17,26 +17,23 @@ /** * Bucketer API for determining the variation id from the specified parameters */ -import { sprintf } from '../../utils/fns'; -import murmurhash from 'murmurhash'; -import { LogHandler } from '../../modules/logging'; +import { LoggerFacade } from '../../logging/logger'; import { DecisionResponse, BucketerParams, TrafficAllocation, Group, } from '../../shared_types'; +import { INVALID_GROUP_ID } from 'error_message'; +import { OptimizelyError } from '../../error/optimizly_error'; +import { generateBucketValue } from './bucket_value_generator'; +import { DecisionReason } from '../decision_service'; -import { - ERROR_MESSAGES, - LOG_LEVEL, - LOG_MESSAGES, -} from '../../utils/enums'; - -const HASH_SEED = 1; -const MAX_HASH_VALUE = Math.pow(2, 32); -const MAX_TRAFFIC_VALUE = 10000; -const MODULE_NAME = 'BUCKETER'; +export const USER_NOT_IN_ANY_EXPERIMENT = 'User %s is not in any experiment of group %s.'; +export const USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP = 'User %s is not in experiment %s of group %s.'; +export const USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP = 'User %s is in experiment %s of group %s.'; +export const USER_ASSIGNED_TO_EXPERIMENT_BUCKET = 'Assigned bucket %s to user with bucketing ID %s.'; +export const INVALID_VARIATION_ID = 'Bucketed into an invalid variation ID. Returning null.'; const RANDOM_POLICY = 'random'; /** @@ -56,14 +53,15 @@ const RANDOM_POLICY = 'random'; * null if user is not bucketed into any experiment and the decide reasons. */ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse<string | null> { - const decideReasons: (string | number)[][] = []; + const decideReasons: DecisionReason[] = []; // Check if user is in a random group; if so, check if user is bucketed into a specific experiment const experiment = bucketerParams.experimentIdMap[bucketerParams.experimentId]; - const groupId = experiment['groupId']; + // Optional chaining skips groupId check for holdout experiments; Holdout experimentId is not in experimentIdMap + const groupId = experiment?.['groupId']; if (groupId) { const group = bucketerParams.groupIdMap[groupId]; if (!group) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_GROUP_ID, MODULE_NAME, groupId)); + throw new OptimizelyError(INVALID_GROUP_ID, groupId); } if (group.policy === RANDOM_POLICY) { const bucketedExperimentId = bucketUserIntoExperiment( @@ -75,16 +73,13 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse // Return if user is not bucketed into any experiment if (bucketedExperimentId === null) { - bucketerParams.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.USER_NOT_IN_ANY_EXPERIMENT, - MODULE_NAME, + bucketerParams.logger?.info( + USER_NOT_IN_ANY_EXPERIMENT, bucketerParams.userId, groupId, ); decideReasons.push([ - LOG_MESSAGES.USER_NOT_IN_ANY_EXPERIMENT, - MODULE_NAME, + USER_NOT_IN_ANY_EXPERIMENT, bucketerParams.userId, groupId, ]); @@ -96,17 +91,14 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse // Return if user is bucketed into a different experiment than the one specified if (bucketedExperimentId !== bucketerParams.experimentId) { - bucketerParams.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, - MODULE_NAME, + bucketerParams.logger?.info( + USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, bucketerParams.userId, bucketerParams.experimentKey, groupId, ); decideReasons.push([ - LOG_MESSAGES.USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, - MODULE_NAME, + USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP, bucketerParams.userId, bucketerParams.experimentKey, groupId, @@ -118,17 +110,14 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse } // Continue bucketing if user is bucketed into specified experiment - bucketerParams.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, - MODULE_NAME, + bucketerParams.logger?.info( + USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, bucketerParams.userId, bucketerParams.experimentKey, groupId, ); decideReasons.push([ - LOG_MESSAGES.USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, - MODULE_NAME, + USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, bucketerParams.userId, bucketerParams.experimentKey, groupId, @@ -136,34 +125,30 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse } } const bucketingId = `${bucketerParams.bucketingId}${bucketerParams.experimentId}`; - const bucketValue = _generateBucketValue(bucketingId); + const bucketValue = generateBucketValue(bucketingId); - bucketerParams.logger.log( - LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_ASSIGNED_TO_EXPERIMENT_BUCKET, - MODULE_NAME, + bucketerParams.logger?.debug( + USER_ASSIGNED_TO_EXPERIMENT_BUCKET, bucketValue, bucketerParams.userId, ); decideReasons.push([ - LOG_MESSAGES.USER_ASSIGNED_TO_EXPERIMENT_BUCKET, - MODULE_NAME, + USER_ASSIGNED_TO_EXPERIMENT_BUCKET, bucketValue, bucketerParams.userId, ]); const entityId = _findBucket(bucketValue, bucketerParams.trafficAllocationConfig); - if (entityId !== null) { - if (!bucketerParams.variationIdMap[entityId]) { - if (entityId) { - bucketerParams.logger.log(LOG_LEVEL.WARNING, LOG_MESSAGES.INVALID_VARIATION_ID, MODULE_NAME); - decideReasons.push([LOG_MESSAGES.INVALID_VARIATION_ID, MODULE_NAME]); - } - return { - result: null, - reasons: decideReasons, - }; + + if (bucketerParams.validateEntity && entityId !== null && !bucketerParams.variationIdMap[entityId]) { + if (entityId) { + bucketerParams.logger?.warn(INVALID_VARIATION_ID); + decideReasons.push([INVALID_VARIATION_ID]); } + return { + result: null, + reasons: decideReasons, + }; } return { @@ -177,21 +162,19 @@ export const bucket = function(bucketerParams: BucketerParams): DecisionResponse * @param {Group} group Group that experiment is in * @param {string} bucketingId Bucketing ID * @param {string} userId ID of user to be bucketed into experiment - * @param {LogHandler} logger Logger implementation + * @param {LoggerFacade} logger Logger implementation * @return {string|null} ID of experiment if user is bucketed into experiment within the group, null otherwise */ export const bucketUserIntoExperiment = function( group: Group, bucketingId: string, userId: string, - logger: LogHandler + logger?: LoggerFacade ): string | null { const bucketingKey = `${bucketingId}${group.id}`; - const bucketValue = _generateBucketValue(bucketingKey); - logger.log( - LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_ASSIGNED_TO_EXPERIMENT_BUCKET, - MODULE_NAME, + const bucketValue = generateBucketValue(bucketingKey); + logger?.debug( + USER_ASSIGNED_TO_EXPERIMENT_BUCKET, bucketValue, userId, ); @@ -221,26 +204,7 @@ export const _findBucket = function( return null; }; -/** - * Helper function to generate bucket value in half-closed interval [0, MAX_TRAFFIC_VALUE) - * @param {string} bucketingKey String value for bucketing - * @return {number} The generated bucket value - * @throws If bucketing value is not a valid string - */ -export const _generateBucketValue = function(bucketingKey: string): number { - try { - // NOTE: the mmh library already does cast the hash value as an unsigned 32bit int - // https://github.com/perezd/node-murmurhash/blob/master/murmurhash.js#L115 - const hashValue = murmurhash.v3(bucketingKey, HASH_SEED); - const ratio = hashValue / MAX_HASH_VALUE; - return Math.floor(ratio * MAX_TRAFFIC_VALUE); - } catch (ex: any) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_BUCKETING_ID, MODULE_NAME, bucketingKey, ex.message)); - } -}; - export default { bucket: bucket, bucketUserIntoExperiment: bucketUserIntoExperiment, - _generateBucketValue: _generateBucketValue, }; diff --git a/lib/core/condition_tree_evaluator/index.spec.ts b/lib/core/condition_tree_evaluator/index.spec.ts new file mode 100644 index 000000000..5afdd0d7d --- /dev/null +++ b/lib/core/condition_tree_evaluator/index.spec.ts @@ -0,0 +1,218 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, vi, expect } from 'vitest'; + +import * as conditionTreeEvaluator from '.'; + +const conditionA = { + name: 'browser_type', + value: 'safari', + type: 'custom_attribute', +}; +const conditionB = { + name: 'device_model', + value: 'iphone6', + type: 'custom_attribute', +}; +const conditionC = { + name: 'location', + match: 'exact', + type: 'custom_attribute', + value: 'CA', +}; +describe('evaluate', function() { + it('should return true for a leaf condition when the leaf condition evaluator returns true', function() { + expect( + conditionTreeEvaluator.evaluate(conditionA, function() { + return true; + }) + ).toBe(true); + }); + + it('should return false for a leaf condition when the leaf condition evaluator returns false', function() { + expect( + conditionTreeEvaluator.evaluate(conditionA, function() { + return false; + }) + ).toBe(false); + }); + + describe('and evaluation', function() { + it('should return true when ALL conditions evaluate to true', function() { + expect( + conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], function() { + return true; + }) + ).toBe(true); + }); + + it('should return false if one condition evaluates to false', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => true).mockImplementationOnce(() => false); + expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], leafEvaluator)).toBe(false); + }); + + describe('null handling', function() { + it('should return null when all operands evaluate to null', function() { + expect( + conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], function() { + return null; + }) + ).toBeNull(); + }); + + it('should return null when operands evaluate to trues and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => true).mockImplementationOnce(() => null); + expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], leafEvaluator)).toBeNull(); + }); + + it('should return false when operands evaluate to falses and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => false).mockImplementationOnce(() => null); + expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], leafEvaluator)).toBe(false); + + leafEvaluator.mockReset(); + leafEvaluator.mockImplementationOnce(() => null).mockImplementationOnce(() => false); + expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB], leafEvaluator)).toBe(false); + }); + + it('should return false when operands evaluate to trues, falses, and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator + .mockImplementationOnce(() => true) + .mockImplementationOnce(() => false) + .mockImplementationOnce(() => null); + expect(conditionTreeEvaluator.evaluate(['and', conditionA, conditionB, conditionC], leafEvaluator)).toBe(false); + }); + }); + }); + + describe('or evaluation', function() { + it('should return true if any condition evaluates to true', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => false).mockImplementationOnce(() => true); + expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], leafEvaluator)).toBe(true); + }); + + it('should return false if all conditions evaluate to false', function() { + expect( + conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], function() { + return false; + }) + ).toBe(false); + }); + + describe('null handling', function() { + it('should return null when all operands evaluate to null', function() { + expect( + conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], function() { + return null; + }) + ).toBeNull(); + }); + + it('should return true when operands evaluate to trues and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => true).mockImplementationOnce(() => null); + expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], leafEvaluator)).toBe(true); + }); + + it('should return null when operands evaluate to falses and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => null).mockImplementationOnce(() => false); + expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], leafEvaluator)).toBeNull(); + + leafEvaluator.mockReset(); + leafEvaluator.mockImplementationOnce(() => false).mockImplementationOnce(() => null); + expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB], leafEvaluator)).toBeNull(); + }); + + it('should return true when operands evaluate to trues, falses, and nulls', function() { + const leafEvaluator = vi.fn(); + leafEvaluator + .mockImplementationOnce(() => true) + .mockImplementationOnce(() => null) + .mockImplementationOnce(() => false); + expect(conditionTreeEvaluator.evaluate(['or', conditionA, conditionB, conditionC], leafEvaluator)).toBe(true); + }); + }); + }); + + describe('not evaluation', function() { + it('should return true if the condition evaluates to false', function() { + expect( + conditionTreeEvaluator.evaluate(['not', conditionA], function() { + return false; + }) + ).toBe(true); + }); + + it('should return false if the condition evaluates to true', function() { + expect( + conditionTreeEvaluator.evaluate(['not', conditionB], function() { + return true; + }) + ).toBe(false); + }); + + it('should return the result of negating the first condition, and ignore any additional conditions', function() { + let result = conditionTreeEvaluator.evaluate(['not', '1', '2', '1'], function(id: string) { + return id === '1'; + }); + expect(result).toBe(false); + result = conditionTreeEvaluator.evaluate(['not', '1', '2', '1'], function(id: string) { + return id === '2'; + }); + expect(result).toBe(true); + result = conditionTreeEvaluator.evaluate(['not', '1', '2', '3'], function(id: string) { + return id === '1' ? null : id === '3'; + }); + expect(result).toBeNull(); + }); + + describe('null handling', function() { + it('should return null when operand evaluates to null', function() { + expect( + conditionTreeEvaluator.evaluate(['not', conditionA], function() { + return null; + }) + ).toBeNull(); + }); + + it('should return null when there are no operands', function() { + expect( + conditionTreeEvaluator.evaluate(['not'], function() { + return null; + }) + ).toBeNull(); + }); + }); + }); + + describe('implicit operator', function() { + it('should behave like an "or" operator when the first item in the array is not a recognized operator', function() { + const leafEvaluator = vi.fn(); + leafEvaluator.mockImplementationOnce(() => true).mockImplementationOnce(() => false); + expect(conditionTreeEvaluator.evaluate([conditionA, conditionB], leafEvaluator)).toBe(true); + expect( + conditionTreeEvaluator.evaluate([conditionA, conditionB], function() { + return false; + }) + ).toBe(false); + }); + }); +}); diff --git a/packages/optimizely-sdk/lib/core/condition_tree_evaluator/index.tests.js b/lib/core/condition_tree_evaluator/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/core/condition_tree_evaluator/index.tests.js rename to lib/core/condition_tree_evaluator/index.tests.js diff --git a/packages/optimizely-sdk/lib/core/condition_tree_evaluator/index.ts b/lib/core/condition_tree_evaluator/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/condition_tree_evaluator/index.ts rename to lib/core/condition_tree_evaluator/index.ts diff --git a/lib/core/custom_attribute_condition_evaluator/index.spec.ts b/lib/core/custom_attribute_condition_evaluator/index.spec.ts new file mode 100644 index 000000000..66f8cae0d --- /dev/null +++ b/lib/core/custom_attribute_condition_evaluator/index.spec.ts @@ -0,0 +1,1411 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; +import * as customAttributeEvaluator from './'; +import { MISSING_ATTRIBUTE_VALUE, UNEXPECTED_TYPE_NULL } from 'log_message'; +import { UNKNOWN_MATCH_TYPE, UNEXPECTED_TYPE, OUT_OF_BOUNDS, UNEXPECTED_CONDITION_VALUE } from 'error_message'; +import { Condition } from '../../shared_types'; +import { getMockLogger } from '../../tests/mock/mock_logger'; +import { LoggerFacade } from '../../logging/logger'; + +const browserConditionSafari = { + name: 'browser_type', + value: 'safari', + type: 'custom_attribute', +}; +const booleanCondition = { + name: 'is_firefox', + value: true, + type: 'custom_attribute', +}; +const integerCondition = { + name: 'num_users', + value: 10, + type: 'custom_attribute', +}; +const doubleCondition = { + name: 'pi_value', + value: 3.14, + type: 'custom_attribute', +}; + +const getMockUserContext: any = (attributes: any) => ({ + getAttributes: () => ({ ...(attributes || {}) }), +}); + +const setLogSpy = (logger: LoggerFacade) => { + vi.spyOn(logger, 'error'); + vi.spyOn(logger, 'debug'); + vi.spyOn(logger, 'info'); + vi.spyOn(logger, 'warn'); +}; + +describe('custom_attribute_condition_evaluator', () => { + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return true when the attributes pass the audience conditions and no match type is provided', () => { + const userAttributes = { + browser_type: 'safari', + }; + + expect( + customAttributeEvaluator.getEvaluator().evaluate(browserConditionSafari, getMockUserContext(userAttributes)) + ).toBe(true); + }); + + it('should return false when the attributes do not pass the audience conditions and no match type is provided', () => { + const userAttributes = { + browser_type: 'firefox', + }; + + expect( + customAttributeEvaluator.getEvaluator().evaluate(browserConditionSafari, getMockUserContext(userAttributes)) + ).toBe(false); + }); + + it('should evaluate different typed attributes', () => { + const userAttributes = { + browser_type: 'safari', + is_firefox: true, + num_users: 10, + pi_value: 3.14, + }; + + expect( + customAttributeEvaluator.getEvaluator().evaluate(browserConditionSafari, getMockUserContext(userAttributes)) + ).toBe(true); + expect(customAttributeEvaluator.getEvaluator().evaluate(booleanCondition, getMockUserContext(userAttributes))).toBe( + true + ); + expect(customAttributeEvaluator.getEvaluator().evaluate(integerCondition, getMockUserContext(userAttributes))).toBe( + true + ); + expect(customAttributeEvaluator.getEvaluator().evaluate(doubleCondition, getMockUserContext(userAttributes))).toBe( + true + ); + }); + + it('should log and return null when condition has an invalid match property', () => { + const invalidMatchCondition = { match: 'weird', name: 'weird_condition', type: 'custom_attribute', value: 'hi' }; + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(invalidMatchCondition, getMockUserContext({ weird_condition: 'bye' })); + + expect(result).toBe(null); + expect(mockLogger.warn).toHaveBeenCalledTimes(1); + expect(mockLogger.warn).toHaveBeenCalledWith(UNKNOWN_MATCH_TYPE, JSON.stringify(invalidMatchCondition)); + }); +}); + +describe('exists match type', () => { + const existsCondition = { + match: 'exists', + name: 'input_value', + type: 'custom_attribute', + value: '', + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return false if there is no user-provided value', () => { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(existsCondition, getMockUserContext({})); + + expect(result).toBe(false); + expect(mockLogger.debug).not.toHaveBeenCalled(); + expect(mockLogger.info).not.toHaveBeenCalled(); + expect(mockLogger.warn).not.toHaveBeenCalled(); + expect(mockLogger.error).not.toHaveBeenCalled(); + }); + + it('should return false if the user-provided value is undefined', () => { + const result = customAttributeEvaluator + .getEvaluator() + .evaluate(existsCondition, getMockUserContext({ input_value: undefined })); + + expect(result).toBe(false); + }); + + it('should return false if the user-provided value is null', () => { + const result = customAttributeEvaluator + .getEvaluator() + .evaluate(existsCondition, getMockUserContext({ input_value: null })); + + expect(result).toBe(false); + }); + + it('should return true if the user-provided value is a string', () => { + const result = customAttributeEvaluator + .getEvaluator() + .evaluate(existsCondition, getMockUserContext({ input_value: 'hi' })); + + expect(result).toBe(true); + }); + + it('should return true if the user-provided value is a number', () => { + const result = customAttributeEvaluator + .getEvaluator() + .evaluate(existsCondition, getMockUserContext({ input_value: 10 })); + + expect(result).toBe(true); + }); + + it('should return true if the user-provided value is a boolean', () => { + const result = customAttributeEvaluator + .getEvaluator() + .evaluate(existsCondition, getMockUserContext({ input_value: true })); + + expect(result).toBe(true); + }); +}); + +describe('exact match type - with a string condition value', () => { + const exactStringCondition = { + match: 'exact', + name: 'favorite_constellation', + type: 'custom_attribute', + value: 'Lacerta', + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return true if the user-provided value is equal to the condition value', () => { + const result = customAttributeEvaluator + .getEvaluator() + .evaluate(exactStringCondition, getMockUserContext({ favorite_constellation: 'Lacerta' })); + + expect(result).toBe(true); + }); + + it('should return false if the user-provided value is not equal to the condition value', () => { + const result = customAttributeEvaluator + .getEvaluator() + .evaluate(exactStringCondition, getMockUserContext({ favorite_constellation: 'The Big Dipper' })); + + expect(result).toBe(false); + }); + + it('should log and return null if condition value is of an unexpected type', () => { + const invalidExactCondition = { + match: 'exact', + name: 'favorite_constellation', + type: 'custom_attribute', + value: null, + }; + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(invalidExactCondition, getMockUserContext({ favorite_constellation: 'Lacerta' })); + + expect(result).toBe(null); + expect(mockLogger.warn).toHaveBeenCalledTimes(1); + expect(mockLogger.warn).toHaveBeenCalledWith(UNEXPECTED_CONDITION_VALUE, JSON.stringify(invalidExactCondition)); + }); + + it('should log and return null if the user-provided value is of a different type than the condition value', () => { + const unexpectedTypeUserAttributes: Record<string, boolean> = { favorite_constellation: false }; + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactStringCondition, getMockUserContext(unexpectedTypeUserAttributes)); + const userValue = unexpectedTypeUserAttributes[exactStringCondition.name]; + const userValueType = typeof userValue; + + expect(result).toBe(null); + expect(mockLogger.warn).toHaveBeenCalledTimes(1); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(exactStringCondition), + userValueType, + exactStringCondition.name + ); + }); + + it('should log and return null if the user-provided value is null', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactStringCondition, getMockUserContext({ favorite_constellation: null })); + + expect(result).toBe(null); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith( + UNEXPECTED_TYPE_NULL, + JSON.stringify(exactStringCondition), + exactStringCondition.name + ); + }); + + it('should log and return null if there is no user-provided value', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactStringCondition, getMockUserContext({})); + + expect(result).toBe(null); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith( + MISSING_ATTRIBUTE_VALUE, + JSON.stringify(exactStringCondition), + exactStringCondition.name + ); + }); + + it('should log and return null if the user-provided value is of an unexpected type', () => { + const unexpectedTypeUserAttributes: Record<string, unknown> = { favorite_constellation: [] }; + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactStringCondition, getMockUserContext(unexpectedTypeUserAttributes)); + const userValue = unexpectedTypeUserAttributes[exactStringCondition.name]; + const userValueType = typeof userValue; + + expect(result).toBe(null); + expect(mockLogger.warn).toHaveBeenCalledTimes(1); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(exactStringCondition), + userValueType, + exactStringCondition.name + ); + }); +}); + +describe('exact match type - with a number condition value', () => { + const exactNumberCondition = { + match: 'exact', + name: 'lasers_count', + type: 'custom_attribute', + value: 9000, + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return true if the user-provided value is equal to the condition value', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactNumberCondition, getMockUserContext({ lasers_count: 9000 })); + + expect(result).toBe(true); + }); + + it('should return false if the user-provided value is not equal to the condition value', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactNumberCondition, getMockUserContext({ lasers_count: 8000 })); + + expect(result).toBe(false); + }); + + it('should log and return null if the user-provided value is of a different type than the condition value', () => { + const unexpectedTypeUserAttributes1: Record<string, any> = { lasers_count: 'yes' }; + let result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactNumberCondition, getMockUserContext(unexpectedTypeUserAttributes1)); + + expect(result).toBe(null); + + const unexpectedTypeUserAttributes2: Record<string, any> = { lasers_count: '1000' }; + result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactNumberCondition, getMockUserContext(unexpectedTypeUserAttributes2)); + + expect(result).toBe(null); + + const userValue1 = unexpectedTypeUserAttributes1[exactNumberCondition.name]; + const userValueType1 = typeof userValue1; + const userValue2 = unexpectedTypeUserAttributes2[exactNumberCondition.name]; + const userValueType2 = typeof userValue2; + + expect(mockLogger.warn).toHaveBeenCalledTimes(2); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(exactNumberCondition), + userValueType1, + exactNumberCondition.name + ); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(exactNumberCondition), + userValueType2, + exactNumberCondition.name + ); + }); + + it('should log and return null if the user-provided number value is out of bounds', () => { + let result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactNumberCondition, getMockUserContext({ lasers_count: -Infinity })); + + expect(result).toBe(null); + + result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactNumberCondition, getMockUserContext({ lasers_count: -Math.pow(2, 53) - 2 })); + + expect(result).toBe(null); + expect(mockLogger.warn).toHaveBeenCalledTimes(2); + expect(mockLogger.warn).toHaveBeenCalledWith( + OUT_OF_BOUNDS, + JSON.stringify(exactNumberCondition), + exactNumberCondition.name + ); + }); + + it('should return null if there is no user-provided value', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactNumberCondition, getMockUserContext({})); + + expect(result).toBe(null); + }); + + it('should log and return null if the condition value is not finite', () => { + const invalidValueCondition1 = { + match: 'exact', + name: 'lasers_count', + type: 'custom_attribute', + value: Infinity, + }; + let result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(invalidValueCondition1, getMockUserContext({ lasers_count: 9000 })); + + expect(result).toBe(null); + + const invalidValueCondition2 = { + match: 'exact', + name: 'lasers_count', + type: 'custom_attribute', + value: Math.pow(2, 53) + 2, + }; + result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(invalidValueCondition2, getMockUserContext({ lasers_count: 9000 })); + + expect(result).toBe(null); + expect(mockLogger.warn).toHaveBeenCalledTimes(2); + expect(mockLogger.warn).toHaveBeenCalledWith(UNEXPECTED_CONDITION_VALUE, JSON.stringify(invalidValueCondition1)); + expect(mockLogger.warn).toHaveBeenCalledWith(UNEXPECTED_CONDITION_VALUE, JSON.stringify(invalidValueCondition2)); + }); +}); + +describe('exact match type - with a boolean condition value', () => { + const exactBoolCondition = { + match: 'exact', + name: 'did_register_user', + type: 'custom_attribute', + value: false, + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return true if the user-provided value is equal to the condition value', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactBoolCondition, getMockUserContext({ did_register_user: false })); + + expect(result).toBe(true); + }); + + it('should return false if the user-provided value is not equal to the condition value', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactBoolCondition, getMockUserContext({ did_register_user: true })); + + expect(result).toBe(false); + }); + + it('should return null if the user-provided value is of a different type than the condition value', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactBoolCondition, getMockUserContext({ did_register_user: 10 })); + + expect(result).toBe(null); + }); + + it('should return null if there is no user-provided value', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(exactBoolCondition, getMockUserContext({})); + + expect(result).toBe(null); + }); +}); + +describe('substring match type', () => { + const mockLogger = getMockLogger(); + const substringCondition = { + match: 'substring', + name: 'headline_text', + type: 'custom_attribute', + value: 'buy now', + }; + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return true if the condition value is a substring of the user-provided value', () => { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + substringCondition, + getMockUserContext({ + headline_text: 'Limited time, buy now!', + }) + ); + + expect(result).toBe(true); + }); + + it('should return false if the user-provided value is not a substring of the condition value', () => { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + substringCondition, + getMockUserContext({ + headline_text: 'Breaking news!', + }) + ); + + expect(result).toBe(false); + }); + + it('should log and return null if the user-provided value is not a string', () => { + const unexpectedTypeUserAttributes: Record<string, unknown> = { headline_text: 10 }; + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(substringCondition, getMockUserContext(unexpectedTypeUserAttributes)); + const userValue = unexpectedTypeUserAttributes[substringCondition.name]; + const userValueType = typeof userValue; + + expect(result).toBe(null); + expect(mockLogger.warn).toHaveBeenCalledTimes(1); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(substringCondition), + userValueType, + substringCondition.name + ); + }); + + it('should log and return null if the condition value is not a string', () => { + const nonStringCondition = { + match: 'substring', + name: 'headline_text', + type: 'custom_attribute', + value: 10, + }; + + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(nonStringCondition, getMockUserContext({ headline_text: 'hello' })); + + expect(result).toBe(null); + expect(mockLogger.warn).toHaveBeenCalledTimes(1); + expect(mockLogger.warn).toHaveBeenCalledWith(UNEXPECTED_CONDITION_VALUE, JSON.stringify(nonStringCondition)); + }); + + it('should log and return null if the user-provided value is null', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(substringCondition, getMockUserContext({ headline_text: null })); + + expect(result).toBe(null); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith( + UNEXPECTED_TYPE_NULL, + JSON.stringify(substringCondition), + substringCondition.name + ); + }); + + it('should return null if there is no user-provided value', function() { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(substringCondition, getMockUserContext({})); + + expect(result).toBe(null); + }); +}); + +describe('greater than match type', () => { + const gtCondition = { + match: 'gt', + name: 'meters_travelled', + type: 'custom_attribute', + value: 48.2, + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return true if the user-provided value is greater than the condition value', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(gtCondition, getMockUserContext({ meters_travelled: 58.4 })); + + expect(result).toBe(true); + }); + + it('should return false if the user-provided value is not greater than the condition value', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(gtCondition, getMockUserContext({ meters_travelled: 20 })); + + expect(result).toBe(false); + }); + + it('should log and return null if the user-provided value is not a number', () => { + const unexpectedTypeUserAttributes1 = { meters_travelled: 'a long way' }; + let result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(gtCondition, getMockUserContext(unexpectedTypeUserAttributes1)); + + expect(result).toBeNull(); + + const unexpectedTypeUserAttributes2 = { meters_travelled: '1000' }; + result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(gtCondition, getMockUserContext(unexpectedTypeUserAttributes2)); + + expect(result).toBeNull(); + expect(mockLogger.warn).toHaveBeenCalledTimes(2); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(gtCondition), + 'string', + gtCondition.name + ); + }); + + it('should log and return null if the user-provided number value is out of bounds', () => { + let result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(gtCondition, getMockUserContext({ meters_travelled: -Infinity })); + + expect(result).toBeNull(); + + result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(gtCondition, getMockUserContext({ meters_travelled: Math.pow(2, 53) + 2 })); + + expect(result).toBeNull(); + expect(mockLogger.warn).toHaveBeenCalledTimes(2); + expect(mockLogger.warn).toHaveBeenCalledWith(OUT_OF_BOUNDS, JSON.stringify(gtCondition), gtCondition.name); + }); + + it('should log and return null if the user-provided value is null', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(gtCondition, getMockUserContext({ meters_travelled: null })); + + expect(result).toBeNull(); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith(UNEXPECTED_TYPE_NULL, JSON.stringify(gtCondition), gtCondition.name); + }); + + it('should return null if there is no user-provided value', () => { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(gtCondition, getMockUserContext({})); + + expect(result).toBeNull(); + }); + + it('should return null if the condition value is not a finite number', () => { + const userAttributes = { meters_travelled: 58.4 }; + const invalidValueCondition: Condition = { + match: 'gt', + name: 'meters_travelled', + type: 'custom_attribute', + value: Infinity, + }; + let result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + + expect(result).toBeNull(); + + invalidValueCondition.value = null; + + result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + + expect(result).toBeNull(); + + invalidValueCondition.value = Math.pow(2, 53) + 2; + result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + + expect(result).toBeNull(); + expect(mockLogger.warn).toHaveBeenCalledTimes(3); + expect(mockLogger.warn).toHaveBeenCalledWith(UNEXPECTED_CONDITION_VALUE, JSON.stringify(invalidValueCondition)); + }); +}); + +describe('less than match type', () => { + const ltCondition = { + match: 'lt', + name: 'meters_travelled', + type: 'custom_attribute', + value: 48.2, + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return true if the user-provided value is less than the condition value', () => { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + ltCondition, + getMockUserContext({ + meters_travelled: 10, + }) + ); + + expect(result).toBe(true); + }); + + it('should return false if the user-provided value is not less than the condition value', () => { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + ltCondition, + getMockUserContext({ + meters_travelled: 64.64, + }) + ); + + expect(result).toBe(false); + }); + + it('should log and return null if the user-provided value is not a number', () => { + const unexpectedTypeUserAttributes1: Record<string, unknown> = { meters_travelled: true }; + let result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(ltCondition, getMockUserContext(unexpectedTypeUserAttributes1)); + + expect(result).toBeNull(); + + const unexpectedTypeUserAttributes2: Record<string, unknown> = { meters_travelled: '48.2' }; + result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(ltCondition, getMockUserContext(unexpectedTypeUserAttributes2)); + + expect(result).toBeNull(); + + const userValue1 = unexpectedTypeUserAttributes1[ltCondition.name]; + const userValueType1 = typeof userValue1; + const userValue2 = unexpectedTypeUserAttributes2[ltCondition.name]; + const userValueType2 = typeof userValue2; + + expect(mockLogger.warn).toHaveBeenCalledTimes(2); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(ltCondition), + userValueType1, + ltCondition.name + ); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(ltCondition), + userValueType2, + ltCondition.name + ); + }); + + it('should log and return null if the user-provided number value is out of bounds', () => { + let result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(ltCondition, getMockUserContext({ meters_travelled: Infinity })); + + expect(result).toBeNull(); + + result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(ltCondition, getMockUserContext({ meters_travelled: Math.pow(2, 53) + 2 })); + + expect(result).toBeNull(); + expect(mockLogger.warn).toHaveBeenCalledTimes(2); + expect(mockLogger.warn).toHaveBeenCalledWith(OUT_OF_BOUNDS, JSON.stringify(ltCondition), ltCondition.name); + expect(mockLogger.warn).toHaveBeenCalledWith(OUT_OF_BOUNDS, JSON.stringify(ltCondition), ltCondition.name); + }); + + it('should log and return null if the user-provided value is null', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(ltCondition, getMockUserContext({ meters_travelled: null })); + + expect(result).toBeNull(); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith(UNEXPECTED_TYPE_NULL, JSON.stringify(ltCondition), ltCondition.name); + }); + + it('should return null if there is no user-provided value', () => { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(ltCondition, getMockUserContext({})); + + expect(result).toBeNull(); + }); + + it('should return null if the condition value is not a finite number', () => { + const userAttributes = { meters_travelled: 10 }; + const invalidValueCondition: Condition = { + match: 'lt', + name: 'meters_travelled', + type: 'custom_attribute', + value: Infinity, + }; + + let result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + + expect(result).toBeNull(); + + invalidValueCondition.value = null; + result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + + expect(result).toBeNull(); + + invalidValueCondition.value = Math.pow(2, 53) + 2; + result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + + expect(result).toBeNull(); + expect(mockLogger.warn).toHaveBeenCalledTimes(3); + expect(mockLogger.warn).toHaveBeenCalledWith(UNEXPECTED_CONDITION_VALUE, JSON.stringify(invalidValueCondition)); + }); +}); + +describe('less than or equal match type', () => { + const leCondition = { + match: 'le', + name: 'meters_travelled', + type: 'custom_attribute', + value: 48.2, + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return false if the user-provided value is greater than the condition value', () => { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + leCondition, + getMockUserContext({ + meters_travelled: 48.3, + }) + ); + + expect(result).toBe(false); + }); + + it('should return true if the user-provided value is less than or equal to the condition value', () => { + const versions = [48, 48.2]; + for (const userValue of versions) { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + leCondition, + getMockUserContext({ + meters_travelled: userValue, + }) + ); + + expect(result).toBe(true); + } + }); +}); + +describe('greater than and equal to match type', () => { + const geCondition = { + match: 'ge', + name: 'meters_travelled', + type: 'custom_attribute', + value: 48.2, + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return false if the user-provided value is less than the condition value', () => { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + geCondition, + getMockUserContext({ + meters_travelled: 48, + }) + ); + + expect(result).toBe(false); + }); + + it('should return true if the user-provided value is less than or equal to the condition value', () => { + const versions = [100, 48.2]; + versions.forEach(userValue => { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + geCondition, + getMockUserContext({ + meters_travelled: userValue, + }) + ); + + expect(result).toBe(true); + }); + }); +}); + +describe('semver greater than match type', () => { + const semvergtCondition = { + match: 'semver_gt', + name: 'app_version', + type: 'custom_attribute', + value: '2.0.0', + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return true if the user-provided version is greater than the condition version', () => { + const versions = [['1.8.1', '1.9']]; + versions.forEach(([targetVersion, userVersion]) => { + const customSemvergtCondition = { + match: 'semver_gt', + name: 'app_version', + type: 'custom_attribute', + value: targetVersion, + }; + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + customSemvergtCondition, + getMockUserContext({ + app_version: userVersion, + }) + ); + + expect(result).toBe(true); + }); + }); + + it('should return false if the user-provided version is not greater than the condition version', function() { + const versions = [ + ['2.0.1', '2.0.1'], + ['2.0', '2.0.0'], + ['2.0', '2.0.1'], + ['2.0.1', '2.0.0'], + ]; + versions.forEach(([targetVersion, userVersion]) => { + const customSemvergtCondition = { + match: 'semver_gt', + name: 'app_version', + type: 'custom_attribute', + value: targetVersion, + }; + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + customSemvergtCondition, + getMockUserContext({ + app_version: userVersion, + }) + ); + + expect(result).toBe(false); + }); + }); + + it('should log and return null if the user-provided version is not a string', () => { + let result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + semvergtCondition, + getMockUserContext({ + app_version: 22, + }) + ); + + expect(result).toBe(null); + + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + semvergtCondition, + getMockUserContext({ + app_version: false, + }) + ); + + expect(result).toBe(null); + expect(mockLogger.warn).toHaveBeenCalledTimes(2); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(semvergtCondition), + 'number', + 'app_version' + ); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(semvergtCondition), + 'boolean', + 'app_version' + ); + }); + + it('should log and return null if the user-provided value is null', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(semvergtCondition, getMockUserContext({ app_version: null })); + + expect(result).toBe(null); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith( + UNEXPECTED_TYPE_NULL, + JSON.stringify(semvergtCondition), + 'app_version' + ); + }); + + it('should return null if there is no user-provided value', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(semvergtCondition, getMockUserContext({})); + + expect(result).toBe(null); + }); +}); + +describe('semver less than match type', () => { + const semverltCondition = { + match: 'semver_lt', + name: 'app_version', + type: 'custom_attribute', + value: '2.0.0', + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return false if the user-provided version is greater than the condition version', () => { + const versions = [ + ['2.0.0', '2.0.1'], + ['1.9', '2.0.0'], + ['2.0.0', '2.0.0'], + ]; + versions.forEach(([targetVersion, userVersion]) => { + const customSemverltCondition = { + match: 'semver_lt', + name: 'app_version', + type: 'custom_attribute', + value: targetVersion, + }; + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + customSemverltCondition, + getMockUserContext({ + app_version: userVersion, + }) + ); + + expect(result).toBe(false); + }); + }); + + it('should return true if the user-provided version is less than the condition version', () => { + const versions = [ + ['2.0.1', '2.0.0'], + ['2.0.0', '1.9'], + ]; + versions.forEach(([targetVersion, userVersion]) => { + const customSemverltCondition = { + match: 'semver_lt', + name: 'app_version', + type: 'custom_attribute', + value: targetVersion, + }; + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + customSemverltCondition, + getMockUserContext({ + app_version: userVersion, + }) + ); + + expect(result).toBe(true); + }); + }); + + it('should log and return null if the user-provided version is not a string', () => { + let result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + semverltCondition, + getMockUserContext({ + app_version: 22, + }) + ); + + expect(result).toBe(null); + + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + semverltCondition, + getMockUserContext({ + app_version: false, + }) + ); + + expect(result).toBe(null); + expect(mockLogger.warn).toHaveBeenCalledTimes(2); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(semverltCondition), + 'number', + 'app_version' + ); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(semverltCondition), + 'boolean', + 'app_version' + ); + }); + + it('should log and return null if the user-provided value is null', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(semverltCondition, getMockUserContext({ app_version: null })); + + expect(result).toBe(null); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith( + UNEXPECTED_TYPE_NULL, + JSON.stringify(semverltCondition), + 'app_version' + ); + }); + + it('should return null if there is no user-provided value', function() { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(semverltCondition, getMockUserContext({})); + + expect(result).toBe(null); + }); +}); +describe('semver equal to match type', () => { + const semvereqCondition = { + match: 'semver_eq', + name: 'app_version', + type: 'custom_attribute', + value: '2.0', + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return false if the user-provided version is greater than the condition version', () => { + const versions = [ + ['2.0.0', '2.0.1'], + ['2.0.1', '2.0.0'], + ['1.9.1', '1.9'], + ]; + versions.forEach(([targetVersion, userVersion]) => { + const customSemvereqCondition = { + match: 'semver_eq', + name: 'app_version', + type: 'custom_attribute', + value: targetVersion, + }; + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + customSemvereqCondition, + getMockUserContext({ + app_version: userVersion, + }) + ); + + expect(result).toBe(false); + }); + }); + + it('should return true if the user-provided version is equal to the condition version', () => { + const versions = [ + ['2.0.1', '2.0.1'], + ['1.9', '1.9.1'], + ]; + versions.forEach(([targetVersion, userVersion]) => { + const customSemvereqCondition = { + match: 'semver_eq', + name: 'app_version', + type: 'custom_attribute', + value: targetVersion, + }; + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + customSemvereqCondition, + getMockUserContext({ + app_version: userVersion, + }) + ); + + expect(result).toBe(true); + }); + }); + + it('should log and return null if the user-provided version is not a string', () => { + let result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + semvereqCondition, + getMockUserContext({ + app_version: 22, + }) + ); + + expect(result).toBe(null); + + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + semvereqCondition, + getMockUserContext({ + app_version: false, + }) + ); + + expect(result).toBe(null); + expect(mockLogger.warn).toHaveBeenCalledTimes(2); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(semvereqCondition), + 'number', + 'app_version' + ); + expect(mockLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_TYPE, + JSON.stringify(semvereqCondition), + 'boolean', + 'app_version' + ); + }); + + it('should log and return null if the user-provided value is null', () => { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(semvereqCondition, getMockUserContext({ app_version: null })); + + expect(result).toBe(null); + expect(mockLogger.debug).toHaveBeenCalledTimes(1); + expect(mockLogger.debug).toHaveBeenCalledWith( + UNEXPECTED_TYPE_NULL, + JSON.stringify(semvereqCondition), + 'app_version' + ); + }); + + it('should return null if there is no user-provided value', function() { + const result = customAttributeEvaluator + .getEvaluator(mockLogger) + .evaluate(semvereqCondition, getMockUserContext({})); + + expect(result).toBe(null); + }); +}); + +describe('semver less than or equal to match type', () => { + const semverleCondition = { + match: 'semver_le', + name: 'app_version', + type: 'custom_attribute', + value: '2.0.0', + }; + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return false if the user-provided version is greater than the condition version', () => { + const versions = [['2.0.0', '2.0.1']]; + versions.forEach(([targetVersion, userVersion]) => { + const customSemvereqCondition = { + match: 'semver_le', + name: 'app_version', + type: 'custom_attribute', + value: targetVersion, + }; + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + customSemvereqCondition, + getMockUserContext({ + app_version: userVersion, + }) + ); + + expect(result).toBe(false); + }); + }); + + it('should return true if the user-provided version is less than or equal to the condition version', () => { + const versions = [ + ['2.0.1', '2.0.0'], + ['2.0.1', '2.0.1'], + ['1.9', '1.9.1'], + ['1.9.1', '1.9'], + ]; + versions.forEach(([targetVersion, userVersion]) => { + const customSemvereqCondition = { + match: 'semver_le', + name: 'app_version', + type: 'custom_attribute', + value: targetVersion, + }; + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + customSemvereqCondition, + getMockUserContext({ + app_version: userVersion, + }) + ); + + expect(result).toBe(true); + }); + }); + + it('should return true if the user-provided version is equal to the condition version', () => { + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + semverleCondition, + getMockUserContext({ + app_version: '2.0', + }) + ); + + expect(result).toBe(true); + }); +}); + +describe('semver greater than or equal to match type', () => { + const mockLogger = getMockLogger(); + + beforeEach(() => { + setLogSpy(mockLogger); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return true if the user-provided version is greater than or equal to the condition version', () => { + const versions = [ + ['2.0.0', '2.0.1'], + ['2.0.1', '2.0.1'], + ['1.9', '1.9.1'], + ]; + versions.forEach(([targetVersion, userVersion]) => { + const customSemvereqCondition = { + match: 'semver_ge', + name: 'app_version', + type: 'custom_attribute', + value: targetVersion, + }; + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + customSemvereqCondition, + getMockUserContext({ + app_version: userVersion, + }) + ); + + expect(result).toBe(true); + }); + }); + + it('should return false if the user-provided version is less than the condition version', () => { + const versions = [ + ['2.0.1', '2.0.0'], + ['1.9.1', '1.9'], + ]; + versions.forEach(([targetVersion, userVersion]) => { + const customSemvereqCondition = { + match: 'semver_ge', + name: 'app_version', + type: 'custom_attribute', + value: targetVersion, + }; + const result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( + customSemvereqCondition, + getMockUserContext({ + app_version: userVersion, + }) + ); + + expect(result).toBe(false); + }); + }); +}); diff --git a/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.tests.js b/lib/core/custom_attribute_condition_evaluator/index.tests.js similarity index 57% rename from packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.tests.js rename to lib/core/custom_attribute_condition_evaluator/index.tests.js index b594cf898..12607e001 100644 --- a/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.tests.js +++ b/lib/core/custom_attribute_condition_evaluator/index.tests.js @@ -17,12 +17,17 @@ import sinon from 'sinon'; import { assert } from 'chai'; import { sprintf } from '../../utils/fns'; -import { - LOG_LEVEL, - LOG_MESSAGES, -} from '../../utils/enums'; -import * as logging from '../../modules/logging'; import * as customAttributeEvaluator from './'; +import { + MISSING_ATTRIBUTE_VALUE, + UNEXPECTED_TYPE_NULL, +} from 'log_message'; +import { + UNKNOWN_MATCH_TYPE, + UNEXPECTED_TYPE, + OUT_OF_BOUNDS, + UNEXPECTED_CONDITION_VALUE, +} from 'error_message'; var browserConditionSafari = { name: 'browser_type', @@ -49,19 +54,29 @@ var getMockUserContext = (attributes) => ({ getAttributes: () => ({ ... (attributes || {})}) }); +var createLogger = () => ({ + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + child: () => createLogger(), +}); + describe('lib/core/custom_attribute_condition_evaluator', function() { - var stubLogHandler; + var mockLogger = createLogger(); beforeEach(function() { - stubLogHandler = { - log: sinon.stub(), - }; - logging.setLogLevel('notset'); - logging.setLogHandler(stubLogHandler); + sinon.stub(mockLogger, 'error'); + sinon.stub(mockLogger, 'debug'); + sinon.stub(mockLogger, 'info'); + sinon.stub(mockLogger, 'warn'); }); afterEach(function() { - logging.resetLogger(); + mockLogger.error.restore(); + mockLogger.debug.restore(); + mockLogger.info.restore(); + mockLogger.warn.restore(); }); it('should return true when the attributes pass the audience conditions and no match type is provided', function() { @@ -69,7 +84,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { browser_type: 'safari', }; - assert.isTrue(customAttributeEvaluator.evaluate(browserConditionSafari, getMockUserContext(userAttributes))); + assert.isTrue(customAttributeEvaluator.getEvaluator().evaluate(browserConditionSafari, getMockUserContext(userAttributes))); }); it('should return false when the attributes do not pass the audience conditions and no match type is provided', function() { @@ -77,7 +92,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { browser_type: 'firefox', }; - assert.isFalse(customAttributeEvaluator.evaluate(browserConditionSafari, getMockUserContext(userAttributes))); + assert.isFalse(customAttributeEvaluator.getEvaluator().evaluate(browserConditionSafari, getMockUserContext(userAttributes))); }); it('should evaluate different typed attributes', function() { @@ -88,23 +103,22 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { pi_value: 3.14, }; - assert.isTrue(customAttributeEvaluator.evaluate(browserConditionSafari, getMockUserContext(userAttributes))); - assert.isTrue(customAttributeEvaluator.evaluate(booleanCondition, getMockUserContext(userAttributes))); - assert.isTrue(customAttributeEvaluator.evaluate(integerCondition, getMockUserContext(userAttributes))); - assert.isTrue(customAttributeEvaluator.evaluate(doubleCondition, getMockUserContext(userAttributes))); + assert.isTrue(customAttributeEvaluator.getEvaluator().evaluate(browserConditionSafari, getMockUserContext(userAttributes))); + assert.isTrue(customAttributeEvaluator.getEvaluator().evaluate(booleanCondition, getMockUserContext(userAttributes))); + assert.isTrue(customAttributeEvaluator.getEvaluator().evaluate(integerCondition, getMockUserContext(userAttributes))); + assert.isTrue(customAttributeEvaluator.getEvaluator().evaluate(doubleCondition, getMockUserContext(userAttributes))); }); it('should log and return null when condition has an invalid match property', function() { var invalidMatchCondition = { match: 'weird', name: 'weird_condition', type: 'custom_attribute', value: 'hi' }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( invalidMatchCondition, getMockUserContext({ weird_condition: 'bye' }) ); assert.isNull(result); - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.UNKNOWN_MATCH_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidMatchCondition))); + sinon.assert.calledOnce(mockLogger.warn); + assert.strictEqual(mockLogger.warn.args[0][0], UNKNOWN_MATCH_TYPE); + assert.strictEqual(mockLogger.warn.args[0][1], JSON.stringify(invalidMatchCondition)); }); describe('exists match type', function() { @@ -115,33 +129,36 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }; it('should return false if there is no user-provided value', function() { - var result = customAttributeEvaluator.evaluate(existsCondition, getMockUserContext({})); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(existsCondition, getMockUserContext({})); assert.isFalse(result); - sinon.assert.notCalled(stubLogHandler.log); + sinon.assert.notCalled(mockLogger.debug); + sinon.assert.notCalled(mockLogger.info); + sinon.assert.notCalled(mockLogger.warn); + sinon.assert.notCalled(mockLogger.error); }); it('should return false if the user-provided value is undefined', function() { - var result = customAttributeEvaluator.evaluate(existsCondition, getMockUserContext({ input_value: undefined })); + var result = customAttributeEvaluator.getEvaluator().evaluate(existsCondition, getMockUserContext({ input_value: undefined })); assert.isFalse(result); }); it('should return false if the user-provided value is null', function() { - var result = customAttributeEvaluator.evaluate(existsCondition, getMockUserContext({ input_value: null })); + var result = customAttributeEvaluator.getEvaluator().evaluate(existsCondition, getMockUserContext({ input_value: null })); assert.isFalse(result); }); it('should return true if the user-provided value is a string', function() { - var result = customAttributeEvaluator.evaluate(existsCondition, getMockUserContext({ input_value: 'hi' })); + var result = customAttributeEvaluator.getEvaluator().evaluate(existsCondition, getMockUserContext({ input_value: 'hi' })); assert.isTrue(result); }); it('should return true if the user-provided value is a number', function() { - var result = customAttributeEvaluator.evaluate(existsCondition, getMockUserContext({ input_value: 10 })); + var result = customAttributeEvaluator.getEvaluator().evaluate(existsCondition, getMockUserContext({ input_value: 10 })); assert.isTrue(result); }); it('should return true if the user-provided value is a boolean', function() { - var result = customAttributeEvaluator.evaluate(existsCondition, getMockUserContext({ input_value: true })); + var result = customAttributeEvaluator.getEvaluator().evaluate(existsCondition, getMockUserContext({ input_value: true })); assert.isTrue(result); }); }); @@ -156,7 +173,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }; it('should return true if the user-provided value is equal to the condition value', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator().evaluate( exactStringCondition, getMockUserContext({ favorite_constellation: 'Lacerta' }) ); @@ -164,7 +181,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }); it('should return false if the user-provided value is not equal to the condition value', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator().evaluate( exactStringCondition, getMockUserContext({ favorite_constellation: 'The Big Dipper' }) ); @@ -178,20 +195,19 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: [], }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( invalidExactCondition, getMockUserContext({ favorite_constellation: 'Lacerta' }) ); assert.isNull(result); - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidExactCondition))); + sinon.assert.calledOnce(mockLogger.warn); + assert.strictEqual(mockLogger.warn.args[0][0], UNEXPECTED_CONDITION_VALUE); + assert.strictEqual(mockLogger.warn.args[0][1], JSON.stringify(invalidExactCondition)); }); it('should log and return null if the user-provided value is of a different type than the condition value', function() { var unexpectedTypeUserAttributes = { favorite_constellation: false }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( exactStringCondition, getMockUserContext(unexpectedTypeUserAttributes) ); @@ -199,58 +215,42 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var userValue = unexpectedTypeUserAttributes[exactStringCondition.name]; var userValueType = typeof userValue; - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual( - logMessage, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactStringCondition), userValueType, exactStringCondition.name) - ); + sinon.assert.calledOnce(mockLogger.warn); + + assert.deepEqual(mockLogger.warn.args[0], [UNEXPECTED_TYPE, JSON.stringify(exactStringCondition), userValueType, exactStringCondition.name]); }); it('should log and return null if the user-provided value is null', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( exactStringCondition, getMockUserContext({ favorite_constellation: null }) ); assert.isNull(result); - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.DEBUG); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual( - logMessage, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE_NULL, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactStringCondition), exactStringCondition.name) - ); + sinon.assert.calledOnce(mockLogger.debug); + + assert.deepEqual(mockLogger.debug.args[0], [UNEXPECTED_TYPE_NULL, JSON.stringify(exactStringCondition), exactStringCondition.name]); }); it('should log and return null if there is no user-provided value', function() { - var result = customAttributeEvaluator.evaluate(exactStringCondition, getMockUserContext({})); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(exactStringCondition, getMockUserContext({})); assert.isNull(result); - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.DEBUG); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual( - logMessage, - sprintf(LOG_MESSAGES.MISSING_ATTRIBUTE_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactStringCondition), exactStringCondition.name) - ); + sinon.assert.calledOnce(mockLogger.debug); + + assert.deepEqual(mockLogger.debug.args[0], [MISSING_ATTRIBUTE_VALUE, JSON.stringify(exactStringCondition), exactStringCondition.name]); }); it('should log and return null if the user-provided value is of an unexpected type', function() { var unexpectedTypeUserAttributes = { favorite_constellation: [] }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( exactStringCondition, getMockUserContext(unexpectedTypeUserAttributes) ); assert.isNull(result); var userValue = unexpectedTypeUserAttributes[exactStringCondition.name]; var userValueType = typeof userValue; - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual( - logMessage, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactStringCondition), userValueType, exactStringCondition.name) - ); + sinon.assert.calledOnce(mockLogger.warn); + + assert.deepEqual(mockLogger.warn.args[0], [UNEXPECTED_TYPE, JSON.stringify(exactStringCondition), userValueType, exactStringCondition.name]); }); }); @@ -263,25 +263,25 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }; it('should return true if the user-provided value is equal to the condition value', function() { - var result = customAttributeEvaluator.evaluate(exactNumberCondition, getMockUserContext({ lasers_count: 9000 })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(exactNumberCondition, getMockUserContext({ lasers_count: 9000 })); assert.isTrue(result); }); it('should return false if the user-provided value is not equal to the condition value', function() { - var result = customAttributeEvaluator.evaluate(exactNumberCondition, getMockUserContext({ lasers_count: 8000 })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(exactNumberCondition, getMockUserContext({ lasers_count: 8000 })); assert.isFalse(result); }); it('should log and return null if the user-provided value is of a different type than the condition value', function() { var unexpectedTypeUserAttributes1 = { lasers_count: 'yes' }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( exactNumberCondition, getMockUserContext(unexpectedTypeUserAttributes1) ); assert.isNull(result); var unexpectedTypeUserAttributes2 = { lasers_count: '1000' }; - result = customAttributeEvaluator.evaluate( + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( exactNumberCondition, getMockUserContext(unexpectedTypeUserAttributes2) ); @@ -291,50 +291,31 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var userValueType1 = typeof userValue1; var userValue2 = unexpectedTypeUserAttributes2[exactNumberCondition.name]; var userValueType2 = typeof userValue2; - assert.strictEqual(2, stubLogHandler.log.callCount); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - assert.strictEqual(stubLogHandler.log.args[1][0], LOG_LEVEL.WARNING); - - var logMessage1 = stubLogHandler.log.args[0][1]; - var logMessage2 = stubLogHandler.log.args[1][1]; - assert.strictEqual( - logMessage1, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactNumberCondition), userValueType1, exactNumberCondition.name) - ); - assert.strictEqual( - logMessage2, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactNumberCondition), userValueType2, exactNumberCondition.name) - ); + assert.strictEqual(2, mockLogger.warn.callCount); + + assert.deepEqual(mockLogger.warn.args[0], [UNEXPECTED_TYPE, JSON.stringify(exactNumberCondition), userValueType1, exactNumberCondition.name]); + assert.deepEqual(mockLogger.warn.args[1], [UNEXPECTED_TYPE, JSON.stringify(exactNumberCondition), userValueType2, exactNumberCondition.name]); }); it('should log and return null if the user-provided number value is out of bounds', function() { - var result = customAttributeEvaluator.evaluate(exactNumberCondition, getMockUserContext({ lasers_count: -Infinity })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(exactNumberCondition, getMockUserContext({ lasers_count: -Infinity })); assert.isNull(result); - result = customAttributeEvaluator.evaluate( + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( exactNumberCondition, getMockUserContext({ lasers_count: -Math.pow(2, 53) - 2 }) ); assert.isNull(result); - assert.strictEqual(2, stubLogHandler.log.callCount); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - assert.strictEqual(stubLogHandler.log.args[1][0], LOG_LEVEL.WARNING); + assert.strictEqual(2, mockLogger.warn.callCount); - var logMessage1 = stubLogHandler.log.args[0][1]; - var logMessage2 = stubLogHandler.log.args[1][1]; - assert.strictEqual( - logMessage1, - sprintf(LOG_MESSAGES.OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactNumberCondition), exactNumberCondition.name) - ); - assert.strictEqual( - logMessage2, - sprintf(LOG_MESSAGES.OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(exactNumberCondition), exactNumberCondition.name) - ); + assert.deepEqual(mockLogger.warn.args[0], [OUT_OF_BOUNDS, JSON.stringify(exactNumberCondition), exactNumberCondition.name]); + + assert.deepEqual(mockLogger.warn.args[1], [OUT_OF_BOUNDS, JSON.stringify(exactNumberCondition), exactNumberCondition.name]); }); it('should return null if there is no user-provided value', function() { - var result = customAttributeEvaluator.evaluate(exactNumberCondition, getMockUserContext({})); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(exactNumberCondition, getMockUserContext({})); assert.isNull(result); }); @@ -345,7 +326,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: Infinity, }; - var result = customAttributeEvaluator.evaluate(invalidValueCondition1, getMockUserContext({ lasers_count: 9000 })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(invalidValueCondition1, getMockUserContext({ lasers_count: 9000 })); assert.isNull(result); var invalidValueCondition2 = { @@ -354,23 +335,14 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: Math.pow(2, 53) + 2, }; - result = customAttributeEvaluator.evaluate(invalidValueCondition2, getMockUserContext({ lasers_count: 9000 })); + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(invalidValueCondition2, getMockUserContext({ lasers_count: 9000 })); assert.isNull(result); - assert.strictEqual(2, stubLogHandler.log.callCount); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - assert.strictEqual(stubLogHandler.log.args[1][0], LOG_LEVEL.WARNING); + assert.strictEqual(2, mockLogger.warn.callCount); - var logMessage1 = stubLogHandler.log.args[0][1]; - var logMessage2 = stubLogHandler.log.args[1][1]; - assert.strictEqual( - logMessage1, - sprintf(LOG_MESSAGES.UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidValueCondition1)) - ); - assert.strictEqual( - logMessage2, - sprintf(LOG_MESSAGES.UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidValueCondition2)) - ); + assert.deepEqual(mockLogger.warn.args[0], [UNEXPECTED_CONDITION_VALUE, JSON.stringify(invalidValueCondition1)]); + + assert.deepEqual(mockLogger.warn.args[1], [UNEXPECTED_CONDITION_VALUE, JSON.stringify(invalidValueCondition2)]); }); }); @@ -383,22 +355,22 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }; it('should return true if the user-provided value is equal to the condition value', function() { - var result = customAttributeEvaluator.evaluate(exactBoolCondition, getMockUserContext({ did_register_user: false })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(exactBoolCondition, getMockUserContext({ did_register_user: false })); assert.isTrue(result); }); it('should return false if the user-provided value is not equal to the condition value', function() { - var result = customAttributeEvaluator.evaluate(exactBoolCondition, getMockUserContext({ did_register_user: true })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(exactBoolCondition, getMockUserContext({ did_register_user: true })); assert.isFalse(result); }); it('should return null if the user-provided value is of a different type than the condition value', function() { - var result = customAttributeEvaluator.evaluate(exactBoolCondition, getMockUserContext({ did_register_user: 10 })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(exactBoolCondition, getMockUserContext({ did_register_user: 10 })); assert.isNull(result); }); it('should return null if there is no user-provided value', function() { - var result = customAttributeEvaluator.evaluate(exactBoolCondition, getMockUserContext({})); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(exactBoolCondition, getMockUserContext({})); assert.isNull(result); }); }); @@ -413,7 +385,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }; it('should return true if the condition value is a substring of the user-provided value', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( substringCondition, getMockUserContext({ headline_text: 'Limited time, buy now!', @@ -423,7 +395,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }); it('should return false if the user-provided value is not a substring of the condition value', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( substringCondition, getMockUserContext({ headline_text: 'Breaking news!', @@ -434,20 +406,16 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { it('should log and return null if the user-provided value is not a string', function() { var unexpectedTypeUserAttributes = { headline_text: 10 }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( substringCondition, getMockUserContext(unexpectedTypeUserAttributes) ); assert.isNull(result); var userValue = unexpectedTypeUserAttributes[substringCondition.name]; var userValueType = typeof userValue; - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual( - logMessage, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(substringCondition), userValueType, substringCondition.name) - ); + sinon.assert.calledOnce(mockLogger.warn); + + assert.deepEqual(mockLogger.warn.args[0], [UNEXPECTED_TYPE, JSON.stringify(substringCondition), userValueType, substringCondition.name]); }); it('should log and return null if the condition value is not a string', function() { @@ -458,31 +426,23 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { value: 10, }; - var result = customAttributeEvaluator.evaluate(nonStringCondition, getMockUserContext({ headline_text: 'hello' })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(nonStringCondition, getMockUserContext({ headline_text: 'hello' })); assert.isNull(result); - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual( - logMessage, - sprintf(LOG_MESSAGES.UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(nonStringCondition)) - ); + sinon.assert.calledOnce(mockLogger.warn); + + assert.deepEqual(mockLogger.warn.args[0], [UNEXPECTED_CONDITION_VALUE, JSON.stringify(nonStringCondition)]); }); it('should log and return null if the user-provided value is null', function() { - var result = customAttributeEvaluator.evaluate(substringCondition, getMockUserContext({ headline_text: null })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(substringCondition, getMockUserContext({ headline_text: null })); assert.isNull(result); - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.DEBUG); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual( - logMessage, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE_NULL, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(substringCondition), substringCondition.name) - ); + sinon.assert.calledOnce(mockLogger.debug); + + assert.deepEqual(mockLogger.debug.args[0], [UNEXPECTED_TYPE_NULL, JSON.stringify(substringCondition), substringCondition.name]); }); it('should return null if there is no user-provided value', function() { - var result = customAttributeEvaluator.evaluate(substringCondition, getMockUserContext({})); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(substringCondition, getMockUserContext({})); assert.isNull(result); }); }); @@ -496,7 +456,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }; it('should return true if the user-provided value is greater than the condition value', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( gtCondition, getMockUserContext({ meters_travelled: 58.4, @@ -506,7 +466,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }); it('should return false if the user-provided value is not greater than the condition value', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( gtCondition, getMockUserContext({ meters_travelled: 20, @@ -517,14 +477,14 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { it('should log and return null if the user-provided value is not a number', function() { var unexpectedTypeUserAttributes1 = { meters_travelled: 'a long way' }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( gtCondition, getMockUserContext(unexpectedTypeUserAttributes1) ); assert.isNull(result); var unexpectedTypeUserAttributes2 = { meters_travelled: '1000' }; - result = customAttributeEvaluator.evaluate( + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( gtCondition, getMockUserContext(unexpectedTypeUserAttributes2) ); @@ -534,65 +494,43 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var userValueType1 = typeof userValue1; var userValue2 = unexpectedTypeUserAttributes2[gtCondition.name]; var userValueType2 = typeof userValue2; - assert.strictEqual(2, stubLogHandler.log.callCount); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - assert.strictEqual(stubLogHandler.log.args[1][0], LOG_LEVEL.WARNING); - - var logMessage1 = stubLogHandler.log.args[0][1]; - var logMessage2 = stubLogHandler.log.args[1][1]; - assert.strictEqual( - logMessage1, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(gtCondition), userValueType1, gtCondition.name) - ); - assert.strictEqual( - logMessage2, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(gtCondition), userValueType2, gtCondition.name) - ); + assert.strictEqual(2, mockLogger.warn.callCount); + + assert.deepEqual(mockLogger.warn.args[0], [UNEXPECTED_TYPE, JSON.stringify(gtCondition), userValueType1, gtCondition.name]); + + assert.deepEqual(mockLogger.warn.args[1], [UNEXPECTED_TYPE, JSON.stringify(gtCondition), userValueType2, gtCondition.name]); }); it('should log and return null if the user-provided number value is out of bounds', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( gtCondition, getMockUserContext({ meters_travelled: -Infinity }) ); assert.isNull(result); - result = customAttributeEvaluator.evaluate( + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( gtCondition, getMockUserContext({ meters_travelled: Math.pow(2, 53) + 2 }) ); assert.isNull(result); - assert.strictEqual(2, stubLogHandler.log.callCount); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - assert.strictEqual(stubLogHandler.log.args[1][0], LOG_LEVEL.WARNING); + assert.strictEqual(2, mockLogger.warn.callCount); - var logMessage1 = stubLogHandler.log.args[0][1]; - var logMessage2 = stubLogHandler.log.args[1][1]; - assert.strictEqual( - logMessage1, - sprintf(LOG_MESSAGES.OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(gtCondition), gtCondition.name) - ); - assert.strictEqual( - logMessage2, - sprintf(LOG_MESSAGES.OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(gtCondition), gtCondition.name) - ); + assert.deepEqual(mockLogger.warn.args[0], [OUT_OF_BOUNDS, JSON.stringify(gtCondition), gtCondition.name]); + + assert.deepEqual(mockLogger.warn.args[1], [OUT_OF_BOUNDS, JSON.stringify(gtCondition), gtCondition.name]); }); it('should log and return null if the user-provided value is null', function() { - var result = customAttributeEvaluator.evaluate(gtCondition, getMockUserContext({ meters_travelled: null })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(gtCondition, getMockUserContext({ meters_travelled: null })); assert.isNull(result); - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.DEBUG); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual( - logMessage, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE_NULL, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(gtCondition), gtCondition.name) - ); + sinon.assert.calledOnce(mockLogger.debug); + + assert.deepEqual(mockLogger.debug.args[0], [UNEXPECTED_TYPE_NULL, JSON.stringify(gtCondition), gtCondition.name]); }); it('should return null if there is no user-provided value', function() { - var result = customAttributeEvaluator.evaluate(gtCondition, getMockUserContext({})); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(gtCondition, getMockUserContext({})); assert.isNull(result); }); @@ -604,23 +542,20 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: Infinity, }; - var result = customAttributeEvaluator.evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(invalidValueCondition, getMockUserContext(userAttributes)); assert.isNull(result); invalidValueCondition.value = null; - result = customAttributeEvaluator.evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(invalidValueCondition, getMockUserContext(userAttributes)); assert.isNull(result); invalidValueCondition.value = Math.pow(2, 53) + 2; - result = customAttributeEvaluator.evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(invalidValueCondition, getMockUserContext(userAttributes)); assert.isNull(result); - sinon.assert.calledThrice(stubLogHandler.log); - var logMessage = stubLogHandler.log.args[2][1]; - assert.strictEqual( - logMessage, - sprintf(LOG_MESSAGES.UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidValueCondition)) - ); + sinon.assert.calledThrice(mockLogger.warn); + + assert.deepEqual(mockLogger.warn.args[2], [UNEXPECTED_CONDITION_VALUE, JSON.stringify(invalidValueCondition)]); }); }); @@ -633,7 +568,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }; it('should return true if the user-provided value is less than the condition value', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( ltCondition, getMockUserContext({ meters_travelled: 10, @@ -643,7 +578,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }); it('should return false if the user-provided value is not less than the condition value', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( ltCondition, getMockUserContext({ meters_travelled: 64.64, @@ -654,14 +589,14 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { it('should log and return null if the user-provided value is not a number', function() { var unexpectedTypeUserAttributes1 = { meters_travelled: true }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( ltCondition, getMockUserContext(unexpectedTypeUserAttributes1) ); assert.isNull(result); var unexpectedTypeUserAttributes2 = { meters_travelled: '48.2' }; - result = customAttributeEvaluator.evaluate( + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( ltCondition, getMockUserContext(unexpectedTypeUserAttributes2) ); @@ -671,24 +606,14 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { var userValueType1 = typeof userValue1; var userValue2 = unexpectedTypeUserAttributes2[ltCondition.name]; var userValueType2 = typeof userValue2; - assert.strictEqual(2, stubLogHandler.log.callCount); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - assert.strictEqual(stubLogHandler.log.args[1][0], LOG_LEVEL.WARNING); - - var logMessage1 = stubLogHandler.log.args[0][1]; - var logMessage2 = stubLogHandler.log.args[1][1]; - assert.strictEqual( - logMessage1, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(ltCondition), userValueType1, ltCondition.name) - ); - assert.strictEqual( - logMessage2, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(ltCondition), userValueType2, ltCondition.name) - ); + + assert.strictEqual(2, mockLogger.warn.callCount); + assert.deepEqual(mockLogger.warn.args[0], [UNEXPECTED_TYPE, JSON.stringify(ltCondition), userValueType1, ltCondition.name]); + assert.deepEqual(mockLogger.warn.args[1], [UNEXPECTED_TYPE, JSON.stringify(ltCondition), userValueType2, ltCondition.name]); }); it('should log and return null if the user-provided number value is out of bounds', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( ltCondition, getMockUserContext({ meters_travelled: Infinity, @@ -696,7 +621,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { ); assert.isNull(result); - result = customAttributeEvaluator.evaluate( + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( ltCondition, getMockUserContext({ meters_travelled: Math.pow(2, 53) + 2, @@ -704,36 +629,23 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { ); assert.isNull(result); - assert.strictEqual(2, stubLogHandler.log.callCount); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.WARNING); - assert.strictEqual(stubLogHandler.log.args[1][0], LOG_LEVEL.WARNING); + assert.strictEqual(2, mockLogger.warn.callCount); - var logMessage1 = stubLogHandler.log.args[0][1]; - var logMessage2 = stubLogHandler.log.args[1][1]; - assert.strictEqual( - logMessage1, - sprintf(LOG_MESSAGES.OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(ltCondition), ltCondition.name) - ); - assert.strictEqual( - logMessage2, - sprintf(LOG_MESSAGES.OUT_OF_BOUNDS, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(ltCondition), ltCondition.name) - ); + assert.deepEqual(mockLogger.warn.args[0], [OUT_OF_BOUNDS, JSON.stringify(ltCondition), ltCondition.name]); + + assert.deepEqual(mockLogger.warn.args[1], [OUT_OF_BOUNDS, JSON.stringify(ltCondition), ltCondition.name]); }); it('should log and return null if the user-provided value is null', function() { - var result = customAttributeEvaluator.evaluate(ltCondition, getMockUserContext({ meters_travelled: null })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(ltCondition, getMockUserContext({ meters_travelled: null })); assert.isNull(result); - sinon.assert.calledOnce(stubLogHandler.log); - assert.strictEqual(stubLogHandler.log.args[0][0], LOG_LEVEL.DEBUG); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual( - logMessage, - sprintf(LOG_MESSAGES.UNEXPECTED_TYPE_NULL, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(ltCondition), ltCondition.name) - ); + sinon.assert.calledOnce(mockLogger.debug); + + assert.deepEqual(mockLogger.debug.args[0], [UNEXPECTED_TYPE_NULL, JSON.stringify(ltCondition), ltCondition.name]); }); it('should return null if there is no user-provided value', function() { - var result = customAttributeEvaluator.evaluate(ltCondition, getMockUserContext({})); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(ltCondition, getMockUserContext({})); assert.isNull(result); }); @@ -745,23 +657,19 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: Infinity, }; - var result = customAttributeEvaluator.evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(invalidValueCondition, getMockUserContext(userAttributes)); assert.isNull(result); invalidValueCondition.value = {}; - result = customAttributeEvaluator.evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(invalidValueCondition, getMockUserContext(userAttributes)); assert.isNull(result); invalidValueCondition.value = Math.pow(2, 53) + 2; - result = customAttributeEvaluator.evaluate(invalidValueCondition, getMockUserContext(userAttributes)); + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(invalidValueCondition, getMockUserContext(userAttributes)); assert.isNull(result); - sinon.assert.calledThrice(stubLogHandler.log); - var logMessage = stubLogHandler.log.args[2][1]; - assert.strictEqual( - logMessage, - sprintf(LOG_MESSAGES.UNEXPECTED_CONDITION_VALUE, 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR', JSON.stringify(invalidValueCondition)) - ); + sinon.assert.calledThrice(mockLogger.warn); + assert.deepEqual(mockLogger.warn.args[2], [UNEXPECTED_CONDITION_VALUE, JSON.stringify(invalidValueCondition)]); }); }); describe('less than or equal to match type', function() { @@ -773,7 +681,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }; it('should return false if the user-provided value is greater than the condition value', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( leCondition, getMockUserContext({ meters_travelled: 48.3, @@ -785,7 +693,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { it('should return true if the user-provided value is less than or equal to the condition value', function() { var versions = [48, 48.2]; for (let userValue of versions) { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( leCondition, getMockUserContext({ meters_travelled: userValue, @@ -806,7 +714,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }; it('should return false if the user-provided value is less than the condition value', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( geCondition, getMockUserContext({ meters_travelled: 48, @@ -818,7 +726,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { it('should return true if the user-provided value is less than or equal to the condition value', function() { var versions = [100, 48.2]; for (let userValue of versions) { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( geCondition, getMockUserContext({ meters_travelled: userValue, @@ -848,7 +756,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: targetVersion, }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( customSemvergtCondition, getMockUserContext({ app_version: userVersion, @@ -872,7 +780,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: targetVersion, }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( customSemvergtCondition, getMockUserContext({ app_version: userVersion, @@ -883,7 +791,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }); it('should log and return null if the user-provided version is not a string', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( semvergtCondition, getMockUserContext({ app_version: 22, @@ -891,7 +799,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { ); assert.isNull(result); - result = customAttributeEvaluator.evaluate( + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( semvergtCondition, getMockUserContext({ app_version: false, @@ -899,30 +807,22 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { ); assert.isNull(result); - assert.strictEqual(2, stubLogHandler.log.callCount); - assert.strictEqual( - stubLogHandler.log.args[0][1], - 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"semver_gt","name":"app_version","type":"custom_attribute","value":"2.0.0"} evaluated to UNKNOWN because a value of type "number" was passed for user attribute "app_version".' - ); - assert.strictEqual( - stubLogHandler.log.args[1][1], - 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"semver_gt","name":"app_version","type":"custom_attribute","value":"2.0.0"} evaluated to UNKNOWN because a value of type "boolean" was passed for user attribute "app_version".' - ); + assert.strictEqual(2, mockLogger.warn.callCount); + assert.deepEqual(mockLogger.warn.args[0], [UNEXPECTED_TYPE, JSON.stringify(semvergtCondition), 'number', 'app_version']); + + assert.deepEqual(mockLogger.warn.args[1], [UNEXPECTED_TYPE, JSON.stringify(semvergtCondition), 'boolean', 'app_version']); }); it('should log and return null if the user-provided value is null', function() { - var result = customAttributeEvaluator.evaluate(semvergtCondition, getMockUserContext({ app_version: null })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(semvergtCondition, getMockUserContext({ app_version: null })); assert.isNull(result); - sinon.assert.calledOnce(stubLogHandler.log); - sinon.assert.calledWithExactly( - stubLogHandler.log, - LOG_LEVEL.DEBUG, - 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"semver_gt","name":"app_version","type":"custom_attribute","value":"2.0.0"} evaluated to UNKNOWN because a null value was passed for user attribute "app_version".' - ); + sinon.assert.calledOnce(mockLogger.debug); + + assert.deepEqual(mockLogger.debug.args[0], [UNEXPECTED_TYPE_NULL, JSON.stringify(semvergtCondition), 'app_version']); }); it('should return null if there is no user-provided value', function() { - var result = customAttributeEvaluator.evaluate(semvergtCondition, getMockUserContext({})); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(semvergtCondition, getMockUserContext({})); assert.isNull(result); }); }); @@ -949,7 +849,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: targetVersion, }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( customSemverltCondition, getMockUserContext({ app_version: userVersion, @@ -971,7 +871,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: targetVersion, }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( customSemverltCondition, getMockUserContext({ app_version: userVersion, @@ -982,7 +882,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }); it('should log and return null if the user-provided version is not a string', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( semverltCondition, getMockUserContext({ app_version: 22, @@ -990,7 +890,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { ); assert.isNull(result); - result = customAttributeEvaluator.evaluate( + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( semverltCondition, getMockUserContext({ app_version: false, @@ -998,30 +898,21 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { ); assert.isNull(result); - assert.strictEqual(2, stubLogHandler.log.callCount); - assert.strictEqual( - stubLogHandler.log.args[0][1], - 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"semver_lt","name":"app_version","type":"custom_attribute","value":"2.0.0"} evaluated to UNKNOWN because a value of type "number" was passed for user attribute "app_version".' - ); - assert.strictEqual( - stubLogHandler.log.args[1][1], - 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"semver_lt","name":"app_version","type":"custom_attribute","value":"2.0.0"} evaluated to UNKNOWN because a value of type "boolean" was passed for user attribute "app_version".' - ); + assert.strictEqual(2, mockLogger.warn.callCount); + + assert.deepEqual(mockLogger.warn.args[0], [UNEXPECTED_TYPE, JSON.stringify(semverltCondition), 'number', 'app_version']); + assert.deepEqual(mockLogger.warn.args[1], [UNEXPECTED_TYPE, JSON.stringify(semverltCondition), 'boolean', 'app_version']); }); it('should log and return null if the user-provided value is null', function() { - var result = customAttributeEvaluator.evaluate(semverltCondition, getMockUserContext({ app_version: null })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(semverltCondition, getMockUserContext({ app_version: null })); assert.isNull(result); - sinon.assert.calledOnce(stubLogHandler.log); - sinon.assert.calledWithExactly( - stubLogHandler.log, - LOG_LEVEL.DEBUG, - 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"semver_lt","name":"app_version","type":"custom_attribute","value":"2.0.0"} evaluated to UNKNOWN because a null value was passed for user attribute "app_version".' - ); + sinon.assert.calledOnce(mockLogger.debug); + assert.deepEqual(mockLogger.debug.args[0], [UNEXPECTED_TYPE_NULL, JSON.stringify(semverltCondition), 'app_version']); }); it('should return null if there is no user-provided value', function() { - var result = customAttributeEvaluator.evaluate(semverltCondition, getMockUserContext({})); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(semverltCondition, getMockUserContext({})); assert.isNull(result); }); }); @@ -1047,7 +938,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: targetVersion, }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( customSemvereqCondition, getMockUserContext({ app_version: userVersion, @@ -1069,7 +960,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: targetVersion, }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( customSemvereqCondition, getMockUserContext({ app_version: userVersion, @@ -1080,7 +971,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }); it('should log and return null if the user-provided version is not a string', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( semvereqCondition, getMockUserContext({ app_version: 22, @@ -1088,7 +979,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { ); assert.isNull(result); - result = customAttributeEvaluator.evaluate( + result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( semvereqCondition, getMockUserContext({ app_version: false, @@ -1096,30 +987,22 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { ); assert.isNull(result); - assert.strictEqual(2, stubLogHandler.log.callCount); - assert.strictEqual( - stubLogHandler.log.args[0][1], - 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"semver_eq","name":"app_version","type":"custom_attribute","value":"2.0"} evaluated to UNKNOWN because a value of type "number" was passed for user attribute "app_version".' - ); - assert.strictEqual( - stubLogHandler.log.args[1][1], - 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"semver_eq","name":"app_version","type":"custom_attribute","value":"2.0"} evaluated to UNKNOWN because a value of type "boolean" was passed for user attribute "app_version".' - ); + assert.strictEqual(2, mockLogger.warn.callCount); + assert.deepEqual(mockLogger.warn.args[0], [UNEXPECTED_TYPE, JSON.stringify(semvereqCondition), 'number', 'app_version']); + assert.deepEqual(mockLogger.warn.args[1], [UNEXPECTED_TYPE, JSON.stringify(semvereqCondition), 'boolean', 'app_version']); }); it('should log and return null if the user-provided value is null', function() { - var result = customAttributeEvaluator.evaluate(semvereqCondition, getMockUserContext({ app_version: null })); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(semvereqCondition, getMockUserContext({ app_version: null })); assert.isNull(result); - sinon.assert.calledOnce(stubLogHandler.log); - sinon.assert.calledWithExactly( - stubLogHandler.log, - LOG_LEVEL.DEBUG, - 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR: Audience condition {"match":"semver_eq","name":"app_version","type":"custom_attribute","value":"2.0"} evaluated to UNKNOWN because a null value was passed for user attribute "app_version".' - ); + sinon.assert.calledOnce(mockLogger.debug); + + assert.strictEqual(mockLogger.debug.args[0][0], UNEXPECTED_TYPE_NULL); + assert.strictEqual(mockLogger.debug.args[0][1], JSON.stringify(semvereqCondition)); }); it('should return null if there is no user-provided value', function() { - var result = customAttributeEvaluator.evaluate(semvereqCondition, getMockUserContext({})); + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate(semvereqCondition, getMockUserContext({})); assert.isNull(result); }); }); @@ -1143,7 +1026,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: targetVersion, }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( customSemvereqCondition, getMockUserContext({ app_version: userVersion, @@ -1166,7 +1049,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: targetVersion, }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( customSemvereqCondition, getMockUserContext({ app_version: userVersion, @@ -1177,7 +1060,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { }); it('should return true if the user-provided version is equal to the condition version', function() { - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( semverleCondition, getMockUserContext({ app_version: '2.0', @@ -1208,7 +1091,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: targetVersion, }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( customSemvereqCondition, getMockUserContext({ app_version: userVersion, @@ -1230,7 +1113,7 @@ describe('lib/core/custom_attribute_condition_evaluator', function() { type: 'custom_attribute', value: targetVersion, }; - var result = customAttributeEvaluator.evaluate( + var result = customAttributeEvaluator.getEvaluator(mockLogger).evaluate( customSemvereqCondition, getMockUserContext({ app_version: userVersion, diff --git a/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.ts b/lib/core/custom_attribute_condition_evaluator/index.ts similarity index 80% rename from packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.ts rename to lib/core/custom_attribute_condition_evaluator/index.ts index 775be8ad7..797a7d4e0 100644 --- a/packages/optimizely-sdk/lib/core/custom_attribute_condition_evaluator/index.ts +++ b/lib/core/custom_attribute_condition_evaluator/index.ts @@ -13,16 +13,21 @@ * See the License for the specific language governing permissions and * * limitations under the License. * ***************************************************************************/ -import { getLogger } from '../../modules/logging'; import { Condition, OptimizelyUserContext } from '../../shared_types'; import fns from '../../utils/fns'; -import { LOG_MESSAGES } from '../../utils/enums'; import { compareVersion } from '../../utils/semantic_version'; - -const MODULE_NAME = 'CUSTOM_ATTRIBUTE_CONDITION_EVALUATOR'; - -const logger = getLogger(); +import { + MISSING_ATTRIBUTE_VALUE, + UNEXPECTED_TYPE_NULL, +} from 'log_message'; +import { + OUT_OF_BOUNDS, + UNEXPECTED_TYPE, + UNEXPECTED_CONDITION_VALUE, + UNKNOWN_MATCH_TYPE +} from 'error_message'; +import { LoggerFacade } from '../../logging/logger'; const EXACT_MATCH_TYPE = 'exact'; const EXISTS_MATCH_TYPE = 'exists'; @@ -52,7 +57,8 @@ const MATCH_TYPES = [ SEMVER_GREATER_OR_EQUAL_THAN_MATCH_TYPE ]; -type ConditionEvaluator = (condition: Condition, user: OptimizelyUserContext) => boolean | null; +type ConditionEvaluator = (condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade) => boolean | null; +type Evaluator = { evaluate: (condition: Condition, user: OptimizelyUserContext) => boolean | null; } const EVALUATORS_BY_MATCH_TYPE: { [conditionType: string]: ConditionEvaluator | undefined } = {}; EVALUATORS_BY_MATCH_TYPE[EXACT_MATCH_TYPE] = exactEvaluator; @@ -68,6 +74,14 @@ EVALUATORS_BY_MATCH_TYPE[SEMVER_GREATER_OR_EQUAL_THAN_MATCH_TYPE] = semverGreate EVALUATORS_BY_MATCH_TYPE[SEMVER_LESS_THAN_MATCH_TYPE] = semverLessThanEvaluator; EVALUATORS_BY_MATCH_TYPE[SEMVER_LESS_OR_EQUAL_THAN_MATCH_TYPE] = semverLessThanOrEqualEvaluator; +export const getEvaluator = (logger?: LoggerFacade): Evaluator => { + return { + evaluate(condition: Condition, user: OptimizelyUserContext): boolean | null { + return evaluate(condition, user, logger); + } + }; +} + /** * Given a custom attribute audience condition and user attributes, evaluate the * condition against the attributes. @@ -77,18 +91,18 @@ EVALUATORS_BY_MATCH_TYPE[SEMVER_LESS_OR_EQUAL_THAN_MATCH_TYPE] = semverLessThanO * null if the given user attributes and condition can't be evaluated * TODO: Change to accept and object with named properties */ -export function evaluate(condition: Condition, user: OptimizelyUserContext): boolean | null { +function evaluate(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { const userAttributes = user.getAttributes(); const conditionMatch = condition.match; if (typeof conditionMatch !== 'undefined' && MATCH_TYPES.indexOf(conditionMatch) === -1) { - logger.warn(LOG_MESSAGES.UNKNOWN_MATCH_TYPE, MODULE_NAME, JSON.stringify(condition)); + logger?.warn(UNKNOWN_MATCH_TYPE, JSON.stringify(condition)); return null; } const attributeKey = condition.name; if (!userAttributes.hasOwnProperty(attributeKey) && conditionMatch != EXISTS_MATCH_TYPE) { - logger.debug( - LOG_MESSAGES.MISSING_ATTRIBUTE_VALUE, MODULE_NAME, JSON.stringify(condition), attributeKey + logger?.debug( + MISSING_ATTRIBUTE_VALUE, JSON.stringify(condition), attributeKey ); return null; } @@ -100,7 +114,7 @@ export function evaluate(condition: Condition, user: OptimizelyUserContext): boo evaluatorForMatch = EVALUATORS_BY_MATCH_TYPE[conditionMatch] || exactEvaluator; } - return evaluatorForMatch(condition, user); + return evaluatorForMatch(condition, user, logger); } /** @@ -123,7 +137,7 @@ function isValueTypeValidForExactConditions(value: unknown): boolean { * if there is a mismatch between the user attribute type and the condition value * type */ -function exactEvaluator(condition: Condition, user: OptimizelyUserContext): boolean | null { +function exactEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { const userAttributes = user.getAttributes(); const conditionValue = condition.value; const conditionValueType = typeof conditionValue; @@ -135,29 +149,29 @@ function exactEvaluator(condition: Condition, user: OptimizelyUserContext): bool !isValueTypeValidForExactConditions(conditionValue) || (fns.isNumber(conditionValue) && !fns.isSafeInteger(conditionValue)) ) { - logger.warn( - LOG_MESSAGES.UNEXPECTED_CONDITION_VALUE, MODULE_NAME, JSON.stringify(condition) + logger?.warn( + UNEXPECTED_CONDITION_VALUE, JSON.stringify(condition) ); return null; } if (userValue === null) { - logger.debug( - LOG_MESSAGES.UNEXPECTED_TYPE_NULL, MODULE_NAME, JSON.stringify(condition), conditionName + logger?.debug( + UNEXPECTED_TYPE_NULL, JSON.stringify(condition), conditionName ); return null; } if (!isValueTypeValidForExactConditions(userValue) || conditionValueType !== userValueType) { - logger.warn( - LOG_MESSAGES.UNEXPECTED_TYPE, MODULE_NAME, JSON.stringify(condition), userValueType, conditionName + logger?.warn( + UNEXPECTED_TYPE, JSON.stringify(condition), userValueType, conditionName ); return null; } if (fns.isNumber(userValue) && !fns.isSafeInteger(userValue)) { - logger.warn( - LOG_MESSAGES.OUT_OF_BOUNDS, MODULE_NAME, JSON.stringify(condition), conditionName + logger?.warn( + OUT_OF_BOUNDS, JSON.stringify(condition), conditionName ); return null; } @@ -174,7 +188,7 @@ function exactEvaluator(condition: Condition, user: OptimizelyUserContext): bool * 2) the user attribute value is neither null nor undefined * Returns false otherwise */ -function existsEvaluator(condition: Condition, user: OptimizelyUserContext): boolean { +function existsEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean { const userAttributes = user.getAttributes(); const userValue = userAttributes[condition.name]; return typeof userValue !== 'undefined' && userValue !== null; @@ -187,7 +201,7 @@ function existsEvaluator(condition: Condition, user: OptimizelyUserContext): boo * @returns {?boolean} true if values are valid, * false if values are not valid */ -function validateValuesForNumericCondition(condition: Condition, user: OptimizelyUserContext): boolean { +function validateValuesForNumericCondition(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean { const userAttributes = user.getAttributes(); const conditionName = condition.name; const userValue = userAttributes[conditionName]; @@ -195,29 +209,29 @@ function validateValuesForNumericCondition(condition: Condition, user: Optimizel const conditionValue = condition.value; if (conditionValue === null || !fns.isSafeInteger(conditionValue)) { - logger.warn( - LOG_MESSAGES.UNEXPECTED_CONDITION_VALUE, MODULE_NAME, JSON.stringify(condition) + logger?.warn( + UNEXPECTED_CONDITION_VALUE, JSON.stringify(condition) ); return false; } if (userValue === null) { - logger.debug( - LOG_MESSAGES.UNEXPECTED_TYPE_NULL, MODULE_NAME, JSON.stringify(condition), conditionName + logger?.debug( + UNEXPECTED_TYPE_NULL, JSON.stringify(condition), conditionName ); return false; } if (!fns.isNumber(userValue)) { - logger.warn( - LOG_MESSAGES.UNEXPECTED_TYPE, MODULE_NAME, JSON.stringify(condition), userValueType, conditionName + logger?.warn( + UNEXPECTED_TYPE, JSON.stringify(condition), userValueType, conditionName ); return false; } if (!fns.isSafeInteger(userValue)) { - logger.warn( - LOG_MESSAGES.OUT_OF_BOUNDS, MODULE_NAME, JSON.stringify(condition), conditionName + logger?.warn( + OUT_OF_BOUNDS, JSON.stringify(condition), conditionName ); return false; } @@ -233,15 +247,15 @@ function validateValuesForNumericCondition(condition: Condition, user: Optimizel * null if the condition value isn't a number or the user attribute value * isn't a number */ -function greaterThanEvaluator(condition: Condition, user: OptimizelyUserContext): boolean | null { +function greaterThanEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { const userAttributes = user.getAttributes(); const userValue = userAttributes[condition.name]; const conditionValue = condition.value; - if (!validateValuesForNumericCondition(condition, user) || conditionValue === null) { + if (!validateValuesForNumericCondition(condition, user, logger) || conditionValue === null) { return null; } - return userValue > conditionValue; + return userValue! > conditionValue; } /** @@ -253,16 +267,16 @@ function greaterThanEvaluator(condition: Condition, user: OptimizelyUserContext) * null if the condition value isn't a number or the user attribute value isn't a * number */ -function greaterThanOrEqualEvaluator(condition: Condition, user: OptimizelyUserContext): boolean | null { +function greaterThanOrEqualEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { const userAttributes = user.getAttributes(); const userValue = userAttributes[condition.name]; const conditionValue = condition.value; - if (!validateValuesForNumericCondition(condition, user) || conditionValue === null) { + if (!validateValuesForNumericCondition(condition, user, logger) || conditionValue === null) { return null; } - return userValue >= conditionValue; + return userValue! >= conditionValue; } /** @@ -274,16 +288,16 @@ function greaterThanOrEqualEvaluator(condition: Condition, user: OptimizelyUserC * null if the condition value isn't a number or the user attribute value isn't a * number */ -function lessThanEvaluator(condition: Condition, user: OptimizelyUserContext): boolean | null { +function lessThanEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { const userAttributes = user.getAttributes(); const userValue = userAttributes[condition.name]; const conditionValue = condition.value; - if (!validateValuesForNumericCondition(condition, user) || conditionValue === null) { + if (!validateValuesForNumericCondition(condition, user, logger) || conditionValue === null) { return null; } - return userValue < conditionValue; + return userValue! < conditionValue; } /** @@ -295,16 +309,16 @@ function lessThanEvaluator(condition: Condition, user: OptimizelyUserContext): b * null if the condition value isn't a number or the user attribute value isn't a * number */ -function lessThanOrEqualEvaluator(condition: Condition, user: OptimizelyUserContext): boolean | null { +function lessThanOrEqualEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { const userAttributes = user.getAttributes(); const userValue = userAttributes[condition.name]; const conditionValue = condition.value; - if (!validateValuesForNumericCondition(condition, user) || conditionValue === null) { + if (!validateValuesForNumericCondition(condition, user, logger) || conditionValue === null) { return null; } - return userValue <= conditionValue; + return userValue! <= conditionValue; } /** @@ -316,7 +330,7 @@ function lessThanOrEqualEvaluator(condition: Condition, user: OptimizelyUserCont * null if the condition value isn't a string or the user attribute value * isn't a string */ -function substringEvaluator(condition: Condition, user: OptimizelyUserContext): boolean | null { +function substringEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { const userAttributes = user.getAttributes(); const conditionName = condition.name; const userValue = userAttributes[condition.name]; @@ -324,22 +338,22 @@ function substringEvaluator(condition: Condition, user: OptimizelyUserContext): const conditionValue = condition.value; if (typeof conditionValue !== 'string') { - logger.warn( - LOG_MESSAGES.UNEXPECTED_CONDITION_VALUE, MODULE_NAME, JSON.stringify(condition) + logger?.warn( + UNEXPECTED_CONDITION_VALUE, JSON.stringify(condition) ); return null; } if (userValue === null) { - logger.debug( - LOG_MESSAGES.UNEXPECTED_TYPE_NULL, MODULE_NAME, JSON.stringify(condition), conditionName + logger?.debug( + UNEXPECTED_TYPE_NULL, JSON.stringify(condition), conditionName ); return null; } if (typeof userValue !== 'string') { - logger.warn( - LOG_MESSAGES.UNEXPECTED_TYPE, MODULE_NAME, JSON.stringify(condition), userValueType, conditionName + logger?.warn( + UNEXPECTED_TYPE, JSON.stringify(condition), userValueType, conditionName ); return null; } @@ -354,7 +368,7 @@ function substringEvaluator(condition: Condition, user: OptimizelyUserContext): * @returns {?number} returns compareVersion result * null if the user attribute version has an invalid type */ -function evaluateSemanticVersion(condition: Condition, user: OptimizelyUserContext): number | null { +function evaluateSemanticVersion(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): number | null { const userAttributes = user.getAttributes(); const conditionName = condition.name; const userValue = userAttributes[conditionName]; @@ -362,27 +376,27 @@ function evaluateSemanticVersion(condition: Condition, user: OptimizelyUserConte const conditionValue = condition.value; if (typeof conditionValue !== 'string') { - logger.warn( - LOG_MESSAGES.UNEXPECTED_CONDITION_VALUE, MODULE_NAME, JSON.stringify(condition) + logger?.warn( + UNEXPECTED_CONDITION_VALUE, JSON.stringify(condition) ); return null; } if (userValue === null) { - logger.debug( - LOG_MESSAGES.UNEXPECTED_TYPE_NULL, MODULE_NAME, JSON.stringify(condition), conditionName + logger?.debug( + UNEXPECTED_TYPE_NULL, JSON.stringify(condition), conditionName ); return null; } if (typeof userValue !== 'string') { - logger.warn( - LOG_MESSAGES.UNEXPECTED_TYPE, MODULE_NAME, JSON.stringify(condition), userValueType, conditionName + logger?.warn( + UNEXPECTED_TYPE, JSON.stringify(condition), userValueType, conditionName ); return null; } - return compareVersion(conditionValue, userValue); + return compareVersion(conditionValue, userValue, logger); } /** @@ -393,8 +407,8 @@ function evaluateSemanticVersion(condition: Condition, user: OptimizelyUserConte * false if the user attribute version is not equal (!==) to the condition version, * null if the user attribute version has an invalid type */ -function semverEqualEvaluator(condition: Condition, user: OptimizelyUserContext): boolean | null { - const result = evaluateSemanticVersion(condition, user); +function semverEqualEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { + const result = evaluateSemanticVersion(condition, user, logger); if (result === null) { return null; } @@ -409,8 +423,8 @@ function semverEqualEvaluator(condition: Condition, user: OptimizelyUserContext) * false if the user attribute version is not greater than the condition version, * null if the user attribute version has an invalid type */ -function semverGreaterThanEvaluator(condition: Condition, user: OptimizelyUserContext): boolean | null { - const result = evaluateSemanticVersion(condition, user); +function semverGreaterThanEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { + const result = evaluateSemanticVersion(condition, user, logger); if (result === null) { return null; } @@ -425,8 +439,8 @@ function semverGreaterThanEvaluator(condition: Condition, user: OptimizelyUserCo * false if the user attribute version is not less than the condition version, * null if the user attribute version has an invalid type */ -function semverLessThanEvaluator(condition: Condition, user: OptimizelyUserContext): boolean | null { - const result = evaluateSemanticVersion(condition, user); +function semverLessThanEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { + const result = evaluateSemanticVersion(condition, user, logger); if (result === null) { return null; } @@ -441,8 +455,8 @@ function semverLessThanEvaluator(condition: Condition, user: OptimizelyUserConte * false if the user attribute version is not greater than or equal to the condition version, * null if the user attribute version has an invalid type */ -function semverGreaterThanOrEqualEvaluator(condition: Condition, user: OptimizelyUserContext): boolean | null { - const result = evaluateSemanticVersion(condition, user); +function semverGreaterThanOrEqualEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { + const result = evaluateSemanticVersion(condition, user, logger); if (result === null) { return null; } @@ -457,11 +471,10 @@ function semverGreaterThanOrEqualEvaluator(condition: Condition, user: Optimizel * false if the user attribute version is not less than or equal to the condition version, * null if the user attribute version has an invalid type */ -function semverLessThanOrEqualEvaluator(condition: Condition, user: OptimizelyUserContext): boolean | null { - const result = evaluateSemanticVersion(condition, user); +function semverLessThanOrEqualEvaluator(condition: Condition, user: OptimizelyUserContext, logger?: LoggerFacade): boolean | null { + const result = evaluateSemanticVersion(condition, user, logger); if (result === null) { return null; } return result <= 0; - } diff --git a/lib/core/decision/index.spec.ts b/lib/core/decision/index.spec.ts new file mode 100644 index 000000000..ea98fba39 --- /dev/null +++ b/lib/core/decision/index.spec.ts @@ -0,0 +1,128 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect } from 'vitest'; +import { rolloutDecisionObj, featureTestDecisionObj } from '../../tests/test_data'; +import * as decision from './'; + +describe('getExperimentKey method', () => { + it('should return empty string when experiment is null', () => { + const experimentKey = decision.getExperimentKey(rolloutDecisionObj); + + expect(experimentKey).toEqual(''); + }); + + it('should return empty string when experiment is not defined', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const experimentKey = decision.getExperimentKey({}); + + expect(experimentKey).toEqual(''); + }); + + it('should return experiment key when experiment is defined', () => { + const experimentKey = decision.getExperimentKey(featureTestDecisionObj); + + expect(experimentKey).toEqual('testing_my_feature'); + }); +}); + +describe('getExperimentId method', () => { + it('should return null when experiment is null', () => { + const experimentId = decision.getExperimentId(rolloutDecisionObj); + + expect(experimentId).toEqual(null); + }); + + it('should return null when experiment is not defined', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const experimentId = decision.getExperimentId({}); + + expect(experimentId).toEqual(null); + }); + + it('should return experiment id when experiment is defined', () => { + const experimentId = decision.getExperimentId(featureTestDecisionObj); + + expect(experimentId).toEqual('594098'); + }); + + describe('getVariationKey method', ()=> { + it('should return empty string when variation is null', () => { + const variationKey = decision.getVariationKey(rolloutDecisionObj); + + expect(variationKey).toEqual(''); + }); + + it('should return empty string when variation is not defined', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const variationKey = decision.getVariationKey({}); + + expect(variationKey).toEqual(''); + }); + + it('should return variation key when variation is defined', () => { + const variationKey = decision.getVariationKey(featureTestDecisionObj); + + expect(variationKey).toEqual('variation'); + }); + }); + + describe('getVariationId method', () => { + it('should return null when variation is null', () => { + const variationId = decision.getVariationId(rolloutDecisionObj); + + expect(variationId).toEqual(null); + }); + + it('should return null when variation is not defined', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const variationId = decision.getVariationId({}); + + expect(variationId).toEqual(null); + }); + + it('should return variation id when variation is defined', () => { + const variationId = decision.getVariationId(featureTestDecisionObj); + + expect(variationId).toEqual('594096'); + }); + }); + + describe('getFeatureEnabledFromVariation method', () => { + it('should return false when variation is null', () => { + const featureEnabled = decision.getFeatureEnabledFromVariation(rolloutDecisionObj); + + expect(featureEnabled).toEqual(false); + }); + + it('should return false when variation is not defined', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const featureEnabled = decision.getFeatureEnabledFromVariation({}); + + expect(featureEnabled).toEqual(false); + }); + + it('should return featureEnabled boolean when variation is defined', () => { + const featureEnabled = decision.getFeatureEnabledFromVariation(featureTestDecisionObj); + + expect(featureEnabled).toEqual(true); + }); + }); +}); diff --git a/packages/optimizely-sdk/lib/core/decision/index.tests.js b/lib/core/decision/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/core/decision/index.tests.js rename to lib/core/decision/index.tests.js diff --git a/packages/optimizely-sdk/lib/core/decision/index.ts b/lib/core/decision/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/core/decision/index.ts rename to lib/core/decision/index.ts diff --git a/lib/core/decision_service/cmab/cmab_client.spec.ts b/lib/core/decision_service/cmab/cmab_client.spec.ts new file mode 100644 index 000000000..88d258892 --- /dev/null +++ b/lib/core/decision_service/cmab/cmab_client.spec.ts @@ -0,0 +1,403 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, vi, Mocked, Mock, MockInstance, beforeEach, afterEach } from 'vitest'; + +import { DefaultCmabClient } from './cmab_client'; +import { getMockAbortableRequest, getMockRequestHandler } from '../../../tests/mock/mock_request_handler'; +import { RequestHandler } from '../../../utils/http_request_handler/http'; +import { advanceTimersByTime, exhaustMicrotasks } from '../../../tests/testUtils'; +import { OptimizelyError } from '../../../error/optimizly_error'; + +const mockSuccessResponse = (variation: string) => Promise.resolve({ + statusCode: 200, + body: JSON.stringify({ + predictions: [ + { + variation_id: variation, + }, + ], + }), + headers: {} +}); + +const mockErrorResponse = (statusCode: number) => Promise.resolve({ + statusCode, + body: '', + headers: {}, +}); + +const assertRequest = ( + call: number, + mockRequestHandler: MockInstance<RequestHandler['makeRequest']>, + ruleId: string, + userId: string, + attributes: Record<string, any>, + cmabUuid: string, +) => { + const [requestUrl, headers, method, data] = mockRequestHandler.mock.calls[call]; + expect(requestUrl).toBe(`https://prediction.cmab.optimizely.com/predict/${ruleId}`); + expect(method).toBe('POST'); + expect(headers).toEqual({ + 'Content-Type': 'application/json', + }); + + const parsedData = JSON.parse(data!); + expect(parsedData.instances).toEqual([ + { + visitorId: userId, + experimentId: ruleId, + attributes: Object.keys(attributes).map((key) => ({ + id: key, + value: attributes[key], + type: 'custom_attribute', + })), + cmabUUID: cmabUuid, + } + ]); +}; + +describe('DefaultCmabClient', () => { + it('should fetch variation using correct parameters', async () => { + const requestHandler = getMockRequestHandler(); + + const mockMakeRequest: MockInstance<RequestHandler['makeRequest']> = requestHandler.makeRequest; + mockMakeRequest.mockReturnValue(getMockAbortableRequest(mockSuccessResponse('var123'))); + + const cmabClient = new DefaultCmabClient({ + requestHandler, + }); + + const ruleId = '123'; + const userId = 'user123'; + const attributes = { + browser: 'chrome', + isMobile: true, + }; + const cmabUuid = 'uuid123'; + + const variation = await cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid); + + expect(variation).toBe('var123'); + assertRequest(0, mockMakeRequest, ruleId, userId, attributes, cmabUuid); + }); + + it('should retry fetch if retryConfig is provided', async () => { + const requestHandler = getMockRequestHandler(); + + const mockMakeRequest: MockInstance<RequestHandler['makeRequest']> = requestHandler.makeRequest; + mockMakeRequest.mockReturnValueOnce(getMockAbortableRequest(Promise.reject('error'))) + .mockReturnValueOnce(getMockAbortableRequest(mockErrorResponse(500))) + .mockReturnValueOnce(getMockAbortableRequest(mockSuccessResponse('var123'))); + + const cmabClient = new DefaultCmabClient({ + requestHandler, + retryConfig: { + maxRetries: 5, + }, + }); + + const ruleId = '123'; + const userId = 'user123'; + const attributes = { + browser: 'chrome', + isMobile: true, + }; + const cmabUuid = 'uuid123'; + + const variation = await cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid); + + expect(variation).toBe('var123'); + expect(mockMakeRequest.mock.calls.length).toBe(3); + for(let i = 0; i < 3; i++) { + assertRequest(i, mockMakeRequest, ruleId, userId, attributes, cmabUuid); + } + }); + + it('should use backoff provider if provided', async () => { + vi.useFakeTimers(); + + const requestHandler = getMockRequestHandler(); + + const mockMakeRequest: MockInstance<RequestHandler['makeRequest']> = requestHandler.makeRequest; + mockMakeRequest.mockReturnValueOnce(getMockAbortableRequest(Promise.reject('error'))) + .mockReturnValueOnce(getMockAbortableRequest(mockErrorResponse(500))) + .mockReturnValueOnce(getMockAbortableRequest(mockErrorResponse(500))) + .mockReturnValueOnce(getMockAbortableRequest(mockSuccessResponse('var123'))); + + const backoffProvider = () => { + let call = 0; + const values = [100, 200, 300]; + return { + reset: () => {}, + backoff: () => { + return values[call++]; + }, + }; + } + + const cmabClient = new DefaultCmabClient({ + requestHandler, + retryConfig: { + maxRetries: 5, + backoffProvider, + }, + }); + + const ruleId = '123'; + const userId = 'user123'; + const attributes = { + browser: 'chrome', + isMobile: true, + }; + const cmabUuid = 'uuid123'; + + const fetchPromise = cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid); + + await exhaustMicrotasks(); + expect(mockMakeRequest.mock.calls.length).toBe(1); + + // first backoff is 100ms, should not retry yet + await advanceTimersByTime(90); + await exhaustMicrotasks(); + expect(mockMakeRequest.mock.calls.length).toBe(1); + + // first backoff is 100ms, should retry now + await advanceTimersByTime(10); + await exhaustMicrotasks(); + expect(mockMakeRequest.mock.calls.length).toBe(2); + + // second backoff is 200ms, should not retry 2nd time yet + await advanceTimersByTime(150); + await exhaustMicrotasks(); + expect(mockMakeRequest.mock.calls.length).toBe(2); + + // second backoff is 200ms, should retry 2nd time now + await advanceTimersByTime(50); + await exhaustMicrotasks(); + expect(mockMakeRequest.mock.calls.length).toBe(3); + + // third backoff is 300ms, should not retry 3rd time yet + await advanceTimersByTime(280); + await exhaustMicrotasks(); + expect(mockMakeRequest.mock.calls.length).toBe(3); + + // third backoff is 300ms, should retry 3rd time now + await advanceTimersByTime(20); + await exhaustMicrotasks(); + expect(mockMakeRequest.mock.calls.length).toBe(4); + + const variation = await fetchPromise; + + expect(variation).toBe('var123'); + expect(mockMakeRequest.mock.calls.length).toBe(4); + for(let i = 0; i < 4; i++) { + assertRequest(i, mockMakeRequest, ruleId, userId, attributes, cmabUuid); + } + vi.useRealTimers(); + }); + + it('should reject the promise after retries are exhausted', async () => { + const requestHandler = getMockRequestHandler(); + + const mockMakeRequest: MockInstance<RequestHandler['makeRequest']> = requestHandler.makeRequest; + mockMakeRequest.mockReturnValue(getMockAbortableRequest(Promise.reject('error'))); + + const cmabClient = new DefaultCmabClient({ + requestHandler, + retryConfig: { + maxRetries: 5, + }, + }); + + const ruleId = '123'; + const userId = 'user123'; + const attributes = { + browser: 'chrome', + isMobile: true, + }; + const cmabUuid = 'uuid123'; + + await expect(cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid)).rejects.toThrow(); + expect(mockMakeRequest.mock.calls.length).toBe(6); + }); + + it('should reject the promise after retries are exhausted with error status', async () => { + const requestHandler = getMockRequestHandler(); + + const mockMakeRequest: MockInstance<RequestHandler['makeRequest']> = requestHandler.makeRequest; + mockMakeRequest.mockReturnValue(getMockAbortableRequest(mockErrorResponse(500))); + + const cmabClient = new DefaultCmabClient({ + requestHandler, + retryConfig: { + maxRetries: 5, + }, + }); + + const ruleId = '123'; + const userId = 'user123'; + const attributes = { + browser: 'chrome', + isMobile: true, + }; + const cmabUuid = 'uuid123'; + + await expect(cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid)).rejects.toThrow(); + expect(mockMakeRequest.mock.calls.length).toBe(6); + }); + + it('should not retry if retryConfig is not provided', async () => { + const requestHandler = getMockRequestHandler(); + + const mockMakeRequest: MockInstance<RequestHandler['makeRequest']> = requestHandler.makeRequest; + mockMakeRequest.mockReturnValueOnce(getMockAbortableRequest(Promise.reject('error'))); + + const cmabClient = new DefaultCmabClient({ + requestHandler, + }); + + const ruleId = '123'; + const userId = 'user123'; + const attributes = { + browser: 'chrome', + isMobile: true, + }; + const cmabUuid = 'uuid123'; + + await expect(cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid)).rejects.toThrow(); + expect(mockMakeRequest.mock.calls.length).toBe(1); + }); + + it('should reject the promise if response status code is not 200', async () => { + const requestHandler = getMockRequestHandler(); + + const mockMakeRequest: MockInstance<RequestHandler['makeRequest']> = requestHandler.makeRequest; + mockMakeRequest.mockReturnValue(getMockAbortableRequest(mockErrorResponse(500))); + + const cmabClient = new DefaultCmabClient({ + requestHandler, + }); + + const ruleId = '123'; + const userId = 'user123'; + const attributes = { + browser: 'chrome', + isMobile: true, + }; + const cmabUuid = 'uuid123'; + + await expect(cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid)).rejects.toMatchObject( + new OptimizelyError('CMAB_FETCH_FAILED', 500), + ); + }); + + it('should reject the promise if api response is not valid', async () => { + const requestHandler = getMockRequestHandler(); + + const mockMakeRequest: MockInstance<RequestHandler['makeRequest']> = requestHandler.makeRequest; + mockMakeRequest.mockReturnValue(getMockAbortableRequest(Promise.resolve({ + statusCode: 200, + body: JSON.stringify({ + predictions: [], + }), + headers: {}, + }))); + + const cmabClient = new DefaultCmabClient({ + requestHandler, + }); + + const ruleId = '123'; + const userId = 'user123'; + const attributes = { + browser: 'chrome', + isMobile: true, + }; + const cmabUuid = 'uuid123'; + + await expect(cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid)).rejects.toMatchObject( + new OptimizelyError('INVALID_CMAB_RESPONSE'), + ); + }); + + it('should reject the promise if requestHandler.makeRequest rejects', async () => { + const requestHandler = getMockRequestHandler(); + + const mockMakeRequest: MockInstance<RequestHandler['makeRequest']> = requestHandler.makeRequest; + mockMakeRequest.mockReturnValue(getMockAbortableRequest(Promise.reject('error'))); + + const cmabClient = new DefaultCmabClient({ + requestHandler, + }); + + const ruleId = '123'; + const userId = 'user123'; + const attributes = { + browser: 'chrome', + isMobile: true, + }; + const cmabUuid = 'uuid123'; + + await expect(cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid)).rejects.toThrow('error'); + }); + + it('should use custom prediction endpoint template when provided', async () => { + const requestHandler = getMockRequestHandler(); + + const mockMakeRequest: MockInstance<RequestHandler['makeRequest']> = requestHandler.makeRequest; + mockMakeRequest.mockReturnValue(getMockAbortableRequest(mockSuccessResponse('var456'))); + + const customEndpoint = 'https://custom.example.com/predict/%s'; + const cmabClient = new DefaultCmabClient({ + requestHandler, + predictionEndpointTemplate: customEndpoint, + }); + const ruleId = '789'; + const userId = 'user789'; + const attributes = { + browser: 'firefox', + }; + const cmabUuid = 'uuid789'; + const variation = await cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid); + const [requestUrl] = mockMakeRequest.mock.calls[0]; + + expect(variation).toBe('var456'); + expect(mockMakeRequest.mock.calls.length).toBe(1); + expect(requestUrl).toBe('https://custom.example.com/predict/789'); + }); + + it('should use default prediction endpoint template when not provided', async () => { + const requestHandler = getMockRequestHandler(); + const mockMakeRequest: MockInstance<RequestHandler['makeRequest']> = requestHandler.makeRequest; + mockMakeRequest.mockReturnValue(getMockAbortableRequest(mockSuccessResponse('var999'))); + const cmabClient = new DefaultCmabClient({ + requestHandler, + }); + const ruleId = '555'; + const userId = 'user555'; + const attributes = { + browser: 'safari', + }; + const cmabUuid = 'uuid555'; + const variation = await cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid); + const [requestUrl] = mockMakeRequest.mock.calls[0]; + + expect(variation).toBe('var999'); + expect(mockMakeRequest.mock.calls.length).toBe(1); + expect(requestUrl).toBe('https://prediction.cmab.optimizely.com/predict/555'); + }); +}); diff --git a/lib/core/decision_service/cmab/cmab_client.ts b/lib/core/decision_service/cmab/cmab_client.ts new file mode 100644 index 000000000..a6925713a --- /dev/null +++ b/lib/core/decision_service/cmab/cmab_client.ts @@ -0,0 +1,121 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { OptimizelyError } from "../../../error/optimizly_error"; +import { CMAB_FETCH_FAILED, INVALID_CMAB_FETCH_RESPONSE } from "../../../message/error_message"; +import { UserAttributes } from "../../../shared_types"; +import { runWithRetry } from "../../../utils/executor/backoff_retry_runner"; +import { sprintf } from "../../../utils/fns"; +import { RequestHandler } from "../../../utils/http_request_handler/http"; +import { isSuccessStatusCode } from "../../../utils/http_request_handler/http_util"; +import { BackoffController } from "../../../utils/repeater/repeater"; +import { Producer } from "../../../utils/type"; + +export interface CmabClient { + fetchDecision( + ruleId: string, + userId: string, + attributes: UserAttributes, + cmabUuid: string, + ): Promise<string> +} + +const DEFAULT_CMAB_PREDICTION_ENDPOINT = 'https://prediction.cmab.optimizely.com/predict/%s'; + +export type RetryConfig = { + maxRetries: number, + backoffProvider?: Producer<BackoffController>; +} + +export type CmabClientConfig = { + requestHandler: RequestHandler; + retryConfig?: RetryConfig; + predictionEndpointTemplate?: string; +} + +export class DefaultCmabClient implements CmabClient { + private requestHandler: RequestHandler; + private retryConfig?: RetryConfig; + private predictionEndpointTemplate: string = DEFAULT_CMAB_PREDICTION_ENDPOINT; + + constructor(config: CmabClientConfig) { + this.requestHandler = config.requestHandler; + this.retryConfig = config.retryConfig; + if (config.predictionEndpointTemplate) { + this.predictionEndpointTemplate = config.predictionEndpointTemplate; + } + } + + async fetchDecision( + ruleId: string, + userId: string, + attributes: UserAttributes, + cmabUuid: string, + ): Promise<string> { + const url = sprintf(this.predictionEndpointTemplate, ruleId); + + const cmabAttributes = Object.keys(attributes).map((key) => ({ + id: key, + value: attributes[key], + type: 'custom_attribute', + })); + + const body = { + instances: [ + { + visitorId: userId, + experimentId: ruleId, + attributes: cmabAttributes, + cmabUUID: cmabUuid, + } + ] + } + + const variation = await (this.retryConfig ? + runWithRetry( + () => this.doFetch(url, JSON.stringify(body)), + this.retryConfig.backoffProvider?.(), + this.retryConfig.maxRetries, + ).result : this.doFetch(url, JSON.stringify(body)) + ); + + return variation; + } + + private async doFetch(url: string, data: string): Promise<string> { + const response = await this.requestHandler.makeRequest( + url, + { 'Content-Type': 'application/json' }, + 'POST', + data, + ).responsePromise; + + if (!isSuccessStatusCode(response.statusCode)) { + return Promise.reject(new OptimizelyError(CMAB_FETCH_FAILED, response.statusCode)); + } + + const body = JSON.parse(response.body); + if (!this.validateResponse(body)) { + return Promise.reject(new OptimizelyError(INVALID_CMAB_FETCH_RESPONSE)); + } + + return String(body.predictions[0].variation_id); + } + + private validateResponse(body: any): boolean { + return body.predictions && body.predictions.length > 0 && body.predictions[0].variation_id; + } +} diff --git a/lib/core/decision_service/cmab/cmab_service.spec.ts b/lib/core/decision_service/cmab/cmab_service.spec.ts new file mode 100644 index 000000000..38ee205e4 --- /dev/null +++ b/lib/core/decision_service/cmab/cmab_service.spec.ts @@ -0,0 +1,510 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, vi } from 'vitest'; + +import { DefaultCmabService } from './cmab_service'; +import { getMockSyncCache } from '../../../tests/mock/mock_cache'; +import { ProjectConfig } from '../../../project_config/project_config'; +import { OptimizelyDecideOption, UserAttributes } from '../../../shared_types'; +import OptimizelyUserContext from '../../../optimizely_user_context'; +import { validate as uuidValidate } from 'uuid'; +import { resolvablePromise } from '../../../utils/promise/resolvablePromise'; +import { exhaustMicrotasks } from '../../../tests/testUtils'; + +const mockProjectConfig = (): ProjectConfig => ({ + experimentIdMap: { + '1234': { + id: '1234', + key: 'cmab_1', + cmab: { + attributeIds: ['66', '77', '88'], + } + }, + '5678': { + id: '5678', + key: 'cmab_2', + cmab: { + attributeIds: ['66', '99'], + } + }, + }, + attributeIdMap: { + '66': { + id: '66', + key: 'country', + }, + '77': { + id: '77', + key: 'age', + }, + '88': { + id: '88', + key: 'language', + }, + '99': { + id: '99', + key: 'gender', + }, + } +} as any); + +const mockUserContext = (userId: string, attributes: UserAttributes): OptimizelyUserContext => new OptimizelyUserContext({ + userId, + attributes, +} as any); + +describe('DefaultCmabService', () => { + it('should fetch and return the variation from cmabClient using correct parameters', async () => { + const mockCmabClient = { + fetchDecision: vi.fn().mockResolvedValue('123'), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + const userContext = mockUserContext('user123', { + country: 'US', + age: '25', + gender: 'male', + }); + + const ruleId = '1234'; + const variation = await cmabService.getDecision(projectConfig, userContext, ruleId, {}); + + expect(variation.variationId).toEqual('123'); + expect(uuidValidate(variation.cmabUuid)).toBe(true); + + expect(mockCmabClient.fetchDecision).toHaveBeenCalledOnce(); + const [ruleIdArg, userIdArg, attributesArg, cmabUuidArg] = mockCmabClient.fetchDecision.mock.calls[0]; + expect(ruleIdArg).toEqual(ruleId); + expect(userIdArg).toEqual(userContext.getUserId()); + expect(attributesArg).toEqual({ + country: 'US', + age: '25', + }); + }); + + it('should filter attributes based on experiment cmab attributeIds before fetching variation', async () => { + const mockCmabClient = { + fetchDecision: vi.fn().mockResolvedValue('123'), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + const userContext = mockUserContext('user123', { + country: 'US', + age: '25', + language: 'en', + gender: 'male' + }); + + await cmabService.getDecision(projectConfig, userContext, '1234', {}); + await cmabService.getDecision(projectConfig, userContext, '5678', {}); + + expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(2); + expect(mockCmabClient.fetchDecision.mock.calls[0][2]).toEqual({ + country: 'US', + age: '25', + language: 'en', + }); + expect(mockCmabClient.fetchDecision.mock.calls[1][2]).toEqual({ + country: 'US', + gender: 'male' + }); + }); + + it('should cache the variation and return the same variation if relevant attributes have not changed', async () => { + const mockCmabClient = { + fetchDecision: vi.fn().mockResolvedValueOnce('123') + .mockResolvedValueOnce('456') + .mockResolvedValueOnce('789'), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + const userContext11 = mockUserContext('user123', { + country: 'US', + age: '25', + language: 'en', + gender: 'male' + }); + + const variation11 = await cmabService.getDecision(projectConfig, userContext11, '1234', {}); + + const userContext12 = mockUserContext('user123', { + country: 'US', + age: '25', + language: 'en', + gender: 'female' + }); + + const variation12 = await cmabService.getDecision(projectConfig, userContext12, '1234', {}); + expect(variation11.variationId).toEqual('123'); + expect(variation12.variationId).toEqual('123'); + expect(variation11.cmabUuid).toEqual(variation12.cmabUuid); + + expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(1); + + const userContext21 = mockUserContext('user456', { + country: 'BD', + age: '30', + }); + + const variation21 = await cmabService.getDecision(projectConfig, userContext21, '5678', {}); + + const userContext22 = mockUserContext('user456', { + country: 'BD', + age: '35', + }); + + const variation22 = await cmabService.getDecision(projectConfig, userContext22, '5678', {}); + expect(variation21.variationId).toEqual('456'); + expect(variation22.variationId).toEqual('456'); + expect(variation21.cmabUuid).toEqual(variation22.cmabUuid); + + expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(2); + }); + + it('should cache the variation and return the same variation if relevant attributes value have not changed but order changed', async () => { + const mockCmabClient = { + fetchDecision: vi.fn().mockResolvedValueOnce('123') + .mockResolvedValueOnce('456') + .mockResolvedValueOnce('789'), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + const userContext11 = mockUserContext('user123', { + country: 'US', + age: '25', + language: 'en', + gender: 'male' + }); + + const variation11 = await cmabService.getDecision(projectConfig, userContext11, '1234', {}); + + const userContext12 = mockUserContext('user123', { + gender: 'female', + language: 'en', + country: 'US', + age: '25', + }); + + const variation12 = await cmabService.getDecision(projectConfig, userContext12, '1234', {}); + expect(variation11.variationId).toEqual('123'); + expect(variation12.variationId).toEqual('123'); + expect(variation11.cmabUuid).toEqual(variation12.cmabUuid); + + expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(1); + }); + + it('should not mix up the cache between different experiments', async () => { + const mockCmabClient = { + fetchDecision: vi.fn().mockResolvedValueOnce('123') + .mockResolvedValueOnce('456') + .mockResolvedValueOnce('789'), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + const userContext = mockUserContext('user123', { + country: 'US', + age: '25', + }); + + const variation1 = await cmabService.getDecision(projectConfig, userContext, '1234', {}); + + const variation2 = await cmabService.getDecision(projectConfig, userContext, '5678', {}); + + expect(variation1.variationId).toEqual('123'); + expect(variation2.variationId).toEqual('456'); + expect(variation1.cmabUuid).not.toEqual(variation2.cmabUuid); + }); + + it('should not mix up the cache between different users', async () => { + const mockCmabClient = { + fetchDecision: vi.fn().mockResolvedValueOnce('123') + .mockResolvedValueOnce('456') + .mockResolvedValueOnce('789'), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + + const userContext1 = mockUserContext('user123', { + country: 'US', + age: '25', + }); + + const userContext2 = mockUserContext('user456', { + country: 'US', + age: '25', + }); + + const variation1 = await cmabService.getDecision(projectConfig, userContext1, '1234', {}); + + const variation2 = await cmabService.getDecision(projectConfig, userContext2, '1234', {}); + expect(variation1.variationId).toEqual('123'); + expect(variation2.variationId).toEqual('456'); + expect(variation1.cmabUuid).not.toEqual(variation2.cmabUuid); + + expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(2); + }); + + it('should invalidate the cache and fetch a new variation if relevant attributes have changed', async () => { + const mockCmabClient = { + fetchDecision: vi.fn().mockResolvedValueOnce('123') + .mockResolvedValueOnce('456'), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + const userContext1 = mockUserContext('user123', { + country: 'US', + age: '25', + language: 'en', + gender: 'male' + }); + + const variation1 = await cmabService.getDecision(projectConfig, userContext1, '1234', {}); + + const userContext2 = mockUserContext('user123', { + country: 'US', + age: '50', + language: 'en', + gender: 'male' + }); + + const variation2 = await cmabService.getDecision(projectConfig, userContext2, '1234', {}); + expect(variation1.variationId).toEqual('123'); + expect(variation2.variationId).toEqual('456'); + expect(variation1.cmabUuid).not.toEqual(variation2.cmabUuid); + + expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(2); + }); + + it('should ignore the cache and fetch variation if IGNORE_CMAB_CACHE option is provided', async () => { + const mockCmabClient = { + fetchDecision: vi.fn().mockResolvedValueOnce('123') + .mockResolvedValueOnce('456'), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + const userContext = mockUserContext('user123', { + country: 'US', + age: '25', + language: 'en', + gender: 'male' + }); + + const variation1 = await cmabService.getDecision(projectConfig, userContext, '1234', {}); + + const variation2 = await cmabService.getDecision(projectConfig, userContext, '1234', { + [OptimizelyDecideOption.IGNORE_CMAB_CACHE]: true, + }); + + const variation3 = await cmabService.getDecision(projectConfig, userContext, '1234', {}); + + expect(variation1.variationId).toEqual('123'); + expect(variation2.variationId).toEqual('456'); + expect(variation1.cmabUuid).not.toEqual(variation2.cmabUuid); + + expect(variation3.variationId).toEqual('123'); + expect(variation3.cmabUuid).toEqual(variation1.cmabUuid); + + expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(2); + }); + + it('should reset the cache before fetching variation if RESET_CMAB_CACHE option is provided', async () => { + const mockCmabClient = { + fetchDecision: vi.fn().mockResolvedValueOnce('123') + .mockResolvedValueOnce('456') + .mockResolvedValueOnce('789') + .mockResolvedValueOnce('101112'), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + const userContext1 = mockUserContext('user123', { + country: 'US', + age: '25' + }); + + const userContext2 = mockUserContext('user456', { + country: 'US', + age: '50' + }); + + const variation1 = await cmabService.getDecision(projectConfig, userContext1, '1234', {}); + expect(variation1.variationId).toEqual('123'); + + const variation2 = await cmabService.getDecision(projectConfig, userContext2, '1234', {}); + expect(variation2.variationId).toEqual('456'); + + const variation3 = await cmabService.getDecision(projectConfig, userContext1, '1234', { + [OptimizelyDecideOption.RESET_CMAB_CACHE]: true, + }); + + expect(variation3.variationId).toEqual('789'); + + const variation4 = await cmabService.getDecision(projectConfig, userContext2, '1234', {}); + expect(variation4.variationId).toEqual('101112'); + }); + + it('should invalidate the cache and fetch a new variation if INVALIDATE_USER_CMAB_CACHE option is provided', async () => { + const mockCmabClient = { + fetchDecision: vi.fn().mockResolvedValueOnce('123') + .mockResolvedValueOnce('456'), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + const userContext = mockUserContext('user123', { + country: 'US', + age: '25', + language: 'en', + gender: 'male' + }); + + const variation1 = await cmabService.getDecision(projectConfig, userContext, '1234', {}); + + const variation2 = await cmabService.getDecision(projectConfig, userContext, '1234', { + [OptimizelyDecideOption.INVALIDATE_USER_CMAB_CACHE]: true, + }); + + const variation3 = await cmabService.getDecision(projectConfig, userContext, '1234', {}); + + expect(variation1.variationId).toEqual('123'); + expect(variation2.variationId).toEqual('456'); + expect(variation1.cmabUuid).not.toEqual(variation2.cmabUuid); + expect(variation3.variationId).toEqual('456'); + expect(variation2.cmabUuid).toEqual(variation3.cmabUuid); + + expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(2); + }); + + it('should serialize concurrent calls to getDecision with the same userId and ruleId', async () => { + const nCall = 10; + let currentVar = 123; + const fetchPromises = Array.from({ length: nCall }, () => resolvablePromise()); + + let callCount = 0; + const mockCmabClient = { + fetchDecision: vi.fn().mockImplementation(async () => { + const variation = `${currentVar++}`; + await fetchPromises[callCount++]; + return variation; + }), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + const userContext = mockUserContext('user123', {}); + + const resultPromises = []; + for (let i = 0; i < nCall; i++) { + resultPromises.push(cmabService.getDecision(projectConfig, userContext, '1234', {})); + } + + await exhaustMicrotasks(); + + expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(1); + + for(let i = 0; i < nCall; i++) { + fetchPromises[i].resolve(''); + await exhaustMicrotasks(); + const result = await resultPromises[i]; + expect(result.variationId).toBe('123'); + expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(1); + } + }); + + it('should not serialize calls to getDecision with different userId or ruleId', async () => { + let currentVar = 123; + const mockCmabClient = { + fetchDecision: vi.fn().mockImplementation(() => Promise.resolve(`${currentVar++}`)), + }; + + const cmabService = new DefaultCmabService({ + cmabCache: getMockSyncCache(), + cmabClient: mockCmabClient, + }); + + const projectConfig = mockProjectConfig(); + const userContext1 = mockUserContext('user123', {}); + const userContext2 = mockUserContext('user456', {}); + + const resultPromises = []; + resultPromises.push(cmabService.getDecision(projectConfig, userContext1, '1234', {})); + resultPromises.push(cmabService.getDecision(projectConfig, userContext1, '5678', {})); + resultPromises.push(cmabService.getDecision(projectConfig, userContext2, '1234', {})); + resultPromises.push(cmabService.getDecision(projectConfig, userContext2, '5678', {})); + + await exhaustMicrotasks(); + + expect(mockCmabClient.fetchDecision).toHaveBeenCalledTimes(4); + + for(let i = 0; i < resultPromises.length; i++) { + const result = await resultPromises[i]; + expect(result.variationId).toBe(`${123 + i}`); + } + }); +}); diff --git a/lib/core/decision_service/cmab/cmab_service.ts b/lib/core/decision_service/cmab/cmab_service.ts new file mode 100644 index 000000000..1963df613 --- /dev/null +++ b/lib/core/decision_service/cmab/cmab_service.ts @@ -0,0 +1,198 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LoggerFacade } from "../../../logging/logger"; +import { IOptimizelyUserContext } from "../../../optimizely_user_context"; +import { ProjectConfig } from "../../../project_config/project_config" +import { OptimizelyDecideOption, UserAttributes } from "../../../shared_types" +import { CacheWithRemove } from "../../../utils/cache/cache"; +import { CmabClient } from "./cmab_client"; +import { v4 as uuidV4 } from 'uuid'; +import murmurhash from "murmurhash"; +import { DecideOptionsMap } from ".."; +import { SerialRunner } from "../../../utils/executor/serial_runner"; +import { + CMAB_CACHE_ATTRIBUTES_MISMATCH, + CMAB_CACHE_HIT, + CMAB_CACHE_MISS, + IGNORE_CMAB_CACHE, + INVALIDATE_CMAB_CACHE, + RESET_CMAB_CACHE, +} from 'log_message'; + +export type CmabDecision = { + variationId: string, + cmabUuid: string, +} + +export interface CmabService { + /** + * Get variation id for the user + * @param {IOptimizelyUserContext} userContext + * @param {string} ruleId + * @param {OptimizelyDecideOption[]} options + * @return {Promise<CmabDecision>} + */ + getDecision( + projectConfig: ProjectConfig, + userContext: IOptimizelyUserContext, + ruleId: string, + options: DecideOptionsMap, + ): Promise<CmabDecision> +} + +export type CmabCacheValue = { + attributesHash: string, + variationId: string, + cmabUuid: string, +} + +export type CmabServiceOptions = { + logger?: LoggerFacade; + cmabCache: CacheWithRemove<CmabCacheValue>; + cmabClient: CmabClient; +} + +const SERIALIZER_BUCKETS = 1000; +const LOGGER_NAME = 'CmabService'; + +export class DefaultCmabService implements CmabService { + private cmabCache: CacheWithRemove<CmabCacheValue>; + private cmabClient: CmabClient; + private logger?: LoggerFacade; + private serializers: SerialRunner[] = Array.from( + { length: SERIALIZER_BUCKETS }, () => new SerialRunner() + ); + + constructor(options: CmabServiceOptions) { + this.cmabCache = options.cmabCache; + this.cmabClient = options.cmabClient; + this.logger = options.logger; + this.logger?.setName(LOGGER_NAME); + } + + private getSerializerIndex(userId: string, experimentId: string): number { + const key = this.getCacheKey(userId, experimentId); + const hash = murmurhash.v3(key); + return Math.abs(hash) % SERIALIZER_BUCKETS; + } + + async getDecision( + projectConfig: ProjectConfig, + userContext: IOptimizelyUserContext, + ruleId: string, + options: DecideOptionsMap, + ): Promise<CmabDecision> { + const serializerIndex = this.getSerializerIndex(userContext.getUserId(), ruleId); + return this.serializers[serializerIndex].run(() => + this.getDecisionInternal(projectConfig, userContext, ruleId, options) + ); + } + + private async getDecisionInternal( + projectConfig: ProjectConfig, + userContext: IOptimizelyUserContext, + ruleId: string, + options: DecideOptionsMap, + ): Promise<CmabDecision> { + const userId = userContext.getUserId(); + const filteredAttributes = this.filterAttributes(projectConfig, userContext, ruleId); + + if (options[OptimizelyDecideOption.IGNORE_CMAB_CACHE]) { + this.logger?.debug(IGNORE_CMAB_CACHE, userId, ruleId); + return this.fetchDecision(ruleId, userId, filteredAttributes); + } + + if (options[OptimizelyDecideOption.RESET_CMAB_CACHE]) { + this.logger?.debug(RESET_CMAB_CACHE, userId, ruleId); + this.cmabCache.reset(); + } + + const cacheKey = this.getCacheKey(userId, ruleId); + + if (options[OptimizelyDecideOption.INVALIDATE_USER_CMAB_CACHE]) { + this.logger?.debug(INVALIDATE_CMAB_CACHE, userId, ruleId); + this.cmabCache.remove(cacheKey); + } + + const cachedValue = await this.cmabCache.lookup(cacheKey); + + const attributesJson = JSON.stringify(filteredAttributes, Object.keys(filteredAttributes).sort()); + const attributesHash = String(murmurhash.v3(attributesJson)); + + if (cachedValue) { + if (cachedValue.attributesHash === attributesHash) { + this.logger?.debug(CMAB_CACHE_HIT, userId, ruleId); + return { variationId: cachedValue.variationId, cmabUuid: cachedValue.cmabUuid }; + } else { + this.logger?.debug(CMAB_CACHE_ATTRIBUTES_MISMATCH, userId, ruleId); + this.cmabCache.remove(cacheKey); + } + } else { + this.logger?.debug(CMAB_CACHE_MISS, userId, ruleId); + } + + const variation = await this.fetchDecision(ruleId, userId, filteredAttributes); + this.cmabCache.save(cacheKey, { + attributesHash, + variationId: variation.variationId, + cmabUuid: variation.cmabUuid, + }); + + return variation; + } + + private async fetchDecision( + ruleId: string, + userId: string, + attributes: UserAttributes, + ): Promise<CmabDecision> { + const cmabUuid = uuidV4(); + const variationId = await this.cmabClient.fetchDecision(ruleId, userId, attributes, cmabUuid); + return { variationId, cmabUuid }; + } + + private filterAttributes( + projectConfig: ProjectConfig, + userContext: IOptimizelyUserContext, + ruleId: string + ): UserAttributes { + const filteredAttributes: UserAttributes = {}; + const userAttributes = userContext.getAttributes(); + + const experiment = projectConfig.experimentIdMap[ruleId]; + if (!experiment || !experiment.cmab) { + return filteredAttributes; + } + + const cmabAttributeIds = experiment.cmab.attributeIds; + + cmabAttributeIds.forEach((aid) => { + const attribute = projectConfig.attributeIdMap[aid]; + + if (userAttributes.hasOwnProperty(attribute.key)) { + filteredAttributes[attribute.key] = userAttributes[attribute.key]; + } + }); + + return filteredAttributes; + } + + private getCacheKey(userId: string, ruleId: string): string { + const len = userId.length; + return `${len}-${userId}-${ruleId}`; + } +} diff --git a/lib/core/decision_service/index.spec.ts b/lib/core/decision_service/index.spec.ts new file mode 100644 index 000000000..9fc9d89a1 --- /dev/null +++ b/lib/core/decision_service/index.spec.ts @@ -0,0 +1,2939 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, vi, MockInstance, beforeEach, afterEach } from 'vitest'; +import { CMAB_DUMMY_ENTITY_ID, CMAB_FETCH_FAILED, DecisionService } from '.'; +import { getMockLogger } from '../../tests/mock/mock_logger'; +import OptimizelyUserContext from '../../optimizely_user_context'; +import { bucket } from '../bucketer'; +import { getTestProjectConfig, getTestProjectConfigWithFeatures } from '../../tests/test_data'; +import { createProjectConfig, ProjectConfig } from '../../project_config/project_config'; +import { BucketerParams, Experiment, Holdout, OptimizelyDecideOption, UserAttributes, UserProfile } from '../../shared_types'; +import { CONTROL_ATTRIBUTES, DECISION_SOURCES } from '../../utils/enums'; +import { getDecisionTestDatafile } from '../../tests/decision_test_datafile'; +import { Value } from '../../utils/promise/operation_value'; +import { + USER_HAS_NO_FORCED_VARIATION, + VALID_BUCKETING_ID, + SAVED_USER_VARIATION, + SAVED_VARIATION_NOT_FOUND, +} from 'log_message'; +import { + EXPERIMENT_NOT_RUNNING, + RETURNING_STORED_VARIATION, + USER_NOT_IN_EXPERIMENT, + USER_FORCED_IN_VARIATION, + EVALUATING_AUDIENCES_COMBINED, + AUDIENCE_EVALUATION_RESULT_COMBINED, + USER_IN_ROLLOUT, + USER_NOT_IN_ROLLOUT, + FEATURE_HAS_NO_EXPERIMENTS, + USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, + USER_NOT_BUCKETED_INTO_TARGETING_RULE, + USER_BUCKETED_INTO_TARGETING_RULE, + NO_ROLLOUT_EXISTS, + USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, +} from '../decision_service/index'; +import { BUCKETING_ID_NOT_STRING, USER_PROFILE_LOOKUP_ERROR, USER_PROFILE_SAVE_ERROR } from 'error_message'; + +type MockLogger = ReturnType<typeof getMockLogger>; + +type MockFnType = ReturnType<typeof vi.fn>; + +type MockUserProfileService = { + lookup: MockFnType; + save: MockFnType; +}; + +type MockCmabService = { + getDecision: MockFnType; +} + +type DecisionServiceInstanceOpt = { + logger?: boolean; + userProfileService?: boolean; + userProfileServiceAsync?: boolean; +} + +type DecisionServiceInstance = { + logger?: MockLogger; + userProfileService?: MockUserProfileService; + userProfileServiceAsync?: MockUserProfileService; + cmabService: MockCmabService; + decisionService: DecisionService; +} + +const getDecisionService = (opt: DecisionServiceInstanceOpt = {}): DecisionServiceInstance => { + const logger = opt.logger ? getMockLogger() : undefined; + const userProfileService = opt.userProfileService ? { + lookup: vi.fn(), + save: vi.fn(), + } : undefined; + + const userProfileServiceAsync = opt.userProfileServiceAsync ? { + lookup: vi.fn(), + save: vi.fn(), + } : undefined; + + const cmabService = { + getDecision: vi.fn(), + }; + + const decisionService = new DecisionService({ + logger, + userProfileService, + userProfileServiceAsync, + UNSTABLE_conditionEvaluators: {}, + cmabService, + }); + + return { + logger, + userProfileService, + userProfileServiceAsync, + decisionService, + cmabService, + }; +}; + +const mockBucket: MockInstance<typeof bucket> = vi.hoisted(() => vi.fn()); + +vi.mock('../bucketer', () => ({ + bucket: mockBucket, +})); + +// Mock the feature toggle for holdout tests +const mockHoldoutToggle = vi.hoisted(() => vi.fn()); + +vi.mock('../../feature_toggle', () => ({ + holdout: mockHoldoutToggle, +})); + + +const cloneDeep = (d: any) => JSON.parse(JSON.stringify(d)); + +const testData = getTestProjectConfig(); +const testDataWithFeatures = getTestProjectConfigWithFeatures(); + +// Utility function to create test datafile with holdout configurations +const getHoldoutTestDatafile = () => { + const datafile = getDecisionTestDatafile(); + + // Add holdouts to the datafile + datafile.holdouts = [ + { + id: 'holdout_running_id', + key: 'holdout_running', + status: 'Running', + includedFlags: [], + excludedFlags: [], + audienceIds: ['4001'], // age_22 audience + audienceConditions: ['or', '4001'], + variations: [ + { + id: 'holdout_variation_running_id', + key: 'holdout_variation_running', + variables: [] + } + ], + trafficAllocation: [ + { + entityId: 'holdout_variation_running_id', + endOfRange: 5000 + } + ] + }, + { + id: "holdout_not_bucketed_id", + key: "holdout_not_bucketed", + status: "Running", + includedFlags: [], + excludedFlags: [], + audienceIds: ['4002'], + audienceConditions: ['or', '4002'], + variations: [ + { + id: 'holdout_not_bucketed_variation_id', + key: 'holdout_not_bucketed_variation', + variables: [] + } + ], + trafficAllocation: [ + { + entityId: 'holdout_not_bucketed_variation_id', + endOfRange: 0, + } + ] + }, + ]; + + return datafile; +}; + +const verifyBucketCall = ( + call: number, + projectConfig: ProjectConfig, + experiment: Experiment, + user: OptimizelyUserContext, + bucketIdFromAttribute = false, +) => { + const { + experimentId, + experimentKey, + userId, + trafficAllocationConfig, + experimentKeyMap, + experimentIdMap, + groupIdMap, + variationIdMap, + bucketingId, + } = mockBucket.mock.calls[call][0]; + let expectedTrafficAllocation = experiment.trafficAllocation; + if (experiment.cmab) { + expectedTrafficAllocation = [{ + endOfRange: experiment.cmab.trafficAllocation, + entityId: CMAB_DUMMY_ENTITY_ID, + }]; + } + + expect(experimentId).toBe(experiment.id); + expect(experimentKey).toBe(experiment.key); + expect(userId).toBe(user.getUserId()); + expect(trafficAllocationConfig).toEqual(expectedTrafficAllocation); + expect(experimentKeyMap).toBe(projectConfig.experimentKeyMap); + expect(experimentIdMap).toBe(projectConfig.experimentIdMap); + expect(groupIdMap).toBe(projectConfig.groupIdMap); + expect(variationIdMap).toBe(projectConfig.variationIdMap); + expect(bucketingId).toBe(bucketIdFromAttribute ? user.getAttributes()[CONTROL_ATTRIBUTES.BUCKETING_ID] : user.getUserId()); +}; + +describe('DecisionService', () => { + describe('getVariation', function() { + beforeEach(() => { + mockBucket.mockClear(); + }); + + it('should return the correct variation from bucketer for the given experiment key and user ID for a running experiment', () => { + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester' + }); + + const config = createProjectConfig(cloneDeep(testData)); + + const fakeDecisionResponse = { + result: '111128', + reasons: [], + }; + + const experiment = config.experimentIdMap['111127']; + + mockBucket.mockReturnValue(fakeDecisionResponse); // contains variation ID of the 'control' variation from `test_data` + + const { decisionService } = getDecisionService(); + + const variation = decisionService.getVariation(config, experiment, user); + + expect(variation.result).toBe('control'); + + expect(mockBucket).toHaveBeenCalledTimes(1); + verifyBucketCall(0, config, experiment, user); + }); + + it('should use $opt_bucketing_id attribute as bucketing id if provided', () => { + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + $opt_bucketing_id: 'test_bucketing_id', + }, + }); + + const config = createProjectConfig(cloneDeep(testData)); + + const fakeDecisionResponse = { + result: '111128', + reasons: [], + }; + + const experiment = config.experimentIdMap['111127']; + + mockBucket.mockReturnValue(fakeDecisionResponse); // contains variation ID of the 'control' variation from `test_data` + + const { decisionService } = getDecisionService(); + + const variation = decisionService.getVariation(config, experiment, user); + + expect(variation.result).toBe('control'); + + expect(mockBucket).toHaveBeenCalledTimes(1); + verifyBucketCall(0, config, experiment, user, true); + }); + + it('should return the whitelisted variation if the user is whitelisted', function() { + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'user2' + }); + + const config = createProjectConfig(cloneDeep(testData)); + + const experiment = config.experimentIdMap['122227']; + + const { decisionService, logger } = getDecisionService({ logger: true }); + + const variation = decisionService.getVariation(config, experiment, user); + + expect(variation.result).toBe('variationWithAudience'); + expect(mockBucket).not.toHaveBeenCalled(); + expect(logger?.debug).toHaveBeenCalledTimes(1); + expect(logger?.info).toHaveBeenCalledTimes(1); + + expect(logger?.debug).toHaveBeenNthCalledWith(1, USER_HAS_NO_FORCED_VARIATION, 'user2'); + expect(logger?.info).toHaveBeenNthCalledWith(1, USER_FORCED_IN_VARIATION, 'user2', 'variationWithAudience'); + }); + + it('should return null if the user does not meet audience conditions', () => { + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'user3' + }); + + const config = createProjectConfig(cloneDeep(testData)); + + const experiment = config.experimentIdMap['122227']; + + const { decisionService, logger } = getDecisionService({ logger: true }); + + const variation = decisionService.getVariation(config, experiment, user); + + expect(variation.result).toBe(null); + expect(mockBucket).not.toHaveBeenCalled(); + + expect(logger?.debug).toHaveBeenNthCalledWith(1, USER_HAS_NO_FORCED_VARIATION, 'user3'); + expect(logger?.debug).toHaveBeenNthCalledWith(2, EVALUATING_AUDIENCES_COMBINED, 'experiment', 'testExperimentWithAudiences', JSON.stringify(["11154"])); + + expect(logger?.info).toHaveBeenNthCalledWith(1, AUDIENCE_EVALUATION_RESULT_COMBINED, 'experiment', 'testExperimentWithAudiences', 'FALSE'); + expect(logger?.info).toHaveBeenNthCalledWith(2, USER_NOT_IN_EXPERIMENT, 'user3', 'testExperimentWithAudiences'); + }); + + it('should return the forced variation set using setForcedVariation \ + in presence of a whitelisted variation', function() { + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'user2' + }); + + const config = createProjectConfig(cloneDeep(testData)); + + const experiment = config.experimentIdMap['122227']; + + const { decisionService } = getDecisionService(); + + const forcedVariation = 'controlWithAudience'; + + decisionService.setForcedVariation(config, experiment.key, user.getUserId(), forcedVariation); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe(forcedVariation); + + const whitelistedVariation = experiment.forcedVariations?.[user.getUserId()]; + expect(whitelistedVariation).toBeDefined(); + expect(whitelistedVariation).not.toEqual(forcedVariation); + }); + + it('should return the forced variation set using setForcedVariation \ + even if user does not satisfy audience condition', function() { + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'user3', // no attributes are set, should not satisfy audience condition 11154 + }); + + const config = createProjectConfig(cloneDeep(testData)); + + const experiment = config.experimentIdMap['122227']; + + const { decisionService } = getDecisionService(); + + const forcedVariation = 'controlWithAudience'; + + decisionService.setForcedVariation(config, experiment.key, user.getUserId(), forcedVariation); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe(forcedVariation); + }); + + it('should return null if the experiment is not running', function() { + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'user1' + }); + + const config = createProjectConfig(cloneDeep(testData)); + + const experiment = config.experimentIdMap['133337']; + + const { decisionService, logger } = getDecisionService({ logger: true }); + + const variation = decisionService.getVariation(config, experiment, user); + + expect(variation.result).toBe(null); + expect(mockBucket).not.toHaveBeenCalled(); + expect(logger?.info).toHaveBeenCalledTimes(1); + expect(logger?.info).toHaveBeenNthCalledWith(1, EXPERIMENT_NOT_RUNNING, 'testExperimentNotRunning'); + }); + + it('should respect the sticky bucketing information for attributes when attributes.$opt_experiment_bucket_map is supplied', () => { + const fakeDecisionResponse = { + result: '111128', + reasons: [], + }; + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const attributes: UserAttributes = { + $opt_experiment_bucket_map: { + '111127': { + variation_id: '111129', // ID of the 'variation' variation + }, + }, + }; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + attributes, + }); + + mockBucket.mockReturnValue(fakeDecisionResponse); // contains variation ID of the 'control' variation from `test_data` + + const { decisionService } = getDecisionService(); + + const variation = decisionService.getVariation(config, experiment, user); + + expect(variation.result).toBe('variation'); + expect(mockBucket).not.toHaveBeenCalled(); + }); + + describe('when a user profile service is provided', function() { + beforeEach(() => { + mockBucket.mockClear(); + }); + + it('should return the previously bucketed variation', () => { + const { decisionService, userProfileService, logger } = getDecisionService({ userProfileService: true, logger: true }); + + userProfileService?.lookup.mockReturnValue({ + user_id: 'decision_service_user', + experiment_bucket_map: { + '111127': { + variation_id: '111129', // ID of the 'variation' variation + }, + }, + }); + + mockBucket.mockReturnValue({ + result: '111128', // ID of the 'control' variation + reasons: [], + }); + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + }); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe('variation'); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileService?.lookup).toHaveBeenCalledWith('decision_service_user'); + expect(mockBucket).not.toHaveBeenCalled(); + + expect(logger?.debug).toHaveBeenCalledTimes(1); + expect(logger?.info).toHaveBeenCalledTimes(1); + + expect(logger?.debug).toHaveBeenNthCalledWith(1, USER_HAS_NO_FORCED_VARIATION, 'decision_service_user'); + expect(logger?.info).toHaveBeenNthCalledWith(1, RETURNING_STORED_VARIATION, 'variation', 'testExperiment', 'decision_service_user'); + }); + + it('should bucket and save user profile if there was no prevously bucketed variation', function() { + mockBucket.mockReturnValue({ + result: '111128', // ID of the 'control' variation + reasons: [], + }); + + const { decisionService, userProfileService, logger } = getDecisionService({ userProfileService: true, logger: true }); + + userProfileService?.lookup.mockReturnValue({ + user_id: 'decision_service_user', + experiment_bucket_map: {}, + }); + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + }); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe('control'); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileService?.lookup).toHaveBeenCalledWith('decision_service_user'); + + expect(mockBucket).toHaveBeenCalledTimes(1); + verifyBucketCall(0, config, experiment, user); + + expect(userProfileService?.save).toHaveBeenCalledTimes(1); + expect(userProfileService?.save).toHaveBeenCalledWith({ + user_id: 'decision_service_user', + experiment_bucket_map: { + '111127': { + variation_id: '111128', + }, + }, + }); + }); + + it('should bucket if the user profile service returns null', function() { + mockBucket.mockReturnValue({ + result: '111128', // ID of the 'control' variation + reasons: [], + }); + + const { decisionService, userProfileService, logger } = getDecisionService({ userProfileService: true, logger: true }); + + userProfileService?.lookup.mockReturnValue(null); + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + }); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe('control'); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileService?.lookup).toHaveBeenCalledWith('decision_service_user'); + + expect(mockBucket).toHaveBeenCalledTimes(1); + verifyBucketCall(0, config, experiment, user); + + expect(userProfileService?.save).toHaveBeenCalledTimes(1); + expect(userProfileService?.save).toHaveBeenCalledWith({ + user_id: 'decision_service_user', + experiment_bucket_map: { + '111127': { + variation_id: '111128', + }, + }, + }); + }); + + it('should re-bucket if the stored variation is no longer valid', function() { + mockBucket.mockReturnValue({ + result: '111128', // ID of the 'control' variation + reasons: [], + }); + + const { decisionService, userProfileService, logger } = getDecisionService({ userProfileService: true, logger: true }); + + userProfileService?.lookup.mockReturnValue({ + user_id: 'decision_service_user', + experiment_bucket_map: { + '111127': { + variation_id: 'not valid variation', + }, + }, + }); + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + }); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe('control'); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileService?.lookup).toHaveBeenCalledWith('decision_service_user'); + + expect(mockBucket).toHaveBeenCalledTimes(1); + verifyBucketCall(0, config, experiment, user); + + expect(logger?.debug).toHaveBeenCalledWith(USER_HAS_NO_FORCED_VARIATION, 'decision_service_user'); + expect(logger?.info).toHaveBeenCalledWith(SAVED_VARIATION_NOT_FOUND, 'decision_service_user', 'not valid variation', 'testExperiment'); + + expect(userProfileService?.save).toHaveBeenCalledTimes(1); + expect(userProfileService?.save).toHaveBeenCalledWith({ + user_id: 'decision_service_user', + experiment_bucket_map: { + '111127': { + variation_id: '111128', + }, + }, + }); + }); + + it('should store the bucketed variation for the user', function() { + mockBucket.mockReturnValue({ + result: '111128', // ID of the 'control' variation + reasons: [], + }); + + const { decisionService, userProfileService, logger } = getDecisionService({ userProfileService: true, logger: true }); + + userProfileService?.lookup.mockReturnValue({ + user_id: 'decision_service_user', + experiment_bucket_map: {}, + }); + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + }); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe('control'); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileService?.lookup).toHaveBeenCalledWith('decision_service_user'); + + expect(mockBucket).toHaveBeenCalledTimes(1); + verifyBucketCall(0, config, experiment, user); + + expect(userProfileService?.save).toHaveBeenCalledTimes(1); + expect(userProfileService?.save).toHaveBeenCalledWith({ + user_id: 'decision_service_user', + experiment_bucket_map: { + '111127': { + variation_id: '111128', + }, + }, + }); + }); + + + it('should log an error message and bucket if "lookup" throws an error', () => { + mockBucket.mockReturnValue({ + result: '111128', // ID of the 'control' variation + reasons: [], + }); + + const { decisionService, userProfileService, logger } = getDecisionService({ userProfileService: true, logger: true }); + + userProfileService?.lookup.mockImplementation(() => { + throw new Error('I am an error'); + }); + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + }); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe('control'); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileService?.lookup).toHaveBeenCalledWith('decision_service_user'); + + expect(mockBucket).toHaveBeenCalledTimes(1); + verifyBucketCall(0, config, experiment, user); + + expect(logger?.debug).toHaveBeenCalledWith(USER_HAS_NO_FORCED_VARIATION, 'decision_service_user'); + expect(logger?.error).toHaveBeenCalledWith(USER_PROFILE_LOOKUP_ERROR, 'decision_service_user', 'I am an error'); + + expect(userProfileService?.save).toHaveBeenCalledTimes(1); + expect(userProfileService?.save).toHaveBeenCalledWith({ + user_id: 'decision_service_user', + experiment_bucket_map: { + '111127': { + variation_id: '111128', + }, + }, + }); + }); + + it('should log an error message if "save" throws an error', () => { + mockBucket.mockReturnValue({ + result: '111128', // ID of the 'control' variation + reasons: [], + }); + + const { decisionService, userProfileService, logger } = getDecisionService({ userProfileService: true, logger: true }); + + userProfileService?.lookup.mockReturnValue(null); + userProfileService?.save.mockImplementation(() => { + throw new Error('I am an error'); + }); + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + }); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe('control'); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileService?.lookup).toHaveBeenCalledWith('decision_service_user'); + + expect(mockBucket).toHaveBeenCalledTimes(1); + verifyBucketCall(0, config, experiment, user); + + expect(userProfileService?.save).toHaveBeenCalledTimes(1); + expect(userProfileService?.save).toHaveBeenCalledWith({ + user_id: 'decision_service_user', + experiment_bucket_map: { + '111127': { + variation_id: '111128', + }, + }, + }); + + expect(logger?.error).toHaveBeenCalledWith(USER_PROFILE_SAVE_ERROR, 'decision_service_user', 'I am an error'); + }); + + it('should respect $opt_experiment_bucket_map attribute over the userProfileService for the matching experiment id', function() { + const { decisionService, userProfileService, logger } = getDecisionService({ userProfileService: true, logger: true }); + + userProfileService?.lookup.mockReturnValue({ + user_id: 'decision_service_user', + experiment_bucket_map: { + '111127': { + variation_id: '111128', // ID of the 'control' variation + }, + }, + }); + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const attributes: UserAttributes = { + $opt_experiment_bucket_map: { + '111127': { + variation_id: '111129', // ID of the 'variation' variation + }, + }, + }; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + attributes, + }); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe('variation'); + }); + + it('should ignore attributes for a different experiment id', function() { + const { decisionService, userProfileService, logger } = getDecisionService({ userProfileService: true, logger: true }); + + userProfileService?.lookup.mockReturnValue({ + user_id: 'decision_service_user', + experiment_bucket_map: { + '111127': { + variation_id: '111128', // ID of the 'control' variation + }, + }, + }); + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const attributes: UserAttributes = { + $opt_experiment_bucket_map: { + '122227': { + variation_id: '111129', // ID of the 'variation' variation + }, + }, + }; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + attributes, + }); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe('control'); + }); + + it('should use $ opt_experiment_bucket_map attribute when the userProfile contains variations for other experiments', function() { + const { decisionService, userProfileService, logger } = getDecisionService({ userProfileService: true, logger: true }); + + userProfileService?.lookup.mockReturnValue({ + user_id: 'decision_service_user', + experiment_bucket_map: { + '122227': { + variation_id: '122229', // ID of the 'variationWithAudience' variation + }, + }, + }); + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const attributes: UserAttributes = { + $opt_experiment_bucket_map: { + '111127': { + variation_id: '111129', // ID of the 'variation' variation + }, + }, + }; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + attributes, + }); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe('variation'); + }); + + it('should use attributes when the userProfileLookup returns null', function() { + const { decisionService, userProfileService, logger } = getDecisionService({ userProfileService: true, logger: true }); + + userProfileService?.lookup.mockReturnValue(null); + + const config = createProjectConfig(cloneDeep(testData)); + const experiment = config.experimentIdMap['111127']; + + const attributes: UserAttributes = { + $opt_experiment_bucket_map: { + '111127': { + variation_id: '111129', // ID of the 'variation' variation + }, + }, + }; + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'decision_service_user', + attributes, + }); + + const variation = decisionService.getVariation(config, experiment, user); + expect(variation.result).toBe('variation'); + }); + }); + }); + + describe('getVariationForFeature - sync', () => { + beforeEach(() => { + mockBucket.mockReset(); + }); + + it('should return variation from the first experiment for which a variation is available', () => { + const { decisionService } = getDecisionService(); + + const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') + .mockImplementation(( + op, + config, + experiment: any, + user, + decideOptions, + userProfileTracker: any, + ) => { + if (experiment.key === 'exp_2') { + return Value.of('sync', { + result: { variationKey: 'variation_2' }, + reasons: [], + }); + } + return Value.of('sync', { + result: {}, + reasons: [], + }); + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 40, + }, + }); + + const feature = config.featureKeyMap['flag_1']; + const variation = decisionService.getVariationForFeature(config, feature, user); + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5002'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(resolveVariationSpy).toHaveBeenCalledTimes(2); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(1, + 'sync', config, config.experimentKeyMap['exp_1'], user, expect.anything(), expect.anything()); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(2, + 'sync', config, config.experimentKeyMap['exp_2'], user, expect.anything(), expect.anything()); + }); + + it('should return the variation forced for an experiment in the userContext if available', () => { + const { decisionService } = getDecisionService(); + + const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') + .mockImplementation(( + op, + config, + experiment: any, + user, + decideOptions, + userProfileTracker: any, + ) => { + if (experiment.key === 'exp_2') { + return Value.of('sync', { + result: { varationKey: 'variation_2' }, + reasons: [], + }); + } + return Value.of('sync', { + result: {}, + reasons: [], + }); + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 40, + }, + }); + + user.setForcedDecision( + { flagKey: 'flag_1', ruleKey: 'exp_2' }, + { variationKey: 'variation_5' } + ); + + const feature = config.featureKeyMap['flag_1']; + const variation = decisionService.getVariationForFeature(config, feature, user); + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5005'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + }); + + it('should save the variation found for an experiment in the user profile', () => { + const { decisionService, userProfileService } = getDecisionService({ userProfileService: true }); + + const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') + .mockImplementation(( + op, + config, + experiment: any, + user, + decideOptions, + userProfileTracker: any, + ) => { + if (experiment.key === 'exp_2') { + const variation = 'variation_2'; + + userProfileTracker.userProfile[experiment.id] = { + variation_id: '5002', + }; + userProfileTracker.isProfileUpdated = true; + + return Value.of('sync', { + result: { variationKey: 'variation_2' }, + reasons: [], + }); + } + return Value.of('sync', { + result: {}, + reasons: [], + }); + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 40, + }, + }); + + userProfileService?.lookup.mockImplementation((userId: string) => { + if (userId === 'tester') { + return { + user_id: 'tester', + experiment_bucket_map: { + '2001': { + variation_id: '5001', + }, + }, + }; + } + return null; + }); + + + const feature = config.featureKeyMap['flag_1']; + const variation = decisionService.getVariationForFeature(config, feature, user); + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5002'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileService?.lookup).toHaveBeenCalledWith('tester'); + + expect(userProfileService?.save).toHaveBeenCalledTimes(1); + expect(userProfileService?.save).toHaveBeenCalledWith({ + user_id: 'tester', + experiment_bucket_map: { + '2001': { + variation_id: '5001', + }, + '2002': { + variation_id: '5002', + }, + }, + }); + }); + + describe('when no variation is found for any experiment and a targeted delivery \ + audience condition is satisfied', () => { + beforeEach(() => { + mockBucket.mockReset(); + }); + + it('should return variation from the target delivery for which audience condition \ + is satisfied if the user is bucketed into it', () => { + const { decisionService } = getDecisionService(); + + const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') + .mockReturnValue(Value.of('sync', { + result: {}, + reasons: [], + })); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 55, // this should satisfy the audience condition for the targeted delivery with key delivery_2 and delivery_3 + }, + }); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey === 'delivery_2') { + return { + result: '5005', + reasons: [], + }; + } + return { + result: null, + reasons: [], + }; + }); + + const feature = config.featureKeyMap['flag_1']; + const variation = decisionService.getVariationForFeature(config, feature, user); + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['delivery_2'], + variation: config.variationIdMap['5005'], + decisionSource: DECISION_SOURCES.ROLLOUT, + }); + + expect(resolveVariationSpy).toHaveBeenCalledTimes(3); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(1, + 'sync', config, config.experimentKeyMap['exp_1'], user, expect.anything(), expect.anything()); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(2, + 'sync', config, config.experimentKeyMap['exp_2'], user, expect.anything(), expect.anything()); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(3, + 'sync', config, config.experimentKeyMap['exp_3'], user, expect.anything(), expect.anything()); + + expect(mockBucket).toHaveBeenCalledTimes(1); + verifyBucketCall(0, config, config.experimentIdMap['3002'], user); + }); + + it('should return variation from the target delivery and use $opt_bucketing_id attribute as bucketing id', () => { + const { decisionService } = getDecisionService(); + + const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') + .mockReturnValue(Value.of('sync', { + result: {}, + reasons: [], + })); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 55, // this should satisfy the audience condition for the targeted delivery with key delivery_2 and delivery_3 + $opt_bucketing_id: 'test_bucketing_id', + }, + }); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey === 'delivery_2') { + return { + result: '5005', + reasons: [], + }; + } + return { + result: null, + reasons: [], + }; + }); + + const feature = config.featureKeyMap['flag_1']; + const variation = decisionService.getVariationForFeature(config, feature, user); + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['delivery_2'], + variation: config.variationIdMap['5005'], + decisionSource: DECISION_SOURCES.ROLLOUT, + }); + + expect(resolveVariationSpy).toHaveBeenCalledTimes(3); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(1, + 'sync', config, config.experimentKeyMap['exp_1'], user, expect.anything(), expect.anything()); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(2, + 'sync', config, config.experimentKeyMap['exp_2'], user, expect.anything(), expect.anything()); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(3, + 'sync', config, config.experimentKeyMap['exp_3'], user, expect.anything(), expect.anything()); + + expect(mockBucket).toHaveBeenCalledTimes(1); + verifyBucketCall(0, config, config.experimentIdMap['3002'], user, true); + }); + + it('should skip to everyone else targeting rule if the user is not bucketed \ + into the targeted delivery for which audience condition is satisfied', () => { + const { decisionService } = getDecisionService(); + + const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') + .mockReturnValue(Value.of('sync', { + result: {}, + reasons: [], + })); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 55, // this should satisfy the audience condition for the targeted delivery with key delivery_2 and delivery_3 + }, + }); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey === 'default-rollout-key') { + return { + result: '5007', + reasons: [], + }; + } + return { + result: null, + reasons: [], + }; + }); + + const feature = config.featureKeyMap['flag_1']; + const variation = decisionService.getVariationForFeature(config, feature, user); + + expect(variation.result).toEqual({ + experiment: config.experimentIdMap['default-rollout-id'], + variation: config.variationIdMap['5007'], + decisionSource: DECISION_SOURCES.ROLLOUT, + }); + + expect(resolveVariationSpy).toHaveBeenCalledTimes(3); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(1, + 'sync', config, config.experimentKeyMap['exp_1'], user, expect.anything(), expect.anything()); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(2, + 'sync', config, config.experimentKeyMap['exp_2'], user, expect.anything(), expect.anything()); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(3, + 'sync', config, config.experimentKeyMap['exp_3'], user, expect.anything(), expect.anything()); + + expect(mockBucket).toHaveBeenCalledTimes(2); + verifyBucketCall(0, config, config.experimentIdMap['3002'], user); + verifyBucketCall(1, config, config.experimentIdMap['default-rollout-id'], user); + }); + }); + + it('should return the forced variation for targeted delivery rule when no variation \ + is found for any experiment and a there is a forced decision \ + for a targeted delivery in the userContext', () => { + const { decisionService } = getDecisionService(); + + const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') + .mockImplementation(( + op, + config, + experiment: any, + user, + decideOptions, + userProfileTracker: any, + ) => { + return Value.of('sync', { + result: {}, + reasons: [], + }); + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + }); + + user.setForcedDecision( + { flagKey: 'flag_1', ruleKey: 'delivery_2' }, + { variationKey: 'variation_1' } + ); + + const feature = config.featureKeyMap['flag_1']; + const variation = decisionService.getVariationForFeature(config, feature, user); + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['delivery_2'], + variation: config.variationIdMap['5001'], + decisionSource: DECISION_SOURCES.ROLLOUT, + }); + }); + + it('should return variation from the everyone else targeting rule if no variation \ + is found for any experiment or targeted delivery', () => { + const { decisionService } = getDecisionService(); + + const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') + .mockReturnValue(Value.of('sync', { + result: {}, + reasons: [], + })); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 100, // this should not satisfy any audience condition for any targeted delivery + }, + }); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey === 'default-rollout-key') { + return { + result: '5007', + reasons: [], + }; + } + return { + result: null, + reasons: [], + }; + }); + + const feature = config.featureKeyMap['flag_1']; + const variation = decisionService.getVariationForFeature(config, feature, user); + + expect(variation.result).toEqual({ + experiment: config.experimentIdMap['default-rollout-id'], + variation: config.variationIdMap['5007'], + decisionSource: DECISION_SOURCES.ROLLOUT, + }); + + expect(resolveVariationSpy).toHaveBeenCalledTimes(3); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(1, + 'sync', config, config.experimentKeyMap['exp_1'], user, expect.anything(), expect.anything()); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(2, + 'sync', config, config.experimentKeyMap['exp_2'], user, expect.anything(), expect.anything()); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(3, + 'sync', config, config.experimentKeyMap['exp_3'], user, expect.anything(), expect.anything()); + + expect(mockBucket).toHaveBeenCalledTimes(1); + verifyBucketCall(0, config, config.experimentIdMap['default-rollout-id'], user); + }); + + it('should return null if no variation is found for any experiment, targeted delivery, or everyone else targeting rule', () => { + const { decisionService } = getDecisionService(); + + const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') + .mockReturnValue(Value.of('sync', { + result: {}, + reasons: [], + })); + + const config = createProjectConfig(getDecisionTestDatafile()); + const rolloutId = config.featureKeyMap['flag_1'].rolloutId; + config.rolloutIdMap[rolloutId].experiments = []; // remove the experiments from the rollout + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 10, + }, + }); + + const feature = config.featureKeyMap['flag_1']; + const variation = decisionService.getVariationForFeature(config, feature, user); + + expect(variation.result).toEqual({ + experiment: null, + variation: null, + decisionSource: DECISION_SOURCES.ROLLOUT, + }); + + expect(resolveVariationSpy).toHaveBeenCalledTimes(3); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(1, + 'sync', config, config.experimentKeyMap['exp_1'], user, expect.anything(), expect.anything()); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(2, + 'sync', config, config.experimentKeyMap['exp_2'], user, expect.anything(), expect.anything()); + expect(resolveVariationSpy).toHaveBeenNthCalledWith(3, + 'sync', config, config.experimentKeyMap['exp_3'], user, expect.anything(), expect.anything()); + + expect(mockBucket).toHaveBeenCalledTimes(0); + }); + }); + + describe('resolveVariationForFeatureList - async', () => { + beforeEach(() => { + mockBucket.mockReset(); + }); + + it('should return variation from the first experiment for which a variation is available', async () => { + const { decisionService } = getDecisionService(); + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 15, // should satisfy audience condition for all experiments + }, + }); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey === 'exp_2') { + return { + result: '5002', + reasons: [], + }; + } + return { + result: null, + reasons: [], + }; + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5002'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + }); + + it('should not return variation and should not call cmab service \ + for cmab experiment if user is not bucketed into it', async () => { + const { decisionService, cmabService } = getDecisionService(); + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + country: 'BD', + age: 80, // should satisfy audience condition for exp_3 which is cmab and not others + }, + }); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey == 'default-rollout-key') { + return { result: param.trafficAllocationConfig[0].entityId, reasons: [] } + } + return { + result: null, + reasons: [], + } + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['default-rollout-key'], + variation: config.variationIdMap['5007'], + decisionSource: DECISION_SOURCES.ROLLOUT, + }); + + verifyBucketCall(0, config, config.experimentKeyMap['exp_3'], user); + expect(cmabService.getDecision).not.toHaveBeenCalled(); + }); + + it('should get decision from the cmab service if the experiment is a cmab experiment \ + and user is bucketed into it', async () => { + const { decisionService, cmabService } = getDecisionService(); + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + country: 'BD', + age: 80, // should satisfy audience condition for exp_3 which is cmab and not others + }, + }); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey == 'exp_3') { + return { result: param.trafficAllocationConfig[0].entityId, reasons: [] } + } + return { + result: null, + reasons: [], + } + }); + + cmabService.getDecision.mockResolvedValue({ + variationId: '5003', + cmabUuid: 'uuid-test', + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + expect(variation.result).toEqual({ + cmabUuid: 'uuid-test', + experiment: config.experimentKeyMap['exp_3'], + variation: config.variationIdMap['5003'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + verifyBucketCall(0, config, config.experimentKeyMap['exp_3'], user); + + expect(cmabService.getDecision).toHaveBeenCalledTimes(1); + expect(cmabService.getDecision).toHaveBeenCalledWith( + config, + user, + '2003', // id of exp_3 + {}, + ); + }); + + it('should pass the correct DecideOptionMap to cmabService', async () => { + const { decisionService, cmabService } = getDecisionService(); + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + country: 'BD', + age: 80, // should satisfy audience condition for exp_3 which is cmab and not others + }, + }); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey == 'exp_3') { + return { result: param.trafficAllocationConfig[0].entityId, reasons: [] } + } + return { + result: null, + reasons: [], + } + }); + + cmabService.getDecision.mockResolvedValue({ + variationId: '5003', + cmabUuid: 'uuid-test', + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, { + [OptimizelyDecideOption.RESET_CMAB_CACHE]: true, + [OptimizelyDecideOption.INVALIDATE_USER_CMAB_CACHE]: true, + }).get(); + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + expect(variation.result).toEqual({ + cmabUuid: 'uuid-test', + experiment: config.experimentKeyMap['exp_3'], + variation: config.variationIdMap['5003'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(cmabService.getDecision).toHaveBeenCalledTimes(1); + expect(cmabService.getDecision).toHaveBeenCalledWith( + config, + user, + '2003', // id of exp_3 + { + [OptimizelyDecideOption.RESET_CMAB_CACHE]: true, + [OptimizelyDecideOption.INVALIDATE_USER_CMAB_CACHE]: true, + }, + ); + }); + + it('should return error if cmab getDecision fails', async () => { + const { decisionService, cmabService } = getDecisionService(); + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + country: 'BD', + age: 80, // should satisfy audience condition for exp_3 which is cmab and not others + }, + }); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey == 'exp_3') { + return { result: param.trafficAllocationConfig[0].entityId, reasons: [] } + } + return { + result: null, + reasons: [], + } + }); + + cmabService.getDecision.mockRejectedValue(new Error('I am an error')); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.error).toBe(true); + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_3'], + variation: null, + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(variation.reasons).toContainEqual( + [CMAB_FETCH_FAILED, 'exp_3'], + ); + + expect(cmabService.getDecision).toHaveBeenCalledTimes(1); + expect(cmabService.getDecision).toHaveBeenCalledWith( + config, + user, + '2003', // id of exp_3 + {}, + ); + }); + + it('should use userProfileServiceAsync if available and sync user profile service is unavialable', async () => { + const { decisionService, cmabService, userProfileServiceAsync } = getDecisionService({ + userProfileService: false, + userProfileServiceAsync: true, + }); + + userProfileServiceAsync?.lookup.mockImplementation((userId: string) => { + if (userId === 'tester-1') { + return Promise.resolve({ + user_id: 'tester-1', + experiment_bucket_map: { + '2003': { + variation_id: '5001', + }, + }, + }); + } + return Promise.resolve(null); + }); + + userProfileServiceAsync?.save.mockImplementation(() => Promise.resolve()); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey == 'exp_3') { + return { result: param.trafficAllocationConfig[0].entityId, reasons: [] } + } + return { + result: null, + reasons: [], + } + }); + + cmabService.getDecision.mockResolvedValue({ + variationId: '5003', + cmabUuid: 'uuid-test', + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user1 = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester-1', + attributes: { + country: 'BD', + age: 80, // should satisfy audience condition for exp_3 which is cmab and not others + }, + }); + + const user2 = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester-2', + attributes: { + country: 'BD', + age: 80, // should satisfy audience condition for exp_3 which is cmab and not others + }, + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user1, {}).get(); + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_3'], + variation: config.variationIdMap['5001'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(cmabService.getDecision).not.toHaveBeenCalled(); + expect(userProfileServiceAsync?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileServiceAsync?.lookup).toHaveBeenCalledWith('tester-1'); + + const value2 = decisionService.resolveVariationsForFeatureList('async', config, [feature], user2, {}).get(); + expect(value2).toBeInstanceOf(Promise); + + const variation2 = (await value2)[0]; + expect(variation2.result).toEqual({ + cmabUuid: 'uuid-test', + experiment: config.experimentKeyMap['exp_3'], + variation: config.variationIdMap['5003'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(userProfileServiceAsync?.lookup).toHaveBeenCalledTimes(2); + expect(userProfileServiceAsync?.lookup).toHaveBeenNthCalledWith(2, 'tester-2'); + expect(userProfileServiceAsync?.save).toHaveBeenCalledTimes(1); + expect(userProfileServiceAsync?.save).toHaveBeenCalledWith({ + user_id: 'tester-2', + experiment_bucket_map: { + '2003': { + variation_id: '5003', + }, + }, + }); + }); + + it('should log error and perform normal decision fetch if async userProfile lookup fails', async () => { + const { decisionService, cmabService, userProfileServiceAsync, logger } = getDecisionService({ + userProfileService: false, + userProfileServiceAsync: true, + logger: true, + }); + + userProfileServiceAsync?.lookup.mockImplementation((userId: string) => { + return Promise.reject(new Error('I am an error')); + }); + + userProfileServiceAsync?.save.mockImplementation(() => Promise.resolve()); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey == 'exp_3') { + return { result: param.trafficAllocationConfig[0].entityId, reasons: [] } + } + return { + result: null, + reasons: [], + } + }); + + cmabService.getDecision.mockResolvedValue({ + variationId: '5003', + cmabUuid: 'uuid-test', + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + country: 'BD', + age: 80, // should satisfy audience condition for exp_3 which is cmab and not others + }, + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + cmabUuid: 'uuid-test', + experiment: config.experimentKeyMap['exp_3'], + variation: config.variationIdMap['5003'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(userProfileServiceAsync?.lookup).toHaveBeenCalledWith('tester'); + expect(cmabService.getDecision).toHaveBeenCalledTimes(1); + expect(cmabService.getDecision).toHaveBeenCalledWith( + config, + user, + '2003', // id of exp_3 + {}, + ); + + expect(logger?.error).toHaveBeenCalledTimes(1); + expect(logger?.error).toHaveBeenCalledWith(USER_PROFILE_LOOKUP_ERROR, 'tester', 'I am an error'); + }); + + it('should log error async userProfile save fails', async () => { + const { decisionService, cmabService, userProfileServiceAsync, logger } = getDecisionService({ + userProfileService: false, + userProfileServiceAsync: true, + logger: true, + }); + + userProfileServiceAsync?.lookup.mockResolvedValue(null); + + userProfileServiceAsync?.save.mockRejectedValue(new Error('I am an error')); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey == 'exp_3') { + return { result: param.trafficAllocationConfig[0].entityId, reasons: [] } + } + return { + result: null, + reasons: [], + } + }); + + cmabService.getDecision.mockResolvedValue({ + variationId: '5003', + cmabUuid: 'uuid-test', + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + country: 'BD', + age: 80, // should satisfy audience condition for exp_3 which is cmab and not others + }, + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + cmabUuid: 'uuid-test', + experiment: config.experimentKeyMap['exp_3'], + variation: config.variationIdMap['5003'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(userProfileServiceAsync?.lookup).toHaveBeenCalledWith('tester'); + expect(cmabService.getDecision).toHaveBeenCalledTimes(1); + expect(cmabService.getDecision).toHaveBeenCalledWith( + config, + user, + '2003', // id of exp_3 + {}, + ); + + expect(userProfileServiceAsync?.save).toHaveBeenCalledTimes(1); + expect(userProfileServiceAsync?.save).toHaveBeenCalledWith({ + user_id: 'tester', + experiment_bucket_map: { + '2003': { + variation_id: '5003', + }, + }, + }); + expect(logger?.error).toHaveBeenCalledTimes(1); + expect(logger?.error).toHaveBeenCalledWith(USER_PROFILE_SAVE_ERROR, 'tester', 'I am an error'); + }); + + it('should use the sync user profile service if both sync and async ups are provided', async () => { + const { decisionService, userProfileService, userProfileServiceAsync, cmabService } = getDecisionService({ + userProfileService: true, + userProfileServiceAsync: true, + }); + + userProfileService?.lookup.mockReturnValue(null); + userProfileService?.save.mockReturnValue(null); + + userProfileServiceAsync?.lookup.mockResolvedValue(null); + userProfileServiceAsync?.save.mockResolvedValue(null); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey == 'exp_3') { + return { result: param.trafficAllocationConfig[0].entityId, reasons: [] } + } + return { + result: null, + reasons: [], + } + }); + + cmabService.getDecision.mockResolvedValue({ + variationId: '5003', + cmabUuid: 'uuid-test', + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + country: 'BD', + age: 80, // should satisfy audience condition for exp_3 which is cmab and not others + }, + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + cmabUuid: 'uuid-test', + experiment: config.experimentKeyMap['exp_3'], + variation: config.variationIdMap['5003'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileService?.lookup).toHaveBeenCalledWith('tester'); + + expect(userProfileService?.save).toHaveBeenCalledTimes(1); + expect(userProfileService?.save).toHaveBeenCalledWith({ + user_id: 'tester', + experiment_bucket_map: { + '2003': { + variation_id: '5003', + }, + }, + }); + + expect(userProfileServiceAsync?.lookup).not.toHaveBeenCalled(); + expect(userProfileServiceAsync?.save).not.toHaveBeenCalled(); + }); + + describe('holdout', () => { + beforeEach(async() => { + mockHoldoutToggle.mockReturnValue(true); + const actualBucketModule = (await vi.importActual('../bucketer')) as { bucket: typeof bucket }; + mockBucket.mockImplementation(actualBucketModule.bucket); + }); + + it('should return holdout variation when user is bucketed into running holdout', async () => { + const { decisionService } = getDecisionService(); + const config = createProjectConfig(getHoldoutTestDatafile()); + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 20, + }, + }); + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + experiment: config.holdoutIdMap && config.holdoutIdMap['holdout_running_id'], + variation: config.variationIdMap['holdout_variation_running_id'], + decisionSource: DECISION_SOURCES.HOLDOUT, + }); + }); + + it("should consider global holdout even if local holdout is present", async () => { + const { decisionService } = getDecisionService(); + const datafile = getHoldoutTestDatafile(); + const newEntry = { + id: 'holdout_included_id', + key: 'holdout_included', + status: 'Running', + includedFlags: ['1001'], + excludedFlags: [], + audienceIds: ['4002'], // age_40 audience + audienceConditions: ['or', '4002'], + variations: [ + { + id: 'holdout_variation_included_id', + key: 'holdout_variation_included', + variables: [], + }, + ], + trafficAllocation: [ + { + entityId: 'holdout_variation_included_id', + endOfRange: 5000, + }, + ], + }; + datafile.holdouts = [newEntry, ...datafile.holdouts]; + const config = createProjectConfig(datafile); + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 20, // satisfies both global holdout (age_22) and included holdout (age_40) audiences + }, + }); + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + experiment: config.holdoutIdMap && config.holdoutIdMap['holdout_running_id'], + variation: config.variationIdMap['holdout_variation_running_id'], + decisionSource: DECISION_SOURCES.HOLDOUT, + }); + }); + + it("should consider local holdout if misses global holdout", async () => { + const { decisionService } = getDecisionService(); + const datafile = getHoldoutTestDatafile(); + + datafile.holdouts.push({ + id: 'holdout_included_specific_id', + key: 'holdout_included_specific', + status: 'Running', + includedFlags: ['1001'], + excludedFlags: [], + audienceIds: ['4002'], // age_60 audience (age <= 60) + audienceConditions: ['or', '4002'], + variations: [ + { + id: 'holdout_variation_included_specific_id', + key: 'holdout_variation_included_specific', + variables: [] + } + ], + trafficAllocation: [ + { + entityId: 'holdout_variation_included_specific_id', + endOfRange: 5000 + } + ] + }); + const config = createProjectConfig(datafile); + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'test_holdout_user', + attributes: { + age: 50, // Does not satisfy global holdout (age_22, age <= 22) but satisfies included holdout (age_60, age <= 60) + }, + }); + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + experiment: config.holdoutIdMap && config.holdoutIdMap['holdout_included_specific_id'], + variation: config.variationIdMap['holdout_variation_included_specific_id'], + decisionSource: DECISION_SOURCES.HOLDOUT, + }); + }); + + it('should fallback to experiment when holdout status is not running', async () => { + const { decisionService } = getDecisionService(); + const datafile = getHoldoutTestDatafile(); + + datafile.holdouts = datafile.holdouts.map((holdout: Holdout) => { + if(holdout.id === 'holdout_running_id') { + return { + ...holdout, + status: "Draft" + } + } + return holdout; + }); + + const config = createProjectConfig(datafile); + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 15, + }, + }); + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_1'], + variation: config.variationIdMap['5001'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + }); + + it('should fallback to experiment when user does not meet holdout audience conditions', async () => { + const { decisionService } = getDecisionService(); + const config = createProjectConfig(getHoldoutTestDatafile()); + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 30, // does not satisfy age_22 audience condition for holdout_running + }, + }); + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5002'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + }); + + it('should fallback to experiment when user is not bucketed into holdout traffic', async () => { + const { decisionService } = getDecisionService(); + const config = createProjectConfig(getHoldoutTestDatafile()); + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 50, + }, + }); + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5002'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + }); + + it('should fallback to rollout when no holdout or experiment matches', async () => { + const { decisionService } = getDecisionService(); + const datafile = getHoldoutTestDatafile(); + // Modify the datafile to create proper audience conditions for this test + // Make exp_1 and exp_2 use age conditions that won't match our test user + datafile.audiences = datafile.audiences.map((audience: any) => { + if (audience.id === '4001') { // age_22 + return { + ...audience, + conditions: JSON.stringify(["or", {"match": "exact", "name": "age", "type": "custom_attribute", "value": 22}]) + }; + } + if (audience.id === '4002') { // age_60 + return { + ...audience, + conditions: JSON.stringify(["or", {"match": "exact", "name": "age", "type": "custom_attribute", "value": 60}]) + }; + } + return audience; + }); + + // Make exp_2 use a different audience so it won't conflict with delivery_2 + datafile.experiments = datafile.experiments.map((experiment: any) => { + if (experiment.key === 'exp_2') { + return { + ...experiment, + audienceIds: ['4001'], // Change from 4002 to 4001 (age_22) + audienceConditions: ['or', '4001'] + }; + } + return experiment; + }); + + const config = createProjectConfig(datafile); + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 60, // matches audience 4002 (age_60) used by delivery_2, but not experiments + }, + }); + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['delivery_2'], + variation: config.variationIdMap['5005'], + decisionSource: DECISION_SOURCES.ROLLOUT, + }); + }); + + it('should skip holdouts excluded for specific flags', async () => { + const { decisionService } = getDecisionService(); + const datafile = getHoldoutTestDatafile(); + + datafile.holdouts = datafile.holdouts.map((holdout: any) => { + if(holdout.id === 'holdout_running_id') { + return { + ...holdout, + excludedFlags: ['1001'] + } + } + return holdout; + }); + + const config = createProjectConfig(datafile); + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 15, // satisfies age_22 audience condition (age <= 22) for global holdout, but holdout excludes flag_1 + }, + }); + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_1'], + variation: config.variationIdMap['5001'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + }); + + it('should handle multiple holdouts and use first matching one', async () => { + const { decisionService } = getDecisionService(); + const datafile = getHoldoutTestDatafile(); + + datafile.holdouts.push({ + id: 'holdout_second_id', + key: 'holdout_second', + status: 'Running', + includedFlags: [], + excludedFlags: [], + audienceIds: [], // no audience requirements + audienceConditions: [], + variations: [ + { + id: 'holdout_variation_second_id', + key: 'holdout_variation_second', + variables: [] + } + ], + trafficAllocation: [ + { + entityId: 'holdout_variation_second_id', + endOfRange: 5000 + } + ] + }); + + const config = createProjectConfig(datafile); + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 20, // satisfies audience for holdout_running + }, + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('async', config, [feature], user, {}).get(); + + expect(value).toBeInstanceOf(Promise); + + const variation = (await value)[0]; + + expect(variation.result).toEqual({ + experiment: config.holdoutIdMap && config.holdoutIdMap['holdout_running_id'], + variation: config.variationIdMap['holdout_variation_running_id'], + decisionSource: DECISION_SOURCES.HOLDOUT, + }); + }); + }); + }); + + describe('resolveVariationForFeatureList - sync', () => { + beforeEach(() => { + mockBucket.mockReset(); + }); + + it('should skip cmab experiments', async () => { + const { decisionService, cmabService } = getDecisionService(); + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 15, // should satisfy audience condition for all experiments and targeted delivery + }, + }); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey === 'delivery_1') { + return { + result: '5004', + reasons: [], + }; + } + return { + result: null, + reasons: [], + }; + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('sync', config, [feature], user, {}).get(); + + const variation = value[0]; + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['delivery_1'], + variation: config.variationIdMap['5004'], + decisionSource: DECISION_SOURCES.ROLLOUT, + }); + + expect(mockBucket).toHaveBeenCalledTimes(3); + verifyBucketCall(0, config, config.experimentKeyMap['exp_1'], user); + verifyBucketCall(1, config, config.experimentKeyMap['exp_2'], user); + verifyBucketCall(2, config, config.experimentKeyMap['delivery_1'], user); + + expect(cmabService.getDecision).not.toHaveBeenCalled(); + }); + + it('should ignore async user profile service', async () => { + const { decisionService, userProfileServiceAsync } = getDecisionService({ + userProfileService: false, + userProfileServiceAsync: true, + }); + + userProfileServiceAsync?.lookup.mockResolvedValue({ + user_id: 'tester', + experiment_bucket_map: { + '2002': { + variation_id: '5001', + }, + }, + }); + userProfileServiceAsync?.save.mockResolvedValue(null); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey === 'exp_2') { + return { + result: '5002', + reasons: [], + }; + } + return { + result: null, + reasons: [], + }; + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 55, // should satisfy audience condition for exp_2 and exp_3 + }, + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('sync', config, [feature], user, {}).get(); + + const variation = value[0]; + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5002'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(userProfileServiceAsync?.lookup).not.toHaveBeenCalled(); + expect(userProfileServiceAsync?.save).not.toHaveBeenCalled(); + }); + + it('should use sync user profile service', async () => { + const { decisionService, userProfileService, userProfileServiceAsync } = getDecisionService({ + userProfileService: true, + userProfileServiceAsync: true, + }); + + userProfileService?.lookup.mockImplementation((userId: string) => { + if (userId === 'tester-1') { + return { + user_id: 'tester-1', + experiment_bucket_map: { + '2002': { + variation_id: '5001', + }, + }, + }; + } + return null; + }); + + userProfileServiceAsync?.lookup.mockResolvedValue(null); + userProfileServiceAsync?.save.mockResolvedValue(null); + + mockBucket.mockImplementation((param: BucketerParams) => { + const ruleKey = param.experimentKey; + if (ruleKey === 'exp_2') { + return { + result: '5002', + reasons: [], + }; + } + return { + result: null, + reasons: [], + }; + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user1 = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester-1', + attributes: { + age: 55, // should satisfy audience condition for exp_2 and exp_3 + }, + }); + + const feature = config.featureKeyMap['flag_1']; + const value = decisionService.resolveVariationsForFeatureList('sync', config, [feature], user1, {}).get(); + + const variation = value[0]; + + expect(variation.result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5001'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileService?.lookup).toHaveBeenCalledWith('tester-1'); + + const user2 = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester-2', + attributes: { + age: 55, // should satisfy audience condition for exp_2 and exp_3 + }, + }); + + const value2 = decisionService.resolveVariationsForFeatureList('sync', config, [feature], user2, {}).get(); + const variation2 = value2[0]; + expect(variation2.result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5002'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(2); + expect(userProfileService?.lookup).toHaveBeenNthCalledWith(2, 'tester-2'); + expect(userProfileService?.save).toHaveBeenCalledTimes(1); + expect(userProfileService?.save).toHaveBeenCalledWith({ + user_id: 'tester-2', + experiment_bucket_map: { + '2002': { + variation_id: '5002', + }, + }, + }); + + expect(userProfileServiceAsync?.lookup).not.toHaveBeenCalled(); + expect(userProfileServiceAsync?.save).not.toHaveBeenCalled(); + }); + }); + + describe('getVariationsForFeatureList', () => { + beforeEach(() => { + mockBucket.mockReset(); + }); + + it('should return correct results for all features in the feature list', () => { + const { decisionService } = getDecisionService(); + + const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') + .mockImplementation(( + op, + config, + experiment: any, + user, + decideOptions, + userProfileTracker: any, + ) => { + if (experiment.key === 'exp_2') { + return Value.of('sync', { + result: { variationKey: 'variation_2' }, + reasons: [], + }); + } else if (experiment.key === 'exp_4') { + return Value.of('sync', { + result: { variationKey: 'variation_flag_2' }, + reasons: [], + }); + } + return Value.of('sync', { + result: {}, + reasons: [], + }); + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 40, + }, + }); + + const featureList = [ + config.featureKeyMap['flag_1'], + config.featureKeyMap['flag_2'], + ]; + + const variations = decisionService.getVariationsForFeatureList(config, featureList, user); + + expect(variations[0].result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5002'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(variations[1].result).toEqual({ + experiment: config.experimentKeyMap['exp_4'], + variation: config.variationIdMap['5100'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + const variations2 = decisionService.getVariationsForFeatureList(config, featureList.reverse(), user); + + expect(variations2[0].result).toEqual({ + experiment: config.experimentKeyMap['exp_4'], + variation: config.variationIdMap['5100'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(variations2[1].result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5002'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + }); + + it('should batch user profile lookup and save', () => { + const { decisionService, userProfileService } = getDecisionService({ userProfileService: true }); + + const resolveVariationSpy = vi.spyOn(decisionService as any, 'resolveVariation') + .mockImplementation(( + op, + config, + experiment: any, + user, + decideOptions, + userProfileTracker: any, + ) => { + if (experiment.key === 'exp_2') { + userProfileTracker.userProfile[experiment.id] = { + variation_id: '5002', + }; + userProfileTracker.isProfileUpdated = true; + + return Value.of('sync', { + result: { variationKey: 'variation_2' }, + reasons: [], + }); + } else if (experiment.key === 'exp_4') { + userProfileTracker.userProfile[experiment.id] = { + variation_id: '5100', + }; + userProfileTracker.isProfileUpdated = true; + + return Value.of('sync', { + result: { variationKey: 'variation_flag_2' }, + reasons: [], + }); + } + return Value.of('sync', { + result: {}, + reasons: [], + }); + }); + + const config = createProjectConfig(getDecisionTestDatafile()); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + age: 40, + }, + }); + + const featureList = [ + config.featureKeyMap['flag_1'], + config.featureKeyMap['flag_2'], + ]; + + const variations = decisionService.getVariationsForFeatureList(config, featureList, user); + + expect(variations[0].result).toEqual({ + experiment: config.experimentKeyMap['exp_2'], + variation: config.variationIdMap['5002'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(variations[1].result).toEqual({ + experiment: config.experimentKeyMap['exp_4'], + variation: config.variationIdMap['5100'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }); + + expect(userProfileService?.lookup).toHaveBeenCalledTimes(1); + expect(userProfileService?.lookup).toHaveBeenCalledWith('tester'); + + expect(userProfileService?.save).toHaveBeenCalledTimes(1); + expect(userProfileService?.save).toHaveBeenCalledWith({ + user_id: 'tester', + experiment_bucket_map: { + '2002': { + variation_id: '5002', + }, + '2004': { + variation_id: '5100', + }, + }, + }); + }); + }); + + + describe('forced variation management', () => { + it('should return true for a valid forcedVariation in setForcedVariation', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + const didSetVariation = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + 'control' + ); + expect(didSetVariation).toBe(true); + }); + + it('should return the same variation from getVariation as was set in setVariation', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + 'control' + ); + + const variation = decisionService.getForcedVariation(config, 'testExperiment', 'user1').result; + expect(variation).toBe('control'); + }); + + it('should return null from getVariation if no forced variation was set for a valid experimentKey', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + expect(config.experimentKeyMap['testExperiment']).toBeDefined(); + const variation = decisionService.getForcedVariation(config, 'testExperiment', 'user1').result; + + expect(variation).toBe(null); + }); + + it('should return null from getVariation for an invalid experimentKey', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + expect(config.experimentKeyMap['definitely_not_valid_exp_key']).not.toBeDefined(); + const variation = decisionService.getForcedVariation(config, 'definitely_not_valid_exp_key', 'user1').result; + + expect(variation).toBe(null); + }); + + it('should return null when a forced decision is set on another experiment key', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + decisionService.setForcedVariation(config, 'testExperiment', 'user1', 'control'); + const variation = decisionService.getForcedVariation(config, 'testExperimentLaunched', 'user1').result; + expect(variation).toBe(null); + }); + + it('should not set forced variation for an invalid variation key and return false', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + const wasSet = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + 'definitely_not_valid_variation_key' + ); + + expect(wasSet).toBe(false); + const variation = decisionService.getForcedVariation(config, 'testExperiment', 'user1').result; + expect(variation).toBe(null); + }); + + it('should reset the forcedVariation if null is passed to setForcedVariation', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + const didSetVariation = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + 'control' + ); + + expect(didSetVariation).toBe(true); + + let variation = decisionService.getForcedVariation(config, 'testExperiment', 'user1').result; + expect(variation).toBe('control'); + + const didSetVariationAgain = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + null + ); + + expect(didSetVariationAgain).toBe(true); + + variation = decisionService.getForcedVariation(config, 'testExperiment', 'user1').result; + expect(variation).toBe(null); + }); + + it('should be able to add variations for multiple experiments for one user', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + const didSetVariation1 = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + 'control' + ); + expect(didSetVariation1).toBe(true); + + const didSetVariation2 = decisionService.setForcedVariation( + config, + 'testExperimentLaunched', + 'user1', + 'controlLaunched' + ); + expect(didSetVariation2).toBe(true); + + const variation = decisionService.getForcedVariation(config, 'testExperiment', 'user1').result; + const variation2 = decisionService.getForcedVariation(config, 'testExperimentLaunched', 'user1').result; + expect(variation).toBe('control'); + expect(variation2).toBe('controlLaunched'); + }); + + it('should be able to forced variation to same experiment for multiple users', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + const didSetVariation1 = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + 'control' + ); + expect(didSetVariation1).toBe(true); + + const didSetVariation2 = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user2', + 'variation' + ); + expect(didSetVariation2).toBe(true); + + const variationControl = decisionService.getForcedVariation(config, 'testExperiment', 'user1').result; + const variationVariation = decisionService.getForcedVariation(config, 'testExperiment', 'user2').result; + + expect(variationControl).toBe('control'); + expect(variationVariation).toBe('variation'); + }); + + it('should be able to reset a variation for a user with multiple experiments', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + // Set the first time + const didSetVariation1 = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + 'control' + ); + expect(didSetVariation1).toBe(true); + + const didSetVariation2 = decisionService.setForcedVariation( + config, + 'testExperimentLaunched', + 'user1', + 'controlLaunched' + ); + expect(didSetVariation2).toBe(true); + + let variation1 = decisionService.getForcedVariation(config, 'testExperiment', 'user1').result; + let variation2 = decisionService.getForcedVariation(config, 'testExperimentLaunched', 'user1').result; + + expect(variation1).toBe('control'); + expect(variation2).toBe('controlLaunched'); + + // Reset for one of the experiments + const didSetVariationAgain = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + 'variation' + ); + expect(didSetVariationAgain).toBe(true); + + variation1 = decisionService.getForcedVariation(config, 'testExperiment', 'user1').result; + variation2 = decisionService.getForcedVariation(config, 'testExperimentLaunched', 'user1').result; + + expect(variation1).toBe('variation'); + expect(variation2).toBe('controlLaunched'); + }); + + it('should be able to unset a variation for a user with multiple experiments', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + // Set the first time + const didSetVariation1 = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + 'control' + ); + expect(didSetVariation1).toBe(true); + + const didSetVariation2 = decisionService.setForcedVariation( + config, + 'testExperimentLaunched', + 'user1', + 'controlLaunched' + ); + expect(didSetVariation2).toBe(true); + + let variation1 = decisionService.getForcedVariation(config, 'testExperiment', 'user1').result; + let variation2 = decisionService.getForcedVariation(config, 'testExperimentLaunched', 'user1').result; + + expect(variation1).toBe('control'); + expect(variation2).toBe('controlLaunched'); + + // Unset for one of the experiments + decisionService.setForcedVariation(config, 'testExperiment', 'user1', null); + + variation1 = decisionService.getForcedVariation(config, 'testExperiment', 'user1').result; + variation2 = decisionService.getForcedVariation(config, 'testExperimentLaunched', 'user1').result; + + expect(variation1).toBe(null); + expect(variation2).toBe('controlLaunched'); + }); + + it('should return false for an empty variation key', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + const didSetVariation = decisionService.setForcedVariation(config, 'testExperiment', 'user1', ''); + expect(didSetVariation).toBe(false); + }); + + it('should return null when a variation was previously set, and that variation no longer exists on the config object', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + const didSetVariation = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + 'control' + ); + expect(didSetVariation).toBe(true); + + const newDatafile = cloneDeep(testData); + // Remove 'control' variation from variations, traffic allocation, and datafile forcedVariations. + newDatafile.experiments[0].variations = [ + { + key: 'variation', + id: '111129', + }, + ]; + newDatafile.experiments[0].trafficAllocation = [ + { + entityId: '111129', + endOfRange: 9000, + }, + ]; + newDatafile.experiments[0].forcedVariations = { + user1: 'variation', + user2: 'variation', + }; + // Now the only variation in testExperiment is 'variation' + const newConfigObj = createProjectConfig(newDatafile); + const forcedVar = decisionService.getForcedVariation(newConfigObj, 'testExperiment', 'user1').result; + expect(forcedVar).toBe(null); + }); + + it("should return null when a variation was previously set, and that variation's experiment no longer exists on the config object", function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + const didSetVariation = decisionService.setForcedVariation( + config, + 'testExperiment', + 'user1', + 'control' + ); + expect(didSetVariation).toBe(true); + + const newConfigObj = createProjectConfig(cloneDeep(testDataWithFeatures)); + const forcedVar = decisionService.getForcedVariation(newConfigObj, 'testExperiment', 'user1').result; + expect(forcedVar).toBe(null); + }); + + it('should return false from setForcedVariation and not set for invalid experiment key', function() { + const config = createProjectConfig(cloneDeep(testData)); + const { decisionService } = getDecisionService(); + + const didSetVariation = decisionService.setForcedVariation( + config, + 'definitelyNotAValidExperimentKey', + 'user1', + 'control' + ); + expect(didSetVariation).toBe(false); + + const variation = decisionService.getForcedVariation( + config, + 'definitelyNotAValidExperimentKey', + 'user1' + ).result; + expect(variation).toBe(null); + }); + }); +}); diff --git a/packages/optimizely-sdk/lib/core/decision_service/index.tests.js b/lib/core/decision_service/index.tests.js similarity index 62% rename from packages/optimizely-sdk/lib/core/decision_service/index.tests.js rename to lib/core/decision_service/index.tests.js index 5f624a12f..346814857 100644 --- a/packages/optimizely-sdk/lib/core/decision_service/index.tests.js +++ b/lib/core/decision_service/index.tests.js @@ -1,18 +1,18 @@ -/**************************************************************************** - * Copyright 2017-2022 Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ +/** + * Copyright 2017-2022, 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import sinon from 'sinon'; import { assert } from 'chai'; import cloneDeep from 'lodash/cloneDeep'; @@ -20,29 +20,65 @@ import { sprintf } from '../../utils/fns'; import { createDecisionService } from './'; import * as bucketer from '../bucketer'; +import * as bucketValueGenerator from '../bucketer/bucket_value_generator'; import { LOG_LEVEL, DECISION_SOURCES, } from '../../utils/enums'; -import { createLogger } from '../../plugins/logger'; -import { createForwardingEventProcessor } from '../../plugins/event_processor/forwarding_event_processor'; -import { createNotificationCenter } from '../notification_center'; +import { getForwardingEventProcessor } from '../../event_processor/event_processor_factory'; +import { createNotificationCenter } from '../../notification_center'; import Optimizely from '../../optimizely'; import OptimizelyUserContext from '../../optimizely_user_context'; -import projectConfig from '../project_config'; +import projectConfig, { createProjectConfig } from '../../project_config/project_config'; import AudienceEvaluator from '../audience_evaluator'; -import errorHandler from '../../plugins/error_handler'; -import eventDispatcher from '../../plugins/event_dispatcher/index.node'; +import eventDispatcher from '../../event_processor/event_dispatcher/default_dispatcher.browser'; import * as jsonSchemaValidator from '../../utils/json_schema_validator'; +import { getMockProjectConfigManager } from '../../tests/mock/mock_project_config_manager'; +import { Value } from '../../utils/promise/operation_value'; + import { getTestProjectConfig, getTestProjectConfigWithFeatures, } from '../../tests/test_data'; +import { + USER_HAS_NO_FORCED_VARIATION, + VALID_BUCKETING_ID, + SAVED_USER_VARIATION, + SAVED_VARIATION_NOT_FOUND, +} from 'log_message'; + +import { + EXPERIMENT_NOT_RUNNING, + RETURNING_STORED_VARIATION, + USER_NOT_IN_EXPERIMENT, + USER_FORCED_IN_VARIATION, + EVALUATING_AUDIENCES_COMBINED, + AUDIENCE_EVALUATION_RESULT_COMBINED, + USER_IN_ROLLOUT, + USER_NOT_IN_ROLLOUT, + FEATURE_HAS_NO_EXPERIMENTS, + USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, + USER_NOT_BUCKETED_INTO_TARGETING_RULE, + USER_BUCKETED_INTO_TARGETING_RULE, + NO_ROLLOUT_EXISTS, + USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, +} from '../decision_service/index'; + +import { BUCKETING_ID_NOT_STRING, USER_PROFILE_LOOKUP_ERROR, USER_PROFILE_SAVE_ERROR } from 'error_message'; + var testData = getTestProjectConfig(); var testDataWithFeatures = getTestProjectConfigWithFeatures(); var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); +var createLogger = () => ({ + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + child: () => createLogger(), +}) + describe('lib/core/decision_service', function() { describe('APIs', function() { var configObj = projectConfig.createProjectConfig(cloneDeep(testData)); @@ -54,7 +90,11 @@ describe('lib/core/decision_service', function() { beforeEach(function() { bucketerStub = sinon.stub(bucketer, 'bucket'); - sinon.stub(mockLogger, 'log'); + sinon.stub(mockLogger, 'info'); + sinon.stub(mockLogger, 'debug'); + sinon.stub(mockLogger, 'warn'); + sinon.stub(mockLogger, 'error'); + decisionServiceInstance = createDecisionService({ logger: mockLogger, }); @@ -62,12 +102,16 @@ describe('lib/core/decision_service', function() { afterEach(function() { bucketer.bucket.restore(); - mockLogger.log.restore(); + mockLogger.debug.restore(); + mockLogger.info.restore(); + mockLogger.warn.restore(); + mockLogger.error.restore(); }); describe('#getVariation', function() { it('should return the correct variation for the given experiment key and user ID for a running experiment', function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'tester' }); @@ -86,6 +130,7 @@ describe('lib/core/decision_service', function() { it('should return the whitelisted variation if the user is whitelisted', function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user2' }); @@ -95,19 +140,17 @@ describe('lib/core/decision_service', function() { decisionServiceInstance.getVariation(configObj, experiment, user).result ); sinon.assert.notCalled(bucketerStub); - assert.strictEqual(2, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: User user2 is not in the forced variation map.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: User user2 is forced in variation variationWithAudience.' - ); + assert.strictEqual(1, mockLogger.debug.callCount); + assert.strictEqual(1, mockLogger.info.callCount); + + assert.deepEqual(mockLogger.debug.args[0], [USER_HAS_NO_FORCED_VARIATION, 'user2']); + + assert.deepEqual(mockLogger.info.args[0], [USER_FORCED_IN_VARIATION, 'user2', 'variationWithAudience']); }); it('should return null if the user does not meet audience conditions', function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user3' }); @@ -115,38 +158,28 @@ describe('lib/core/decision_service', function() { assert.isNull( decisionServiceInstance.getVariation(configObj, experiment, user, { foo: 'bar' }).result ); - assert.strictEqual(4, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: User user3 is not in the forced variation map.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: Evaluating audiences for experiment "testExperimentWithAudiences": ["11154"].' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[2]), - 'DECISION_SERVICE: Audiences for experiment testExperimentWithAudiences collectively evaluated to FALSE.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[3]), - 'DECISION_SERVICE: User user3 does not meet conditions to be in experiment testExperimentWithAudiences.' - ); + + assert.deepEqual(mockLogger.debug.args[0], [USER_HAS_NO_FORCED_VARIATION, 'user3']); + + assert.deepEqual(mockLogger.debug.args[1], [EVALUATING_AUDIENCES_COMBINED, 'experiment', 'testExperimentWithAudiences', JSON.stringify(["11154"])]); + + assert.deepEqual(mockLogger.info.args[0], [AUDIENCE_EVALUATION_RESULT_COMBINED, 'experiment', 'testExperimentWithAudiences', 'FALSE']); + + assert.deepEqual(mockLogger.info.args[1], [USER_NOT_IN_EXPERIMENT, 'user3', 'testExperimentWithAudiences']); }); it('should return null if the experiment is not running', function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1' }); experiment = configObj.experimentIdMap['133337']; assert.isNull(decisionServiceInstance.getVariation(configObj, experiment, user).result); sinon.assert.notCalled(bucketerStub); - assert.strictEqual(1, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: Experiment testExperimentNotRunning is not running.' - ); + assert.strictEqual(1, mockLogger.info.callCount); + + assert.deepEqual(mockLogger.info.args[0], [EXPERIMENT_NOT_RUNNING, 'testExperimentNotRunning']); }); describe('when attributes.$opt_experiment_bucket_map is supplied', function() { @@ -165,6 +198,7 @@ describe('lib/core/decision_service', function() { }, }; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', attributes, @@ -222,6 +256,7 @@ describe('lib/core/decision_service', function() { }); experiment = configObj.experimentIdMap['111127']; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', }); @@ -232,14 +267,10 @@ describe('lib/core/decision_service', function() { ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.notCalled(bucketerStub); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: User decision_service_user is not in the forced variation map.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: Returning previously activated variation "control" of experiment "testExperiment" for user "decision_service_user" from user profile.' - ); + + assert.deepEqual(mockLogger.debug.args[0], [USER_HAS_NO_FORCED_VARIATION, 'decision_service_user']); + + assert.deepEqual(mockLogger.info.args[0], [RETURNING_STORED_VARIATION, 'control', 'testExperiment', 'decision_service_user']); }); it('should bucket if there was no prevously bucketed variation', function() { @@ -250,6 +281,7 @@ describe('lib/core/decision_service', function() { }); experiment = configObj.experimentIdMap['111127']; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', }); @@ -276,6 +308,7 @@ describe('lib/core/decision_service', function() { userProfileLookupStub.returns(null); experiment = configObj.experimentIdMap['111127']; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', }); @@ -308,6 +341,7 @@ describe('lib/core/decision_service', function() { }); experiment = configObj.experimentIdMap['111127']; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', }); @@ -317,14 +351,20 @@ describe('lib/core/decision_service', function() { ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.calledOnce(bucketerStub); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: User decision_service_user is not in the forced variation map.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: User decision_service_user was previously bucketed into variation with ID not valid variation for experiment testExperiment, but no matching variation was found.' + // assert.strictEqual( + // buildLogMessageFromArgs(mockLogger.log.args[0]), + // 'DECISION_SERVICE: User decision_service_user is not in the forced variation map.' + // ); + assert.deepEqual(mockLogger.debug.args[0], [USER_HAS_NO_FORCED_VARIATION, 'decision_service_user']); + + sinon.assert.calledWith( + mockLogger.info, + SAVED_VARIATION_NOT_FOUND, + 'decision_service_user', + 'not valid variation', + 'testExperiment' ); + // make sure we save the decision sinon.assert.calledWith(userProfileSaveStub, { user_id: 'decision_service_user', @@ -343,6 +383,7 @@ describe('lib/core/decision_service', function() { experiment_bucket_map: {}, // no decisions for user }); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', }); @@ -354,7 +395,7 @@ describe('lib/core/decision_service', function() { ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.calledOnce(bucketerStub); - assert.strictEqual(5, mockLogger.log.callCount); + sinon.assert.calledWith(userProfileServiceInstance.save, { user_id: 'decision_service_user', experiment_bucket_map: { @@ -363,14 +404,11 @@ describe('lib/core/decision_service', function() { }, }, }); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: User decision_service_user is not in the forced variation map.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[4]), - 'DECISION_SERVICE: Saved variation "control" of experiment "testExperiment" for user "decision_service_user".' - ); + + + assert.deepEqual(mockLogger.debug.args[0], [USER_HAS_NO_FORCED_VARIATION, 'decision_service_user']); + + assert.deepEqual(mockLogger.info.lastCall.args, [SAVED_USER_VARIATION, 'decision_service_user']); }); it('should log an error message if "lookup" throws an error', function() { @@ -378,23 +416,21 @@ describe('lib/core/decision_service', function() { userProfileLookupStub.throws(new Error('I am an error')); experiment = configObj.experimentIdMap['111127']; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', }); + assert.strictEqual( 'control', decisionServiceInstance.getVariation(configObj, experiment, user).result ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.calledOnce(bucketerStub); // should still go through with bucketing - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: User decision_service_user is not in the forced variation map.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: Error while looking up user profile for user ID "decision_service_user": I am an error.' - ); + + assert.deepEqual(mockLogger.error.args[0], [USER_PROFILE_LOOKUP_ERROR, 'decision_service_user', 'I am an error']); + + assert.deepEqual(mockLogger.debug.args[0], [USER_HAS_NO_FORCED_VARIATION, 'decision_service_user']); }); it('should log an error message if "save" throws an error', function() { @@ -403,6 +439,7 @@ describe('lib/core/decision_service', function() { userProfileSaveStub.throws(new Error('I am an error')); experiment = configObj.experimentIdMap['111127']; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', }); @@ -413,15 +450,10 @@ describe('lib/core/decision_service', function() { sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.calledOnce(bucketerStub); // should still go through with bucketing - assert.strictEqual(5, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: User decision_service_user is not in the forced variation map.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[4]), - 'DECISION_SERVICE: Error while saving user profile for user ID "decision_service_user": I am an error.' - ); + + assert.deepEqual(mockLogger.debug.args[0], [USER_HAS_NO_FORCED_VARIATION, 'decision_service_user']); + + assert.deepEqual(mockLogger.error.args[0], [USER_PROFILE_SAVE_ERROR, 'decision_service_user', 'I am an error']); // make sure that we save the decision sinon.assert.calledWith(userProfileSaveStub, { @@ -456,6 +488,7 @@ describe('lib/core/decision_service', function() { experiment = configObj.experimentIdMap['111127']; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', attributes, @@ -467,14 +500,10 @@ describe('lib/core/decision_service', function() { ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.notCalled(bucketerStub); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: User decision_service_user is not in the forced variation map.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: Returning previously activated variation "variation" of experiment "testExperiment" for user "decision_service_user" from user profile.' - ); + + assert.deepEqual(mockLogger.debug.args[0], [USER_HAS_NO_FORCED_VARIATION, 'decision_service_user']); + + assert.deepEqual(mockLogger.info.args[0], [RETURNING_STORED_VARIATION, 'variation', 'testExperiment', 'decision_service_user']); }); it('should ignore attributes for a different experiment id', function() { @@ -500,6 +529,7 @@ describe('lib/core/decision_service', function() { }; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', attributes, @@ -511,14 +541,10 @@ describe('lib/core/decision_service', function() { ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.notCalled(bucketerStub); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: User decision_service_user is not in the forced variation map.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: Returning previously activated variation "control" of experiment "testExperiment" for user "decision_service_user" from user profile.' - ); + + assert.deepEqual(mockLogger.debug.args[0], [USER_HAS_NO_FORCED_VARIATION, 'decision_service_user']); + + assert.deepEqual(mockLogger.info.args[0], [RETURNING_STORED_VARIATION, 'control', 'testExperiment', 'decision_service_user']); }); it('should use attributes when the userProfileLookup variations for other experiments', function() { @@ -544,6 +570,7 @@ describe('lib/core/decision_service', function() { }; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', attributes, @@ -555,14 +582,10 @@ describe('lib/core/decision_service', function() { ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.notCalled(bucketerStub); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: User decision_service_user is not in the forced variation map.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: Returning previously activated variation "variation" of experiment "testExperiment" for user "decision_service_user" from user profile.' - ); + + assert.deepEqual(mockLogger.debug.args[0], [USER_HAS_NO_FORCED_VARIATION, 'decision_service_user']); + + assert.deepEqual(mockLogger.info.args[0], [RETURNING_STORED_VARIATION, 'variation', 'testExperiment', 'decision_service_user']); }); it('should use attributes when the userProfileLookup returns null', function() { @@ -579,6 +602,7 @@ describe('lib/core/decision_service', function() { }; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'decision_service_user', attributes, @@ -590,14 +614,10 @@ describe('lib/core/decision_service', function() { ); sinon.assert.calledWith(userProfileLookupStub, 'decision_service_user'); sinon.assert.notCalled(bucketerStub); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: User decision_service_user is not in the forced variation map.' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: Returning previously activated variation "variation" of experiment "testExperiment" for user "decision_service_user" from user profile.' - ); + + assert.deepEqual(mockLogger.debug.args[0], [USER_HAS_NO_FORCED_VARIATION, 'decision_service_user']); + + assert.deepEqual(mockLogger.info.args[0], [RETURNING_STORED_VARIATION, 'variation', 'testExperiment', 'decision_service_user']); }); }); }); @@ -633,21 +653,10 @@ describe('lib/core/decision_service', function() { experimentIdMap: configObj.experimentIdMap, experimentKeyMap: configObj.experimentKeyMap, groupIdMap: configObj.groupIdMap, + validateEntity: true, }; assert.deepEqual(bucketerParams, expectedParams); - - sinon.assert.notCalled(mockLogger.log); - }); - }); - - describe('checkIfExperimentIsActive', function() { - it('should return true if experiment is running', function() { - assert.isTrue(decisionServiceInstance.checkIfExperimentIsActive(configObj, 'testExperiment')); - }); - - it('should return false when experiment is not running', function() { - assert.isFalse(decisionServiceInstance.checkIfExperimentIsActive(configObj, 'testExperimentNotRunning')); }); }); @@ -673,15 +682,10 @@ describe('lib/core/decision_service', function() { '' ).result ); - assert.strictEqual(2, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: Evaluating audiences for experiment "testExperimentWithAudiences": ["11154"].' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: Audiences for experiment testExperimentWithAudiences collectively evaluated to TRUE.' - ); + + assert.deepEqual(mockLogger.debug.args[0], [EVALUATING_AUDIENCES_COMBINED, 'experiment', 'testExperimentWithAudiences', JSON.stringify(["11154"])]); + + assert.deepEqual(mockLogger.info.args[0], [AUDIENCE_EVALUATION_RESULT_COMBINED, 'experiment', 'testExperimentWithAudiences', 'TRUE']); }); it('should return decision response with result true when experiment has no audience', function() { @@ -697,15 +701,9 @@ describe('lib/core/decision_service', function() { ); assert.isTrue(__audienceEvaluateSpy.alwaysReturned(true)); - assert.strictEqual(2, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: Evaluating audiences for experiment "testExperiment": [].' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: Audiences for experiment testExperiment collectively evaluated to TRUE.' - ); + assert.deepEqual(mockLogger.debug.args[0], [EVALUATING_AUDIENCES_COMBINED, 'experiment', 'testExperiment', JSON.stringify([])]); + + assert.deepEqual(mockLogger.info.args[0], [AUDIENCE_EVALUATION_RESULT_COMBINED, 'experiment', 'testExperiment', 'TRUE']); }); it('should return decision response with result false when audience conditions can not be evaluated', function() { @@ -721,15 +719,9 @@ describe('lib/core/decision_service', function() { ); assert.isTrue(__audienceEvaluateSpy.alwaysReturned(false)); - assert.strictEqual(2, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: Evaluating audiences for experiment "testExperimentWithAudiences": ["11154"].' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: Audiences for experiment testExperimentWithAudiences collectively evaluated to FALSE.' - ); + assert.deepEqual(mockLogger.debug.args[0], [EVALUATING_AUDIENCES_COMBINED, 'experiment', 'testExperimentWithAudiences', JSON.stringify(["11154"])]); + + assert.deepEqual(mockLogger.info.args[0], [AUDIENCE_EVALUATION_RESULT_COMBINED, 'experiment', 'testExperimentWithAudiences', 'FALSE']); }); it('should return decision response with result false when audience conditions are not met', function() { @@ -745,15 +737,10 @@ describe('lib/core/decision_service', function() { ); assert.isTrue(__audienceEvaluateSpy.alwaysReturned(false)); - assert.strictEqual(2, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: Evaluating audiences for experiment "testExperimentWithAudiences": ["11154"].' - ); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[1]), - 'DECISION_SERVICE: Audiences for experiment testExperimentWithAudiences collectively evaluated to FALSE.' - ); + + assert.deepEqual(mockLogger.debug.args[0], [EVALUATING_AUDIENCES_COMBINED, 'experiment', 'testExperimentWithAudiences', JSON.stringify(["11154"])]); + + assert.deepEqual(mockLogger.info.args[0], [AUDIENCE_EVALUATION_RESULT_COMBINED, 'experiment', 'testExperimentWithAudiences', 'FALSE']); }); }); @@ -1051,22 +1038,21 @@ describe('lib/core/decision_service', function() { beforeEach(function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: cloneDeep(testData), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(cloneDeep(testData)) + }), jsonSchemaValidator: jsonSchemaValidator, isValidInstance: true, logger: createdLogger, - eventProcessor: createForwardingEventProcessor(eventDispatcher), - notificationCenter: createNotificationCenter(createdLogger, errorHandler), - errorHandler: errorHandler, + eventProcessor: getForwardingEventProcessor(eventDispatcher), + notificationCenter: createNotificationCenter(createdLogger), }); sinon.stub(eventDispatcher, 'dispatchEvent'); - sinon.stub(errorHandler, 'handleError'); }); afterEach(function() { eventDispatcher.dispatchEvent.restore(); - errorHandler.handleError.restore(); }); var testUserAttributes = { @@ -1146,6 +1132,7 @@ describe('lib/core/decision_service', function() { }, }); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'test_user', attributes: userAttributesWithBucketingId, @@ -1180,7 +1167,11 @@ describe('lib/core/decision_service', function() { }; beforeEach(function() { - sinon.stub(mockLogger, 'log'); + sinon.stub(mockLogger, 'debug'); + sinon.stub(mockLogger, 'info'); + sinon.stub(mockLogger, 'warn'); + sinon.stub(mockLogger, 'error'); + configObj = projectConfig.createProjectConfig(cloneDeep(testData)); decisionService = createDecisionService({ logger: mockLogger, @@ -1188,7 +1179,10 @@ describe('lib/core/decision_service', function() { }); afterEach(function() { - mockLogger.log.restore(); + mockLogger.debug.restore(); + mockLogger.info.restore(); + mockLogger.warn.restore(); + mockLogger.error.restore(); }); it('should return userId if bucketingId is not defined in user attributes', function() { @@ -1198,17 +1192,13 @@ describe('lib/core/decision_service', function() { it('should log warning in case of invalid bucketingId', function() { assert.strictEqual(userId, decisionService.getBucketingId(userId, userAttributesWithInvalidBucketingId)); - assert.strictEqual(1, mockLogger.log.callCount); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[0]), - 'DECISION_SERVICE: BucketingID attribute is not a string. Defaulted to userId' - ); + assert.deepEqual(mockLogger.warn.args[0], [BUCKETING_ID_NOT_STRING]); }); it('should return correct bucketingId when provided in attributes', function() { assert.strictEqual('123456789', decisionService.getBucketingId(userId, userAttributesWithBucketingId)); - assert.strictEqual(1, mockLogger.log.callCount); - assert.strictEqual(buildLogMessageFromArgs(mockLogger.log.args[0]), 'DECISION_SERVICE: BucketingId is valid: "123456789"'); + assert.strictEqual(1, mockLogger.debug.callCount); + assert.deepEqual(mockLogger.debug.args[0], [VALID_BUCKETING_ID, '123456789']); }); }); @@ -1219,15 +1209,19 @@ describe('lib/core/decision_service', function() { var sandbox; var mockLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); var fakeDecisionResponseWithArgs; - var fakeDecisionResponse = { - result: null, + var fakeDecisionResponse = Value.of('sync', { + result: {}, reasons: [], - }; + }); var user; beforeEach(function() { configObj = projectConfig.createProjectConfig(cloneDeep(testDataWithFeatures)); sandbox = sinon.sandbox.create(); - sandbox.stub(mockLogger, 'log'); + sandbox.stub(mockLogger, 'debug'); + sandbox.stub(mockLogger, 'info'); + sandbox.stub(mockLogger, 'warn'); + sandbox.stub(mockLogger, 'error'); + decisionServiceInstance = createDecisionService({ logger: mockLogger, }); @@ -1248,239 +1242,41 @@ describe('lib/core/decision_service', function() { var experiment; beforeEach(function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { test_attribute: 'test_value', }, }); - fakeDecisionResponseWithArgs = { - result: 'variation', + fakeDecisionResponseWithArgs = Value.of('sync', { + result: { variationKey: 'variation' }, reasons: [], - }; + }); experiment = configObj.experimentIdMap['594098']; - getVariationStub = sandbox.stub(decisionServiceInstance, 'getVariation'); + getVariationStub = sandbox.stub(decisionServiceInstance, 'resolveVariation'); getVariationStub.returns(fakeDecisionResponse); - getVariationStub.withArgs(configObj, experiment, user).returns(fakeDecisionResponseWithArgs); + getVariationStub.withArgs('sync', configObj, experiment, user, sinon.match.any, sinon.match.any).returns(fakeDecisionResponseWithArgs); }); it('returns a decision with a variation in the experiment the feature is attached to', function() { var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; - var expectedDecision = { - experiment: { - forcedVariations: {}, - status: 'Running', - key: 'testing_my_feature', - id: '594098', - variations: [ - { - id: '594096', - variables: [ - { - id: '4792309476491264', - value: '2', - }, - { - id: '5073784453201920', - value: 'true', - }, - { - id: '5636734406623232', - value: 'Buy me NOW', - }, - { - id: '6199684360044544', - value: '20.25', - }, - { - id: '1547854156498475', - value: '{ "num_buttons": 1, "text": "first variation"}', - }, - ], - featureEnabled: true, - key: 'variation', - }, - { - id: '594097', - variables: [ - { - id: '4792309476491264', - value: '10', - }, - { - id: '5073784453201920', - value: 'false', - }, - { - id: '5636734406623232', - value: 'Buy me', - }, - { - id: '6199684360044544', - value: '50.55', - }, - { - id: '1547854156498475', - value: '{ "num_buttons": 2, "text": "second variation"}', - }, - ], - featureEnabled: true, - key: 'control', - }, - { - id: '594099', - variables: [ - { - id: '4792309476491264', - value: '40', - }, - { - id: '5073784453201920', - value: 'true', - }, - { - id: '5636734406623232', - value: 'Buy me Later', - }, - { - id: '6199684360044544', - value: '99.99', - }, - { - id: '1547854156498475', - value: '{ "num_buttons": 3, "text": "third variation"}', - }, - ], - featureEnabled: false, - key: 'variation2', - }, - ], - audienceIds: [], - trafficAllocation: [ - { endOfRange: 5000, entityId: '594096' }, - { endOfRange: 10000, entityId: '594097' }, - ], - layerId: '594093', - variationKeyMap: { - control: { - id: '594097', - variables: [ - { - id: '4792309476491264', - value: '10', - }, - { - id: '5073784453201920', - value: 'false', - }, - { - id: '5636734406623232', - value: 'Buy me', - }, - { - id: '6199684360044544', - value: '50.55', - }, - { - id: '1547854156498475', - value: '{ "num_buttons": 2, "text": "second variation"}', - }, - ], - featureEnabled: true, - key: 'control', - }, - variation: { - id: '594096', - variables: [ - { - id: '4792309476491264', - value: '2', - }, - { - id: '5073784453201920', - value: 'true', - }, - { - id: '5636734406623232', - value: 'Buy me NOW', - }, - { - id: '6199684360044544', - value: '20.25', - }, - { - id: '1547854156498475', - value: '{ "num_buttons": 1, "text": "first variation"}', - }, - ], - featureEnabled: true, - key: 'variation', - }, - variation2: { - id: '594099', - variables: [ - { - id: '4792309476491264', - value: '40', - }, - { - id: '5073784453201920', - value: 'true', - }, - { - id: '5636734406623232', - value: 'Buy me Later', - }, - { - id: '6199684360044544', - value: '99.99', - }, - { - id: '1547854156498475', - value: '{ "num_buttons": 3, "text": "third variation"}', - }, - ], - featureEnabled: false, - key: 'variation2', - }, - }, - }, - variation: { - id: '594096', - variables: [ - { - id: '4792309476491264', - value: '2', - }, - { - id: '5073784453201920', - value: 'true', - }, - { - id: '5636734406623232', - value: 'Buy me NOW', - }, - { - id: '6199684360044544', - value: '20.25', - }, - { - id: '1547854156498475', - value: '{ "num_buttons": 1, "text": "first variation"}', - }, - ], - featureEnabled: true, - key: 'variation', - }, + const expectedDecision = { + cmabUuid: undefined, + experiment: configObj.experimentIdMap['594098'], + variation: configObj.variationIdMap['594096'], decisionSource: DECISION_SOURCES.FEATURE_TEST, }; + assert.deepEqual(decision, expectedDecision); - sinon.assert.calledWithExactly( + sinon.assert.calledWith( getVariationStub, + 'sync', configObj, experiment, user, - {} + sinon.match.any, + sinon.match.any ); }); }); @@ -1489,10 +1285,11 @@ describe('lib/core/decision_service', function() { var getVariationStub; beforeEach(function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', }); - getVariationStub = sandbox.stub(decisionServiceInstance, 'getVariation'); + getVariationStub = sandbox.stub(decisionServiceInstance, 'resolveVariation'); getVariationStub.returns(fakeDecisionResponse); }); @@ -1504,10 +1301,8 @@ describe('lib/core/decision_service', function() { decisionSource: DECISION_SOURCES.ROLLOUT, }; assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.lastCall.args), - 'DECISION_SERVICE: User user1 is not in rollout of feature test_feature_for_experiment.' - ); + + assert.deepEqual(mockLogger.debug.lastCall.args, [USER_NOT_IN_ROLLOUT, 'user1', 'test_feature_for_experiment']); }); }); }); @@ -1523,14 +1318,15 @@ describe('lib/core/decision_service', function() { var user; beforeEach(function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', }); - fakeDecisionResponseWithArgs = { - result: 'var', + fakeDecisionResponseWithArgs = Value.of('sync', { + result: { variationKey: 'var' }, reasons: [], - }; - getVariationStub = sandbox.stub(decisionServiceInstance, 'getVariation'); + }); + getVariationStub = sandbox.stub(decisionServiceInstance, 'resolveVariation'); getVariationStub.returns(fakeDecisionResponseWithArgs); getVariationStub.withArgs(configObj, 'exp_with_group', user).returns(fakeDecisionResponseWithArgs); }); @@ -1538,42 +1334,12 @@ describe('lib/core/decision_service', function() { it('returns a decision with a variation in an experiment in a group', function() { var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; var expectedDecision = { - experiment: { - forcedVariations: {}, - status: 'Running', - key: 'exp_with_group', - id: '595010', - variations: [ - { id: '595008', variables: [], key: 'var' }, - { id: '595009', variables: [], key: 'con' }, - ], - audienceIds: [], - trafficAllocation: [ - { endOfRange: 5000, entityId: '595008' }, - { endOfRange: 10000, entityId: '595009' }, - ], - layerId: '595005', - groupId: '595024', - variationKeyMap: { - con: { - id: '595009', - variables: [], - key: 'con', - }, - var: { - id: '595008', - variables: [], - key: 'var', - }, - }, - }, - variation: { - id: '595008', - variables: [], - key: 'var', - }, + cmabUuid: undefined, + experiment: configObj.experimentIdMap['595010'], + variation: configObj.variationIdMap['595008'], decisionSource: DECISION_SOURCES.FEATURE_TEST, - }; + }; + assert.deepEqual(decision, expectedDecision); }); }); @@ -1583,10 +1349,11 @@ describe('lib/core/decision_service', function() { var user; beforeEach(function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', }); - getVariationStub = sandbox.stub(decisionServiceInstance, 'getVariation'); + getVariationStub = sandbox.stub(decisionServiceInstance, 'resolveVariation'); getVariationStub.returns(fakeDecisionResponse); }); @@ -1598,10 +1365,8 @@ describe('lib/core/decision_service', function() { decisionSource: DECISION_SOURCES.ROLLOUT, }; assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.lastCall.args), - 'DECISION_SERVICE: User user1 is not in rollout of feature feature_with_group.' - ); + + assert.deepEqual(mockLogger.debug.lastCall.args, [USER_NOT_IN_ROLLOUT, 'user1', 'feature_with_group']); }); it('returns null decision for group experiment not referenced by the feature', function() { @@ -1614,10 +1379,9 @@ describe('lib/core/decision_service', function() { }; assert.deepEqual(decision, expectedDecision); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: There is no rollout of feature %s.', - 'DECISION_SERVICE', 'feature_exp_no_traffic' + mockLogger.debug, + NO_ROLLOUT_EXISTS, + 'feature_exp_no_traffic' ); }); }); @@ -1642,127 +1406,34 @@ describe('lib/core/decision_service', function() { it('returns a decision with a variation and experiment from the audience targeting rule', function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { test_attribute: 'test_value' }, }); var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; var expectedDecision = { - experiment: { - forcedVariations: {}, - status: 'Not started', - key: '594031', - id: '594031', - variations: [ - { - id: '594032', - variables: [ - { - id: '4919852825313280', - value: 'true', - }, - { - id: '5482802778734592', - value: '395', - }, - { - id: '6045752732155904', - value: '4.99', - }, - { - id: '6327227708866560', - value: 'Hello audience', - }, - { - id: "8765345281230956", - value: '{ "count": 2, "message": "Hello audience" }', - }, - ], - featureEnabled: true, - key: '594032', - }, - ], - variationKeyMap: { - 594032: { - id: '594032', - variables: [ - { - id: '4919852825313280', - value: 'true', - }, - { - id: '5482802778734592', - value: '395', - }, - { - id: '6045752732155904', - value: '4.99', - }, - { - id: '6327227708866560', - value: 'Hello audience', - }, - { - id: "8765345281230956", - value: '{ "count": 2, "message": "Hello audience" }', - }, - ], - featureEnabled: true, - key: '594032', - }, - }, - audienceIds: ['594017'], - trafficAllocation: [{ endOfRange: 5000, entityId: '594032' }], - layerId: '594030', - }, - variation: { - id: '594032', - variables: [ - { - id: '4919852825313280', - value: 'true', - }, - { - id: '5482802778734592', - value: '395', - }, - { - id: '6045752732155904', - value: '4.99', - }, - { - id: '6327227708866560', - value: 'Hello audience', - }, - { - id: "8765345281230956", - value: '{ "count": 2, "message": "Hello audience" }', - }, - ], - featureEnabled: true, - key: '594032', - }, + experiment: configObj.experimentIdMap['594031'], + variation: configObj.variationIdMap['594032'], decisionSource: DECISION_SOURCES.ROLLOUT, }; + assert.deepEqual(decision, expectedDecision); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s meets conditions for targeting rule %s.', - 'DECISION_SERVICE', 'user1', 1 + mockLogger.debug, + USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, + 'user1', 1 ); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s bucketed into targeting rule %s.', - 'DECISION_SERVICE', 'user1', 1 + mockLogger.debug, + USER_BUCKETED_INTO_TARGETING_RULE, + 'user1', 1 ); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s is in rollout of feature %s.', - 'DECISION_SERVICE', 'user1', 'test_feature' + mockLogger.debug, + USER_IN_ROLLOUT, + 'user1', 'test_feature' ); }); }); @@ -1778,126 +1449,33 @@ describe('lib/core/decision_service', function() { it('returns a decision with a variation and experiment from the everyone else targeting rule', function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: {}, }); var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; var expectedDecision = { - experiment: { - forcedVariations: {}, - status: 'Not started', - key: '594037', - id: '594037', - variations: [ - { - id: '594038', - variables: [ - { - id: '4919852825313280', - value: 'false', - }, - { - id: '5482802778734592', - value: '400', - }, - { - id: '6045752732155904', - value: '14.99', - }, - { - id: '6327227708866560', - value: 'Hello', - }, - { - id: '8765345281230956', - value: '{ "count": 1, "message": "Hello" }', - }, - ], - featureEnabled: false, - key: '594038', - }, - ], - audienceIds: [], - trafficAllocation: [{ endOfRange: 0, entityId: '594038' }], - layerId: '594030', - variationKeyMap: { - 594038: { - id: '594038', - variables: [ - { - id: '4919852825313280', - value: 'false', - }, - { - id: '5482802778734592', - value: '400', - }, - { - id: '6045752732155904', - value: '14.99', - }, - { - id: '6327227708866560', - value: 'Hello', - }, - { - id: '8765345281230956', - value: '{ "count": 1, "message": "Hello" }', - }, - ], - featureEnabled: false, - key: '594038', - }, - }, - }, - variation: { - id: '594038', - variables: [ - { - id: '4919852825313280', - value: 'false', - }, - { - id: '5482802778734592', - value: '400', - }, - { - id: '6045752732155904', - value: '14.99', - }, - { - id: '6327227708866560', - value: 'Hello', - }, - { - id: '8765345281230956', - value: '{ "count": 1, "message": "Hello" }', - }, - ], - featureEnabled: false, - key: '594038', - }, + experiment: configObj.experimentIdMap['594037'], + variation: configObj.variationIdMap['594038'], decisionSource: DECISION_SOURCES.ROLLOUT, - }; + }; + assert.deepEqual(decision, expectedDecision); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s does not meet conditions for targeting rule %s.', - 'DECISION_SERVICE', 'user1', 1 + mockLogger.debug, + USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, + 'user1', 1 ); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s bucketed into targeting rule %s.', - 'DECISION_SERVICE', 'user1', 'Everyone Else' + mockLogger.debug, + USER_BUCKETED_INTO_TARGETING_RULE, + 'user1', 'Everyone Else' ); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s is in rollout of feature %s.', - 'DECISION_SERVICE', 'user1', 'test_feature' + mockLogger.debug, + USER_IN_ROLLOUT, + 'user1', 'test_feature' ); }); }); @@ -1913,6 +1491,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with no variation, no experiment and source rollout', function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', }); @@ -1924,16 +1503,14 @@ describe('lib/core/decision_service', function() { }; assert.deepEqual(decision, expectedDecision); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s does not meet conditions for targeting rule %s.', - 'DECISION_SERVICE', 'user1', 1 + mockLogger.debug, + USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, + 'user1', 1 ); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s is not in rollout of feature %s.', - 'DECISION_SERVICE', 'user1', 'test_feature' + mockLogger.debug, + USER_NOT_IN_ROLLOUT, + 'user1', 'test_feature' ); }); }); @@ -1960,126 +1537,33 @@ describe('lib/core/decision_service', function() { it('returns a decision with a variation and experiment from the everyone else targeting rule', function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { test_attribute: 'test_value' } }); var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; var expectedDecision = { - experiment: { - forcedVariations: {}, - status: 'Not started', - key: '594037', - id: '594037', - variations: [ - { - id: '594038', - variables: [ - { - id: '4919852825313280', - value: 'false', - }, - { - id: '5482802778734592', - value: '400', - }, - { - id: '6045752732155904', - value: '14.99', - }, - { - id: '6327227708866560', - value: 'Hello', - }, - { - id: '8765345281230956', - value: '{ "count": 1, "message": "Hello" }', - }, - ], - featureEnabled: false, - key: '594038', - }, - ], - audienceIds: [], - trafficAllocation: [{ endOfRange: 0, entityId: '594038' }], - layerId: '594030', - variationKeyMap: { - 594038: { - id: '594038', - variables: [ - { - id: '4919852825313280', - value: 'false', - }, - { - id: '5482802778734592', - value: '400', - }, - { - id: '6045752732155904', - value: '14.99', - }, - { - id: '6327227708866560', - value: 'Hello', - }, - { - id: '8765345281230956', - value: '{ "count": 1, "message": "Hello" }', - }, - ], - featureEnabled: false, - key: '594038', - }, - }, - }, - variation: { - id: '594038', - variables: [ - { - id: '4919852825313280', - value: 'false', - }, - { - id: '5482802778734592', - value: '400', - }, - { - id: '6045752732155904', - value: '14.99', - }, - { - id: '6327227708866560', - value: 'Hello', - }, - { - id: '8765345281230956', - value: '{ "count": 1, "message": "Hello" }', - }, - ], - featureEnabled: false, - key: '594038', - }, + experiment: configObj.experimentIdMap['594037'], + variation: configObj.variationIdMap['594038'], decisionSource: DECISION_SOURCES.ROLLOUT, }; + assert.deepEqual(decision, expectedDecision); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s meets conditions for targeting rule %s.', - 'DECISION_SERVICE', 'user1', 1 + mockLogger.debug, + USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, + 'user1', 1 ); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s User %s not bucketed into targeting rule %s due to traffic allocation. Trying everyone rule.', - 'DECISION_SERVICE', 'user1', 1 + mockLogger.debug, + USER_NOT_BUCKETED_INTO_TARGETING_RULE, + 'user1', 1 ); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s bucketed into targeting rule %s.', - 'DECISION_SERVICE', 'user1', 'Everyone Else' + mockLogger.debug, + USER_BUCKETED_INTO_TARGETING_RULE, + 'user1', 'Everyone Else' ); }); }); @@ -2089,10 +1573,10 @@ describe('lib/core/decision_service', function() { var feature; var getVariationStub; var bucketStub; - fakeDecisionResponse = { - result: null, + fakeDecisionResponse = Value.of('sync', { + result: {}, reasons: [], - }; + }); var fakeBucketStubDecisionResponse = { result: '599057', reasons: [], @@ -2109,88 +1593,27 @@ describe('lib/core/decision_service', function() { // No attributes passed to the user context, so user is not in the audience for the experiment // It should fall through to the rollout user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1' }); var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; var expectedDecision = { - experiment: { - trafficAllocation: [ - { - endOfRange: 10000, - entityId: '599057', - }, - ], - layerId: '599055', - forcedVariations: {}, - audienceIds: [], - variations: [ - { - key: '599057', - id: '599057', - featureEnabled: true, - variables: [ - { - id: '4937719889264640', - value: '200', - }, - { - id: '6345094772817920', - value: "i'm a rollout", - }, - ], - }, - ], - status: 'Not started', - key: '599056', - id: '599056', - variationKeyMap: { - 599057: { - key: '599057', - id: '599057', - featureEnabled: true, - variables: [ - { - id: '4937719889264640', - value: '200', - }, - { - id: '6345094772817920', - value: "i'm a rollout", - }, - ], - }, - }, - }, - variation: { - key: '599057', - id: '599057', - featureEnabled: true, - variables: [ - { - id: '4937719889264640', - value: '200', - }, - { - id: '6345094772817920', - value: "i'm a rollout", - }, - ], - }, + experiment: configObj.experimentIdMap['599056'], + variation: configObj.variationIdMap['599057'], decisionSource: DECISION_SOURCES.ROLLOUT, }; + assert.deepEqual(decision, expectedDecision); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s bucketed into targeting rule %s.', - 'DECISION_SERVICE', 'user1', 'Everyone Else' + mockLogger.debug, + USER_BUCKETED_INTO_TARGETING_RULE, + 'user1', 'Everyone Else' ); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: User %s is in rollout of feature %s.', - 'DECISION_SERVICE', 'user1', 'shared_feature' + mockLogger.debug, + USER_IN_ROLLOUT, + 'user1', 'shared_feature' ); }); }); @@ -2203,6 +1626,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with no variation, no experiment and source rollout', function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1' }); @@ -2214,16 +1638,14 @@ describe('lib/core/decision_service', function() { }; assert.deepEqual(decision, expectedDecision); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: Feature %s is not attached to any experiments.', - 'DECISION_SERVICE', 'unused_flag' + mockLogger.debug, + FEATURE_HAS_NO_EXPERIMENTS, + 'unused_flag' ); sinon.assert.calledWithExactly( - mockLogger.log, - LOG_LEVEL.DEBUG, - '%s: There is no rollout of feature %s.', - 'DECISION_SERVICE', 'unused_flag' + mockLogger.debug, + NO_ROLLOUT_EXISTS, + 'unused_flag' ); }); }); @@ -2233,12 +1655,13 @@ describe('lib/core/decision_service', function() { var generateBucketValueStub; beforeEach(function() { feature = configObj.featureKeyMap.test_feature_in_exclusion_group; - generateBucketValueStub = sandbox.stub(bucketer, '_generateBucketValue'); + generateBucketValueStub = sandbox.stub(bucketValueGenerator, 'generateBucketValue'); }); it('returns a decision with a variation in mutex group bucket less than 2500', function() { generateBucketValueStub.returns(2400); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { experiment_attr: 'group_experiment' } @@ -2246,6 +1669,7 @@ describe('lib/core/decision_service', function() { var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; var expectedExperiment = projectConfig.getExperimentFromKey(configObj, 'group_2_exp_1'); var expectedDecision = { + cmabUuid: undefined, experiment: expectedExperiment, variation: { id: '38901', @@ -2255,10 +1679,7 @@ describe('lib/core/decision_service', function() { decisionSource: DECISION_SOURCES.FEATURE_TEST, }; assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[3]), - 'BUCKETER: Assigned bucket 2400 to user with bucketing ID user1.' - ); + sinon.assert.calledWithExactly( generateBucketValueStub, 'user142222' @@ -2268,6 +1689,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with a variation in mutex group bucket range 2500 to 5000', function() { generateBucketValueStub.returns(4000); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { experiment_attr: 'group_experiment' } @@ -2275,6 +1697,7 @@ describe('lib/core/decision_service', function() { var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; var expectedExperiment = projectConfig.getExperimentFromKey(configObj, 'group_2_exp_2'); var expectedDecision = { + cmabUuid: undefined, experiment: expectedExperiment, variation: { id: '38905', @@ -2284,10 +1707,7 @@ describe('lib/core/decision_service', function() { decisionSource: DECISION_SOURCES.FEATURE_TEST, }; assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[3]), - 'BUCKETER: Assigned bucket 4000 to user with bucketing ID user1.' - ); + sinon.assert.calledWithExactly( generateBucketValueStub, 'user142223' @@ -2297,6 +1717,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with a variation in mutex group bucket range 5000 to 7500', function() { generateBucketValueStub.returns(6500); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { experiment_attr: 'group_experiment' } @@ -2304,6 +1725,7 @@ describe('lib/core/decision_service', function() { var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; var expectedExperiment = projectConfig.getExperimentFromKey(configObj, 'group_2_exp_3'); var expectedDecision = { + cmabUuid: undefined, experiment: expectedExperiment, variation: { id: '38906', @@ -2313,10 +1735,7 @@ describe('lib/core/decision_service', function() { decisionSource: DECISION_SOURCES.FEATURE_TEST, }; assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[3]), - 'BUCKETER: Assigned bucket 6500 to user with bucketing ID user1.' - ); + sinon.assert.calledWithExactly( generateBucketValueStub, 'user142224' @@ -2326,6 +1745,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with variation and source rollout in mutex group bucket greater than 7500', function() { generateBucketValueStub.returns(8000); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { experiment_attr: 'group_experiment' } @@ -2334,36 +1754,11 @@ describe('lib/core/decision_service', function() { var expectedExperiment = projectConfig.getExperimentFromId(configObj, '594066'); var expectedDecision = { experiment: expectedExperiment, - variation: { - id: '594067', - key: '594067', - featureEnabled: true, - variables: [ - { - id: '5060590313668608', - value: '30.34' - }, - { - id: '5342065290379264', - value: 'Winter is coming definitely' - }, - { - id: '6186490220511232', - value: '500' - }, - { - id: '6467965197221888', - value: 'true' - }, - ], - }, + variation: configObj.variationIdMap['594067'], decisionSource: DECISION_SOURCES.ROLLOUT, } assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[3]), - 'BUCKETER: Assigned bucket 8000 to user with bucketing ID user1.' - ); + sinon.assert.calledWithExactly( generateBucketValueStub, 'user1594066' @@ -2373,6 +1768,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with variation for rollout in mutex group with audience mismatch', function() { generateBucketValueStub.returns(2400); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { experiment_attr: 'group_experiment_invalid' } @@ -2381,36 +1777,11 @@ describe('lib/core/decision_service', function() { var expectedExperiment = projectConfig.getExperimentFromId(configObj, '594066', mockLogger); var expectedDecision = { experiment: expectedExperiment, - variation: { - id: '594067', - key: '594067', - featureEnabled: true, - variables: [ - { - id: '5060590313668608', - value: '30.34' - }, - { - id: '5342065290379264', - value: 'Winter is coming definitely' - }, - { - id: '6186490220511232', - value: '500' - }, - { - id: '6467965197221888', - value: 'true' - }, - ], - }, + variation: configObj.variationIdMap['594067'], decisionSource: DECISION_SOURCES.ROLLOUT, } assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[18]), - 'BUCKETER: Assigned bucket 2400 to user with bucketing ID user1.' - ); + sinon.assert.calledWithExactly( generateBucketValueStub, 'user1594066' @@ -2423,12 +1794,13 @@ describe('lib/core/decision_service', function() { var generateBucketValueStub; beforeEach(function() { feature = configObj.featureKeyMap.test_feature_in_multiple_experiments; - generateBucketValueStub = sandbox.stub(bucketer, '_generateBucketValue'); + generateBucketValueStub = sandbox.stub(bucketValueGenerator, 'generateBucketValue'); }); it('returns a decision with a variation in mutex group bucket less than 2500', function() { generateBucketValueStub.returns(2400); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { experiment_attr: 'group_experiment' } @@ -2436,6 +1808,7 @@ describe('lib/core/decision_service', function() { var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; var expectedExperiment = projectConfig.getExperimentFromKey(configObj, 'test_experiment3'); var expectedDecision = { + cmabUuid: undefined, experiment: expectedExperiment, variation: { id: '222239', @@ -2446,10 +1819,7 @@ describe('lib/core/decision_service', function() { decisionSource: DECISION_SOURCES.FEATURE_TEST, }; assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[3]), - 'BUCKETER: Assigned bucket 2400 to user with bucketing ID user1.' - ); + sinon.assert.calledWithExactly( generateBucketValueStub, 'user1111134' @@ -2459,6 +1829,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with a variation in mutex group bucket range 2500 to 5000', function() { generateBucketValueStub.returns(4000); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { experiment_attr: 'group_experiment' } @@ -2466,6 +1837,7 @@ describe('lib/core/decision_service', function() { var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; var expectedExperiment = projectConfig.getExperimentFromKey(configObj, 'test_experiment4'); var expectedDecision = { + cmabUuid: undefined, experiment: expectedExperiment, variation: { id: '222240', @@ -2476,10 +1848,7 @@ describe('lib/core/decision_service', function() { decisionSource: DECISION_SOURCES.FEATURE_TEST, }; assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[3]), - 'BUCKETER: Assigned bucket 4000 to user with bucketing ID user1.' - ); + sinon.assert.calledWithExactly( generateBucketValueStub, 'user1111135' @@ -2489,6 +1858,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with a variation in mutex group bucket range 5000 to 7500', function() { generateBucketValueStub.returns(6500); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { experiment_attr: 'group_experiment' } @@ -2496,6 +1866,7 @@ describe('lib/core/decision_service', function() { var decision = decisionServiceInstance.getVariationForFeature(configObj, feature, user).result; var expectedExperiment = projectConfig.getExperimentFromKey(configObj, 'test_experiment5'); var expectedDecision = { + cmabUuid: undefined, experiment: expectedExperiment, variation: { id: '222241', @@ -2506,10 +1877,7 @@ describe('lib/core/decision_service', function() { decisionSource: DECISION_SOURCES.FEATURE_TEST, }; assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[3]), - 'BUCKETER: Assigned bucket 6500 to user with bucketing ID user1.' - ); + sinon.assert.calledWithExactly( generateBucketValueStub, 'user1111136' @@ -2519,6 +1887,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with variation and source rollout in mutex group bucket greater than 7500', function() { generateBucketValueStub.returns(8000); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { experiment_attr: 'group_experiment' } @@ -2527,36 +1896,11 @@ describe('lib/core/decision_service', function() { var expectedExperiment = projectConfig.getExperimentFromId(configObj, '594066'); var expectedDecision = { experiment: expectedExperiment, - variation: { - id: '594067', - key: '594067', - featureEnabled: true, - variables: [ - { - id: '5060590313668608', - value: '30.34' - }, - { - id: '5342065290379264', - value: 'Winter is coming definitely' - }, - { - id: '6186490220511232', - value: '500' - }, - { - id: '6467965197221888', - value: 'true' - }, - ], - }, + variation: configObj.variationIdMap['594067'], decisionSource: DECISION_SOURCES.ROLLOUT, } assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[3]), - 'BUCKETER: Assigned bucket 8000 to user with bucketing ID user1.' - ); + sinon.assert.calledWithExactly( generateBucketValueStub, 'user1594066' @@ -2566,6 +1910,7 @@ describe('lib/core/decision_service', function() { it('returns a decision with variation for rollout in mutex group bucket range 2500 to 5000', function() { generateBucketValueStub.returns(4000); user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'user1', attributes: { experiment_attr: 'group_experiment_invalid' } @@ -2574,36 +1919,11 @@ describe('lib/core/decision_service', function() { var expectedExperiment = projectConfig.getExperimentFromId(configObj, '594066', mockLogger); var expectedDecision = { experiment: expectedExperiment, - variation: { - id: '594067', - key: '594067', - featureEnabled: true, - variables: [ - { - id: '5060590313668608', - value: '30.34' - }, - { - id: '5342065290379264', - value: 'Winter is coming definitely' - }, - { - id: '6186490220511232', - value: '500' - }, - { - id: '6467965197221888', - value: 'true' - }, - ], - }, + variation: configObj.variationIdMap['594067'], decisionSource: DECISION_SOURCES.ROLLOUT, } assert.deepEqual(decision, expectedDecision); - assert.strictEqual( - buildLogMessageFromArgs(mockLogger.log.args[18]), - 'BUCKETER: Assigned bucket 4000 to user with bucketing ID user1.' - ); + sinon.assert.calledWithExactly( generateBucketValueStub, 'user1594066' @@ -2634,6 +1954,7 @@ describe('lib/core/decision_service', function() { it('should call buildBucketerParams with user Id when bucketing Id is not provided in the attributes', function() { user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'testUser', attributes: { test_attribute: 'test_value' } @@ -2651,6 +1972,7 @@ describe('lib/core/decision_service', function() { $opt_bucketing_id: 'abcdefg', }; user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: {}, userId: 'testUser', attributes, diff --git a/lib/core/decision_service/index.ts b/lib/core/decision_service/index.ts new file mode 100644 index 000000000..057a0e129 --- /dev/null +++ b/lib/core/decision_service/index.ts @@ -0,0 +1,1697 @@ +/** + * Copyright 2017-2022, 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LoggerFacade } from '../../logging/logger' +import { bucket } from '../bucketer'; +import { + AUDIENCE_EVALUATION_TYPES, + CONTROL_ATTRIBUTES, + DECISION_SOURCES, + DecisionSource, +} from '../../utils/enums'; +import { + getAudiencesById, + getExperimentFromId, + getExperimentFromKey, + getFlagVariationByKey, + getVariationIdFromExperimentAndVariationKey, + getVariationFromId, + getVariationKeyFromId, + isActive, + ProjectConfig, + getHoldoutsForFlag, +} from '../../project_config/project_config'; +import { AudienceEvaluator, createAudienceEvaluator } from '../audience_evaluator'; +import * as stringValidator from '../../utils/string_value_validator'; +import { + BucketerParams, + DecisionResponse, + Experiment, + ExperimentBucketMap, + ExperimentCore, + FeatureFlag, + Holdout, + OptimizelyDecideOption, + OptimizelyUserContext, + UserAttributes, + UserProfile, + UserProfileService, + UserProfileServiceAsync, + Variation, +} from '../../shared_types'; + +import { + INVALID_USER_ID, + INVALID_VARIATION_KEY, + NO_VARIATION_FOR_EXPERIMENT_KEY, + USER_NOT_IN_FORCED_VARIATION, + USER_PROFILE_LOOKUP_ERROR, + USER_PROFILE_SAVE_ERROR, + BUCKETING_ID_NOT_STRING, +} from 'error_message'; + +import { + SAVED_USER_VARIATION, + SAVED_VARIATION_NOT_FOUND, + USER_HAS_NO_FORCED_VARIATION, + USER_MAPPED_TO_FORCED_VARIATION, + USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, + VALID_BUCKETING_ID, + VARIATION_REMOVED_FOR_USER, +} from 'log_message'; +import { OptimizelyError } from '../../error/optimizly_error'; +import { CmabService } from './cmab/cmab_service'; +import { Maybe, OpType, OpValue } from '../../utils/type'; +import { Value } from '../../utils/promise/operation_value'; +import * as featureToggle from '../../feature_toggle'; + +export const EXPERIMENT_NOT_RUNNING = 'Experiment %s is not running.'; +export const RETURNING_STORED_VARIATION = + 'Returning previously activated variation "%s" of experiment "%s" for user "%s" from user profile.'; +export const USER_NOT_IN_EXPERIMENT = 'User %s does not meet conditions to be in experiment %s.'; +export const USER_HAS_NO_VARIATION = 'User %s is in no variation of experiment %s.'; +export const USER_HAS_VARIATION = 'User %s is in variation %s of experiment %s.'; +export const USER_FORCED_IN_VARIATION = 'User %s is forced in variation %s.'; +export const FORCED_BUCKETING_FAILED = 'Variation key %s is not in datafile. Not activating user %s.'; +export const EVALUATING_AUDIENCES_COMBINED = 'Evaluating audiences for %s "%s": %s.'; +export const AUDIENCE_EVALUATION_RESULT_COMBINED = 'Audiences for %s %s collectively evaluated to %s.'; +export const USER_IN_ROLLOUT = 'User %s is in rollout of feature %s.'; +export const USER_NOT_IN_ROLLOUT = 'User %s is not in rollout of feature %s.'; +export const FEATURE_HAS_NO_EXPERIMENTS = 'Feature %s is not attached to any experiments.'; +export const USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE = + 'User %s does not meet conditions for targeting rule %s.'; +export const USER_NOT_BUCKETED_INTO_TARGETING_RULE = +'User %s not bucketed into targeting rule %s due to traffic allocation. Trying everyone rule.'; +export const USER_BUCKETED_INTO_TARGETING_RULE = 'User %s bucketed into targeting rule %s.'; +export const NO_ROLLOUT_EXISTS = 'There is no rollout of feature %s.'; +export const INVALID_ROLLOUT_ID = 'Invalid rollout ID %s attached to feature %s'; +export const ROLLOUT_HAS_NO_EXPERIMENTS = 'Rollout of feature %s has no experiments'; +export const IMPROPERLY_FORMATTED_EXPERIMENT = 'Experiment key %s is improperly formatted.'; +export const USER_HAS_FORCED_VARIATION = + 'Variation %s is mapped to experiment %s and user %s in the forced variation map.'; +export const USER_MEETS_CONDITIONS_FOR_TARGETING_RULE = 'User %s meets conditions for targeting rule %s.'; +export const USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED = + 'Variation (%s) is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.'; +export const USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED = + 'Variation (%s) is mapped to flag (%s) and user (%s) in the forced decision map.'; +export const USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID = + 'Invalid variation is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.'; +export const USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID = + 'Invalid variation is mapped to flag (%s) and user (%s) in the forced decision map.'; +export const CMAB_NOT_SUPPORTED_IN_SYNC = 'CMAB is not supported in sync mode.'; +export const CMAB_FETCH_FAILED = 'Failed to fetch CMAB data for experiment %s.'; +export const CMAB_FETCHED_VARIATION_INVALID = 'Fetched variation %s for cmab experiment %s is invalid.'; +export const HOLDOUT_NOT_RUNNING = 'Holdout %s is not running.'; +export const USER_MEETS_CONDITIONS_FOR_HOLDOUT = 'User %s meets conditions for holdout %s.'; +export const USER_DOESNT_MEET_CONDITIONS_FOR_HOLDOUT = 'User %s does not meet conditions for holdout %s.'; +export const USER_BUCKETED_INTO_HOLDOUT_VARIATION = 'User %s is in variation %s of holdout %s.'; +export const USER_NOT_BUCKETED_INTO_HOLDOUT_VARIATION = 'User %s is in no holdout variation.'; + +export interface DecisionObj { + experiment: Experiment | Holdout | null; + variation: Variation | null; + decisionSource: DecisionSource; + cmabUuid?: string; +} + +interface DecisionServiceOptions { + userProfileService?: UserProfileService; + userProfileServiceAsync?: UserProfileServiceAsync; + logger?: LoggerFacade; + UNSTABLE_conditionEvaluators: unknown; + cmabService: CmabService; +} + +interface DeliveryRuleResponse<T, K> extends DecisionResponse<T> { + skipToEveryoneElse: K; +} + +interface UserProfileTracker { + userProfile: ExperimentBucketMap | null; + isProfileUpdated: boolean; +} + +type VarationKeyWithCmabParams = { + variationKey?: string; + cmabUuid?: string; +}; +export type DecisionReason = [string, ...any[]]; +export type VariationResult = DecisionResponse<VarationKeyWithCmabParams>; +export type DecisionResult = DecisionResponse<DecisionObj>; +type VariationIdWithCmabParams = { + variationId? : string; + cmabUuid?: string; +}; +export type DecideOptionsMap = Partial<Record<OptimizelyDecideOption, boolean>>; + +export const CMAB_DUMMY_ENTITY_ID= '$' + +/** + * Optimizely's decision service that determines which variation of an experiment the user will be allocated to. + * + * The decision service contains all logic around how a user decision is made. This includes all of the following (in order): + * 1. Checking experiment status + * 2. Checking forced bucketing + * 3. Checking whitelisting + * 4. Checking user profile service for past bucketing decisions (sticky bucketing) + * 5. Checking audience targeting + * 6. Using Murmurhash3 to bucket the user. + * + * @constructor + * @param {DecisionServiceOptions} options + * @returns {DecisionService} + */ +export class DecisionService { + private logger?: LoggerFacade; + private audienceEvaluator: AudienceEvaluator; + private forcedVariationMap: { [key: string]: { [id: string]: string } }; + private userProfileService?: UserProfileService; + private userProfileServiceAsync?: UserProfileServiceAsync; + private cmabService: CmabService; + + constructor(options: DecisionServiceOptions) { + this.logger = options.logger; + this.audienceEvaluator = createAudienceEvaluator(options.UNSTABLE_conditionEvaluators, this.logger); + this.forcedVariationMap = {}; + this.userProfileService = options.userProfileService; + this.userProfileServiceAsync = options.userProfileServiceAsync; + this.cmabService = options.cmabService; + } + + private isCmab(experiment: Experiment): boolean { + return !!experiment.cmab; + } + + /** + * Resolves the variation into which the visitor will be bucketed. + * + * @param {ProjectConfig} configObj - The parsed project configuration object. + * @param {Experiment} experiment - The experiment for which the variation is being resolved. + * @param {OptimizelyUserContext} user - The user context associated with this decision. + * @returns {DecisionResponse<string|null>} - A DecisionResponse containing the variation the user is bucketed into, + * along with the decision reasons. + */ + private resolveVariation<OP extends OpType>( + op: OP, + configObj: ProjectConfig, + experiment: Experiment, + user: OptimizelyUserContext, + decideOptions: DecideOptionsMap, + userProfileTracker?: UserProfileTracker, + ): Value<OP, VariationResult> { + const userId = user.getUserId(); + + const experimentKey = experiment.key; + + if (!isActive(configObj, experimentKey)) { + this.logger?.info(EXPERIMENT_NOT_RUNNING, experimentKey); + return Value.of(op, { + result: {}, + reasons: [[EXPERIMENT_NOT_RUNNING, experimentKey]], + }); + } + + const decideReasons: DecisionReason[] = []; + + const decisionForcedVariation = this.getForcedVariation(configObj, experimentKey, userId); + decideReasons.push(...decisionForcedVariation.reasons); + const forcedVariationKey = decisionForcedVariation.result; + + if (forcedVariationKey) { + return Value.of(op, { + result: { variationKey: forcedVariationKey }, + reasons: decideReasons, + }); + } + + const decisionWhitelistedVariation = this.getWhitelistedVariation(experiment, userId); + decideReasons.push(...decisionWhitelistedVariation.reasons); + let variation = decisionWhitelistedVariation.result; + if (variation) { + return Value.of(op, { + result: { variationKey: variation.key }, + reasons: decideReasons, + }); + } + + // check for sticky bucketing + if (userProfileTracker) { + variation = this.getStoredVariation(configObj, experiment, userId, userProfileTracker.userProfile); + if (variation) { + this.logger?.info( + RETURNING_STORED_VARIATION, + variation.key, + experimentKey, + userId, + ); + decideReasons.push([ + RETURNING_STORED_VARIATION, + variation.key, + experimentKey, + userId, + ]); + return Value.of(op, { + result: { variationKey: variation.key }, + reasons: decideReasons, + }); + } + } + + const decisionifUserIsInAudience = this.checkIfUserIsInAudience( + configObj, + experiment, + AUDIENCE_EVALUATION_TYPES.EXPERIMENT, + user, + '' + ); + decideReasons.push(...decisionifUserIsInAudience.reasons); + if (!decisionifUserIsInAudience.result) { + this.logger?.info( + USER_NOT_IN_EXPERIMENT, + userId, + experimentKey, + ); + decideReasons.push([ + USER_NOT_IN_EXPERIMENT, + userId, + experimentKey, + ]); + return Value.of(op, { + result: {}, + reasons: decideReasons, + }); + } + + const decisionVariationValue = this.isCmab(experiment) ? + this.getDecisionForCmabExperiment(op, configObj, experiment, user, decideOptions) : + this.getDecisionFromBucketer(op, configObj, experiment, user); + + return decisionVariationValue.then((variationResult): Value<OP, VariationResult> => { + decideReasons.push(...variationResult.reasons); + if (variationResult.error) { + return Value.of(op, { + error: true, + result: {}, + reasons: decideReasons, + }); + } + + const variationId = variationResult.result.variationId; + variation = variationId ? configObj.variationIdMap[variationId] : null; + if (!variation) { + this.logger?.debug( + USER_HAS_NO_VARIATION, + userId, + experimentKey, + ); + decideReasons.push([ + USER_HAS_NO_VARIATION, + userId, + experimentKey, + ]); + return Value.of(op, { + result: {}, + reasons: decideReasons, + }); + } + + this.logger?.info( + USER_HAS_VARIATION, + userId, + variation.key, + experimentKey, + ); + decideReasons.push([ + USER_HAS_VARIATION, + userId, + variation.key, + experimentKey, + ]); + // update experiment bucket map if decide options do not include shouldIgnoreUPS + if (userProfileTracker) { + this.updateUserProfile(experiment, variation, userProfileTracker); + } + + return Value.of(op, { + result: { variationKey: variation.key, cmabUuid: variationResult.result.cmabUuid }, + reasons: decideReasons, + }); + }); + } + + private getDecisionForCmabExperiment<OP extends OpType>( + op: OP, + configObj: ProjectConfig, + experiment: Experiment, + user: OptimizelyUserContext, + decideOptions: DecideOptionsMap, + ): Value<OP, DecisionResponse<VariationIdWithCmabParams>> { + if (op === 'sync') { + return Value.of(op, { + error: false, // this is not considered an error, the evaluation should continue to next rule + result: {}, + reasons: [[CMAB_NOT_SUPPORTED_IN_SYNC]], + }); + } + + const userId = user.getUserId(); + const attributes = user.getAttributes(); + + const bucketingId = this.getBucketingId(userId, attributes); + const bucketerParams = this.buildBucketerParams(configObj, experiment, bucketingId, userId); + + const bucketerResult = bucket(bucketerParams); + + // this means the user is not in the cmab experiment + if (bucketerResult.result !== CMAB_DUMMY_ENTITY_ID) { + return Value.of(op, { + error: false, + result: {}, + reasons: bucketerResult.reasons, + }); + } + + const cmabPromise = this.cmabService.getDecision(configObj, user, experiment.id, decideOptions).then( + (cmabDecision) => { + return { + error: false, + result: cmabDecision, + reasons: [] as DecisionReason[], + }; + } + ).catch((ex: any) => { + this.logger?.error(CMAB_FETCH_FAILED, experiment.key); + return { + error: true, + result: {}, + reasons: [[CMAB_FETCH_FAILED, experiment.key]] as DecisionReason[], + }; + }); + + return Value.of(op, cmabPromise); + } + + private getDecisionFromBucketer<OP extends OpType>( + op: OP, + configObj: ProjectConfig, + experiment: Experiment, + user: OptimizelyUserContext + ): Value<OP, DecisionResponse<VariationIdWithCmabParams>> { + const userId = user.getUserId(); + const attributes = user.getAttributes(); + + // by default, the bucketing ID should be the user ID + const bucketingId = this.getBucketingId(userId, attributes); + const bucketerParams = this.buildBucketerParams(configObj, experiment, bucketingId, userId); + + const decisionVariation = bucket(bucketerParams); + return Value.of(op, { + result: { + variationId: decisionVariation.result || undefined, + }, + reasons: decisionVariation.reasons, + }); + } + + /** + * Gets variation where visitor will be bucketed. + * @param {ProjectConfig} configObj The parsed project configuration object + * @param {Experiment} experiment + * @param {OptimizelyUserContext} user A user context + * @param {[key: string]: boolean} options Optional map of decide options + * @return {DecisionResponse<string|null>} DecisionResponse containing the variation the user is bucketed into + * and the decide reasons. + */ + getVariation( + configObj: ProjectConfig, + experiment: Experiment, + user: OptimizelyUserContext, + options: DecideOptionsMap = {} + ): DecisionResponse<string | null> { + const shouldIgnoreUPS = options[OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE]; + const userProfileTracker: Maybe<UserProfileTracker> = shouldIgnoreUPS ? undefined + : { + isProfileUpdated: false, + userProfile: this.resolveExperimentBucketMap('sync', user.getUserId(), user.getAttributes()).get(), + }; + + const result = this.resolveVariation('sync', configObj, experiment, user, options, userProfileTracker).get(); + + if(userProfileTracker) { + this.saveUserProfile('sync', user.getUserId(), userProfileTracker) + } + + return { + result: result.result.variationKey || null, + reasons: result.reasons, + } + } + + /** + * Merges attributes from attributes[STICKY_BUCKETING_KEY] and userProfileService + * @param {string} userId + * @param {UserAttributes} attributes + * @return {ExperimentBucketMap} finalized copy of experiment_bucket_map + */ + private resolveExperimentBucketMap<OP extends OpType>( + op: OP, + userId: string, + attributes: UserAttributes = {}, + ): Value<OP, ExperimentBucketMap> { + const fromAttributes = (attributes[CONTROL_ATTRIBUTES.STICKY_BUCKETING_KEY] || {}) as any as ExperimentBucketMap; + return this.getUserProfile(op, userId).then((userProfile) => { + const fromUserProfileService = userProfile?.experiment_bucket_map || {}; + return Value.of(op, { + ...fromUserProfileService, + ...fromAttributes, + }); + }); + } + + /** + * Checks if user is whitelisted into any variation and return that variation if so + * @param {Experiment} experiment + * @param {string} userId + * @return {DecisionResponse<Variation|null>} DecisionResponse containing the forced variation if it exists + * or user ID and the decide reasons. + */ + private getWhitelistedVariation( + experiment: Experiment, + userId: string + ): DecisionResponse<Variation | null> { + const decideReasons: DecisionReason[] = []; + if (experiment.forcedVariations && experiment.forcedVariations.hasOwnProperty(userId)) { + const forcedVariationKey = experiment.forcedVariations[userId]; + if (experiment.variationKeyMap.hasOwnProperty(forcedVariationKey)) { + this.logger?.info( + USER_FORCED_IN_VARIATION, + userId, + forcedVariationKey, + ); + decideReasons.push([ + USER_FORCED_IN_VARIATION, + userId, + forcedVariationKey, + ]); + return { + result: experiment.variationKeyMap[forcedVariationKey], + reasons: decideReasons, + }; + } else { + this.logger?.error( + FORCED_BUCKETING_FAILED, + forcedVariationKey, + userId, + ); + decideReasons.push([ + FORCED_BUCKETING_FAILED, + forcedVariationKey, + userId, + ]); + return { + result: null, + reasons: decideReasons, + }; + } + } + + return { + result: null, + reasons: decideReasons, + }; + } + + /** + * Checks whether the user is included in experiment audience + * @param {ProjectConfig} configObj The parsed project configuration object + * @param {string} experimentKey Key of experiment being validated + * @param {string} evaluationAttribute String representing experiment key or rule + * @param {string} userId ID of user + * @param {UserAttributes} attributes Optional parameter for user's attributes + * @param {string} loggingKey String representing experiment key or rollout rule. To be used in log messages only. + * @return {DecisionResponse<boolean>} DecisionResponse DecisionResponse containing result true if user meets audience conditions and + * the decide reasons. + */ + private checkIfUserIsInAudience( + configObj: ProjectConfig, + experiment: ExperimentCore, + evaluationAttribute: string, + user: OptimizelyUserContext, + loggingKey?: string | number, + ): DecisionResponse<boolean> { + const decideReasons: DecisionReason[] = []; + const experimentAudienceConditions = experiment.audienceConditions || experiment.audienceIds; + const audiencesById = getAudiencesById(configObj); + + this.logger?.debug( + EVALUATING_AUDIENCES_COMBINED, + evaluationAttribute, + loggingKey || experiment.key, + JSON.stringify(experimentAudienceConditions), + ); + decideReasons.push([ + EVALUATING_AUDIENCES_COMBINED, + evaluationAttribute, + loggingKey || experiment.key, + JSON.stringify(experimentAudienceConditions), + ]); + + const result = this.audienceEvaluator.evaluate(experimentAudienceConditions, audiencesById, user); + + this.logger?.info( + AUDIENCE_EVALUATION_RESULT_COMBINED, + evaluationAttribute, + loggingKey || experiment.key, + result.toString().toUpperCase(), + ); + decideReasons.push([ + AUDIENCE_EVALUATION_RESULT_COMBINED, + evaluationAttribute, + loggingKey || experiment.key, + result.toString().toUpperCase(), + ]); + + return { + result: result, + reasons: decideReasons, + }; + } + + /** + * Given an experiment key and user ID, returns params used in bucketer call + * @param {ProjectConfig} configObj The parsed project configuration object + * @param {string} experimentKey Experiment key used for bucketer + * @param {string} bucketingId ID to bucket user into + * @param {string} userId ID of user to be bucketed + * @return {BucketerParams} + */ + private buildBucketerParams( + configObj: ProjectConfig, + experiment: Experiment | Holdout, + bucketingId: string, + userId: string + ): BucketerParams { + let validateEntity = true; + + let trafficAllocationConfig = experiment.trafficAllocation; + + if ('cmab' in experiment && experiment.cmab) { + trafficAllocationConfig = [{ + entityId: CMAB_DUMMY_ENTITY_ID, + endOfRange: experiment.cmab.trafficAllocation + }]; + + validateEntity = false; + } + + return { + bucketingId, + experimentId: experiment.id, + experimentKey: experiment.key, + experimentIdMap: configObj.experimentIdMap, + experimentKeyMap: configObj.experimentKeyMap, + groupIdMap: configObj.groupIdMap, + logger: this.logger, + trafficAllocationConfig, + userId, + variationIdMap: configObj.variationIdMap, + validateEntity, + } + } + + /** + * Determines if a user should be bucketed into a holdout variation. + * @param {ProjectConfig} configObj - The parsed project configuration object. + * @param {Holdout} holdout - The holdout to evaluate. + * @param {OptimizelyUserContext} user - The user context. + * @returns {DecisionResponse<DecisionObj>} - DecisionResponse containing holdout decision and reasons. + */ + private getVariationForHoldout( + configObj: ProjectConfig, + holdout: Holdout, + user: OptimizelyUserContext, + ): DecisionResponse<DecisionObj> { + const userId = user.getUserId(); + const decideReasons: DecisionReason[] = []; + + if (holdout.status !== 'Running') { + const reason: DecisionReason = [HOLDOUT_NOT_RUNNING, holdout.key]; + decideReasons.push(reason); + this.logger?.info(HOLDOUT_NOT_RUNNING, holdout.key); + return { + result: { + experiment: null, + variation: null, + decisionSource: DECISION_SOURCES.HOLDOUT + }, + reasons: decideReasons + }; + } + + const audienceResult = this.checkIfUserIsInAudience( + configObj, + holdout, + AUDIENCE_EVALUATION_TYPES.EXPERIMENT, + user + ); + decideReasons.push(...audienceResult.reasons); + + if (!audienceResult.result) { + const reason: DecisionReason = [USER_DOESNT_MEET_CONDITIONS_FOR_HOLDOUT, userId, holdout.key]; + decideReasons.push(reason); + this.logger?.info(USER_DOESNT_MEET_CONDITIONS_FOR_HOLDOUT, userId, holdout.key); + return { + result: { + experiment: null, + variation: null, + decisionSource: DECISION_SOURCES.HOLDOUT + }, + reasons: decideReasons + }; + } + + const reason: DecisionReason = [USER_MEETS_CONDITIONS_FOR_HOLDOUT, userId, holdout.key]; + decideReasons.push(reason); + this.logger?.info(USER_MEETS_CONDITIONS_FOR_HOLDOUT, userId, holdout.key); + + const attributes = user.getAttributes(); + const bucketingId = this.getBucketingId(userId, attributes); + const bucketerParams = this.buildBucketerParams(configObj, holdout, bucketingId, userId); + const bucketResult = bucket(bucketerParams); + + decideReasons.push(...bucketResult.reasons); + + if (bucketResult.result) { + const variation = configObj.variationIdMap[bucketResult.result]; + if (variation) { + const bucketReason: DecisionReason = [USER_BUCKETED_INTO_HOLDOUT_VARIATION, userId, holdout.key, variation.key]; + decideReasons.push(bucketReason); + this.logger?.info(USER_BUCKETED_INTO_HOLDOUT_VARIATION, userId, holdout.key, variation.key); + + return { + result: { + experiment: holdout, + variation: variation, + decisionSource: DECISION_SOURCES.HOLDOUT + }, + reasons: decideReasons + }; + } + } + + const noBucketReason: DecisionReason = [USER_NOT_BUCKETED_INTO_HOLDOUT_VARIATION, userId]; + decideReasons.push(noBucketReason); + this.logger?.info(USER_NOT_BUCKETED_INTO_HOLDOUT_VARIATION, userId); + return { + result: { + experiment: null, + variation: null, + decisionSource: DECISION_SOURCES.HOLDOUT + }, + reasons: decideReasons + }; + } + + /** + * Pull the stored variation out of the experimentBucketMap for an experiment/userId + * @param {ProjectConfig} configObj The parsed project configuration object + * @param {Experiment} experiment + * @param {string} userId + * @param {ExperimentBucketMap} experimentBucketMap mapping experiment => { variation_id: <variationId> } + * @return {Variation|null} the stored variation or null if the user profile does not have one for the given experiment + */ + private getStoredVariation( + configObj: ProjectConfig, + experiment: Experiment, + userId: string, + experimentBucketMap: ExperimentBucketMap | null + ): Variation | null { + if (experimentBucketMap?.hasOwnProperty(experiment.id)) { + const decision = experimentBucketMap[experiment.id]; + const variationId = decision.variation_id; + if (configObj.variationIdMap.hasOwnProperty(variationId)) { + return configObj.variationIdMap[decision.variation_id]; + } else { + this.logger?.info( + SAVED_VARIATION_NOT_FOUND, + userId, + variationId, + experiment.key, + ); + } + } + + return null; + } + + /** + * Get the user profile with the given user ID + * @param {string} userId + * @return {UserProfile} the stored user profile or an empty profile if one isn't found or error + */ + private getUserProfile<OP extends OpType>(op: OP, userId: string): Value<OP, UserProfile> { + const emptyProfile = { + user_id: userId, + experiment_bucket_map: {}, + }; + + if (this.userProfileService) { + try { + return Value.of(op, this.userProfileService.lookup(userId)); + } catch (ex: any) { + this.logger?.error( + USER_PROFILE_LOOKUP_ERROR, + userId, + ex.message, + ); + } + return Value.of(op, emptyProfile); + } + + if (this.userProfileServiceAsync && op === 'async') { + return Value.of(op, this.userProfileServiceAsync.lookup(userId).catch((ex: any) => { + this.logger?.error( + USER_PROFILE_LOOKUP_ERROR, + userId, + ex.message, + ); + return emptyProfile; + })); + } + + return Value.of(op, emptyProfile); + } + + private updateUserProfile( + experiment: Experiment, + variation: Variation, + userProfileTracker: UserProfileTracker + ): void { + if(!userProfileTracker.userProfile) { + return + } + + userProfileTracker.userProfile[experiment.id] = { + variation_id: variation.id + } + userProfileTracker.isProfileUpdated = true + } + + /** + * Saves the bucketing decision to the user profile + * @param {Experiment} experiment + * @param {Variation} variation + * @param {string} userId + * @param {ExperimentBucketMap} experimentBucketMap + */ + private saveUserProfile<OP extends OpType>( + op: OP, + userId: string, + userProfileTracker: UserProfileTracker + ): Value<OP, unknown> { + const { userProfile, isProfileUpdated } = userProfileTracker; + + if (!userProfile || !isProfileUpdated) { + return Value.of(op, undefined); + } + + if (op === 'sync' && !this.userProfileService) { + return Value.of(op, undefined); + } + + if (this.userProfileService) { + try { + this.userProfileService.save({ + user_id: userId, + experiment_bucket_map: userProfile, + }); + + this.logger?.info( + SAVED_USER_VARIATION, + userId, + ); + } catch (ex: any) { + this.logger?.error(USER_PROFILE_SAVE_ERROR, userId, ex.message); + } + return Value.of(op, undefined); + } + + if (this.userProfileServiceAsync) { + return Value.of(op, this.userProfileServiceAsync.save({ + user_id: userId, + experiment_bucket_map: userProfile, + }).catch((ex: any) => { + this.logger?.error(USER_PROFILE_SAVE_ERROR, userId, ex.message); + })); + } + + return Value.of(op, undefined); + } + + + /** + * Determines variations for the specified feature flags. + * + * @param {ProjectConfig} configObj - The parsed project configuration object. + * @param {FeatureFlag[]} featureFlags - The feature flags for which variations are to be determined. + * @param {OptimizelyUserContext} user - The user context associated with this decision. + * @param {Record<string, boolean>} options - An optional map of decision options. + * @returns {DecisionResponse<DecisionObj>[]} - An array of DecisionResponse containing objects with + * experiment, variation, decisionSource properties, and decision reasons. + */ + getVariationsForFeatureList( + configObj: ProjectConfig, + featureFlags: FeatureFlag[], + user: OptimizelyUserContext, + options: DecideOptionsMap = {}): DecisionResult[] { + return this.resolveVariationsForFeatureList('sync', configObj, featureFlags, user, options).get(); + } + + resolveVariationsForFeatureList<OP extends OpType>( + op: OP, + configObj: ProjectConfig, + featureFlags: FeatureFlag[], + user: OptimizelyUserContext, + options: DecideOptionsMap): Value<OP, DecisionResult[]> { + const userId = user.getUserId(); + const attributes = user.getAttributes(); + const decisions: DecisionResponse<DecisionObj>[] = []; + // const userProfileTracker : UserProfileTracker = { + // isProfileUpdated: false, + // userProfile: null, + // } + const shouldIgnoreUPS = !!options[OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE]; + + const userProfileTrackerValue: Value<OP, Maybe<UserProfileTracker>> = shouldIgnoreUPS ? Value.of(op, undefined) + : this.resolveExperimentBucketMap(op, userId, attributes).then((userProfile) => { + return Value.of(op, { + isProfileUpdated: false, + userProfile: userProfile, + }); + }); + + return userProfileTrackerValue.then((userProfileTracker) => { + const flagResults = featureFlags.map((feature) => this.resolveVariationForFlag(op, configObj, feature, user, options, userProfileTracker)); + const opFlagResults = Value.all(op, flagResults); + + return opFlagResults.then(() => { + if(userProfileTracker) { + this.saveUserProfile(op, userId, userProfileTracker); + } + return opFlagResults; + }); + }); + } + + private resolveVariationForFlag<OP extends OpType>( + op: OP, + configObj: ProjectConfig, + feature: FeatureFlag, + user: OptimizelyUserContext, + decideOptions: DecideOptionsMap, + userProfileTracker?: UserProfileTracker + ): Value<OP, DecisionResult> { + const decideReasons: DecisionReason[] = []; + + const forcedDecisionResponse = this.findValidatedForcedDecision(configObj, user, feature.key); + decideReasons.push(...forcedDecisionResponse.reasons); + + if (forcedDecisionResponse.result) { + return Value.of(op, { + result: { + variation: forcedDecisionResponse.result, + experiment: null, + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }, + reasons: decideReasons, + }); + } + if (featureToggle.holdout()) { + const holdouts = getHoldoutsForFlag(configObj, feature.key); + + for (const holdout of holdouts) { + const holdoutDecision = this.getVariationForHoldout(configObj, holdout, user); + decideReasons.push(...holdoutDecision.reasons); + + if (holdoutDecision.result.variation) { + return Value.of(op, { + result: holdoutDecision.result, + reasons: decideReasons, + }); + } + } + } + + return this.getVariationForFeatureExperiment(op, configObj, feature, user, decideOptions, userProfileTracker).then((experimentDecision) => { + if (experimentDecision.error || experimentDecision.result.variation !== null) { + return Value.of(op, { + ...experimentDecision, + reasons: [...decideReasons, ...experimentDecision.reasons], + }); + } + + decideReasons.push(...experimentDecision.reasons); + + const rolloutDecision = this.getVariationForRollout(configObj, feature, user); + decideReasons.push(...rolloutDecision.reasons); + const rolloutDecisionResult = rolloutDecision.result; + const userId = user.getUserId(); + + if (rolloutDecisionResult.variation) { + this.logger?.debug(USER_IN_ROLLOUT, userId, feature.key); + decideReasons.push([USER_IN_ROLLOUT, userId, feature.key]); + } else { + this.logger?.debug(USER_NOT_IN_ROLLOUT, userId, feature.key); + decideReasons.push([USER_NOT_IN_ROLLOUT, userId, feature.key]); + } + + return Value.of(op, { + result: rolloutDecisionResult, + reasons: decideReasons, + }); + }); + } + + /** + * Given a feature, user ID, and attributes, returns a decision response containing + * an object representing a decision and decide reasons. If the user was bucketed into + * a variation for the given feature and attributes, the decision object will have variation and + * experiment properties (both objects), as well as a decisionSource property. + * decisionSource indicates whether the decision was due to a rollout or an + * experiment. + * @param {ProjectConfig} configObj The parsed project configuration object + * @param {FeatureFlag} feature A feature flag object from project configuration + * @param {OptimizelyUserContext} user A user context + * @param {[key: string]: boolean} options Map of decide options + * @return {DecisionResponse} DecisionResponse DecisionResponse containing an object with experiment, variation, and decisionSource + * properties and decide reasons. If the user was not bucketed into a variation, the variation + * property in decision object is null. + */ + getVariationForFeature( + configObj: ProjectConfig, + feature: FeatureFlag, + user: OptimizelyUserContext, + options: DecideOptionsMap = {} + ): DecisionResponse<DecisionObj> { + return this.resolveVariationsForFeatureList('sync', configObj, [feature], user, options).get()[0] + } + + private getVariationForFeatureExperiment<OP extends OpType>( + op: OP, + configObj: ProjectConfig, + feature: FeatureFlag, + user: OptimizelyUserContext, + decideOptions: DecideOptionsMap, + userProfileTracker?: UserProfileTracker, + ): Value<OP, DecisionResult> { + + // const decideReasons: DecisionReason[] = []; + // let variationKey = null; + // let decisionVariation; + // let index; + // let variationForFeatureExperiment; + + if (feature.experimentIds.length === 0) { + this.logger?.debug(FEATURE_HAS_NO_EXPERIMENTS, feature.key); + return Value.of(op, { + result: { + experiment: null, + variation: null, + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }, + reasons: [ + [FEATURE_HAS_NO_EXPERIMENTS, feature.key], + ], + }); + } + + return this.traverseFeatureExperimentList(op, configObj, feature, 0, user, [], decideOptions, userProfileTracker); + } + + private traverseFeatureExperimentList<OP extends OpType>( + op: OP, + configObj: ProjectConfig, + feature: FeatureFlag, + fromIndex: number, + user: OptimizelyUserContext, + decideReasons: DecisionReason[], + decideOptions: DecideOptionsMap, + userProfileTracker?: UserProfileTracker, + ): Value<OP, DecisionResult> { + const experimentIds = feature.experimentIds; + if (fromIndex >= experimentIds.length) { + return Value.of(op, { + result: { + experiment: null, + variation: null, + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }, + reasons: decideReasons, + }); + } + + const experiment = getExperimentFromId(configObj, experimentIds[fromIndex], this.logger); + if (!experiment) { + return this.traverseFeatureExperimentList( + op, configObj, feature, fromIndex + 1, user, decideReasons, decideOptions, userProfileTracker); + } + + const decisionVariationValue = this.getVariationFromExperimentRule( + op, configObj, feature.key, experiment, user, decideOptions, userProfileTracker, + ); + + return decisionVariationValue.then((decisionVariation) => { + decideReasons.push(...decisionVariation.reasons); + + if (decisionVariation.error) { + return Value.of(op, { + error: true, + result: { + experiment, + variation: null, + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }, + reasons: decideReasons, + }); + } + + if(!decisionVariation.result.variationKey) { + return this.traverseFeatureExperimentList( + op, configObj, feature, fromIndex + 1, user, decideReasons, decideOptions, userProfileTracker); + } + + const variationKey = decisionVariation.result.variationKey; + let variation: Variation | null = experiment.variationKeyMap[variationKey]; + if (!variation) { + variation = getFlagVariationByKey(configObj, feature.key, variationKey); + } + + return Value.of(op, { + result: { + cmabUuid: decisionVariation.result.cmabUuid, + experiment, + variation, + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }, + reasons: decideReasons, + }); + }); + } + + private getVariationForRollout( + configObj: ProjectConfig, + feature: FeatureFlag, + user: OptimizelyUserContext, + ): DecisionResponse<DecisionObj> { + const decideReasons: DecisionReason[] = []; + let decisionObj: DecisionObj; + if (!feature.rolloutId) { + this.logger?.debug(NO_ROLLOUT_EXISTS, feature.key); + decideReasons.push([NO_ROLLOUT_EXISTS, feature.key]); + decisionObj = { + experiment: null, + variation: null, + decisionSource: DECISION_SOURCES.ROLLOUT, + }; + + return { + result: decisionObj, + reasons: decideReasons, + }; + } + + const rollout = configObj.rolloutIdMap[feature.rolloutId]; + if (!rollout) { + this.logger?.error( + INVALID_ROLLOUT_ID, + feature.rolloutId, + feature.key, + ); + decideReasons.push([INVALID_ROLLOUT_ID, feature.rolloutId, feature.key]); + decisionObj = { + experiment: null, + variation: null, + decisionSource: DECISION_SOURCES.ROLLOUT, + }; + return { + result: decisionObj, + reasons: decideReasons, + }; + } + + const rolloutRules = rollout.experiments; + if (rolloutRules.length === 0) { + this.logger?.error( + ROLLOUT_HAS_NO_EXPERIMENTS, + feature.rolloutId, + ); + decideReasons.push([ROLLOUT_HAS_NO_EXPERIMENTS, feature.rolloutId]); + decisionObj = { + experiment: null, + variation: null, + decisionSource: DECISION_SOURCES.ROLLOUT, + }; + return { + result: decisionObj, + reasons: decideReasons, + }; + } + let decisionVariation; + let skipToEveryoneElse; + let variation; + let rolloutRule; + let index = 0; + while (index < rolloutRules.length) { + decisionVariation = this.getVariationFromDeliveryRule(configObj, feature.key, rolloutRules, index, user); + decideReasons.push(...decisionVariation.reasons); + variation = decisionVariation.result; + skipToEveryoneElse = decisionVariation.skipToEveryoneElse; + if (variation) { + rolloutRule = configObj.experimentIdMap[rolloutRules[index].id]; + decisionObj = { + experiment: rolloutRule, + variation: variation, + decisionSource: DECISION_SOURCES.ROLLOUT + }; + return { + result: decisionObj, + reasons: decideReasons, + }; + } + // the last rule is special for "Everyone Else" + index = skipToEveryoneElse ? (rolloutRules.length - 1) : (index + 1); + } + + decisionObj = { + experiment: null, + variation: null, + decisionSource: DECISION_SOURCES.ROLLOUT, + }; + + return { + result: decisionObj, + reasons: decideReasons, + }; + } + + /** + * Get bucketing Id from user attributes. + * @param {string} userId + * @param {UserAttributes} attributes + * @returns {string} Bucketing Id if it is a string type in attributes, user Id otherwise. + */ + private getBucketingId(userId: string, attributes?: UserAttributes): string { + let bucketingId = userId; + + // If the bucketing ID key is defined in attributes, than use that in place of the userID for the murmur hash key + if ( + attributes != null && + typeof attributes === 'object' && + attributes.hasOwnProperty(CONTROL_ATTRIBUTES.BUCKETING_ID) + ) { + if (typeof attributes[CONTROL_ATTRIBUTES.BUCKETING_ID] === 'string') { + bucketingId = String(attributes[CONTROL_ATTRIBUTES.BUCKETING_ID]); + this.logger?.debug(VALID_BUCKETING_ID, bucketingId); + } else { + this.logger?.warn(BUCKETING_ID_NOT_STRING); + } + } + + return bucketingId; + } + + /** + * Finds a validated forced decision for specific flagKey and optional ruleKey. + * @param {ProjectConfig} config A projectConfig. + * @param {OptimizelyUserContext} user A Optimizely User Context. + * @param {string} flagKey A flagKey. + * @param {ruleKey} ruleKey A ruleKey (optional). + * @return {DecisionResponse<Variation|null>} DecisionResponse object containing valid variation object and decide reasons. + */ + findValidatedForcedDecision( + config: ProjectConfig, + user: OptimizelyUserContext, + flagKey: string, + ruleKey?: string + ): DecisionResponse<Variation | null> { + + const decideReasons: DecisionReason[] = []; + const forcedDecision = user.getForcedDecision({ flagKey, ruleKey }); + let variation = null; + let variationKey; + const userId = user.getUserId() + if (config && forcedDecision) { + variationKey = forcedDecision.variationKey; + variation = getFlagVariationByKey(config, flagKey, variationKey); + if (variation) { + if (ruleKey) { + this.logger?.info( + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, + variationKey, + flagKey, + ruleKey, + userId + ); + decideReasons.push([ + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, + variationKey, + flagKey, + ruleKey, + userId + ]); + } else { + this.logger?.info( + USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, + variationKey, + flagKey, + userId + ); + decideReasons.push([ + USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, + variationKey, + flagKey, + userId + ]) + } + } else { + if (ruleKey) { + this.logger?.info( + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, + flagKey, + ruleKey, + userId + ); + decideReasons.push([ + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, + flagKey, + ruleKey, + userId + ]); + } else { + this.logger?.info( + USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, + flagKey, + userId + ); + decideReasons.push([ + USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, + flagKey, + userId + ]) + } + } + } + + return { + result: variation, + reasons: decideReasons, + } + } + + /** + * Removes forced variation for given userId and experimentKey + * @param {string} userId String representing the user id + * @param {string} experimentId Number representing the experiment id + * @param {string} experimentKey Key representing the experiment id + * @throws If the user id is not valid or not in the forced variation map + */ + private removeForcedVariation(userId: string, experimentId: string, experimentKey: string): void { + if (!userId) { + throw new OptimizelyError(INVALID_USER_ID); + } + + if (this.forcedVariationMap.hasOwnProperty(userId)) { + delete this.forcedVariationMap[userId][experimentId]; + this.logger?.debug( + VARIATION_REMOVED_FOR_USER, + experimentKey, + userId, + ); + } else { + throw new OptimizelyError(USER_NOT_IN_FORCED_VARIATION, userId); + } + } + + /** + * Sets forced variation for given userId and experimentKey + * @param {string} userId String representing the user id + * @param {string} experimentId Number representing the experiment id + * @param {number} variationId Number representing the variation id + * @throws If the user id is not valid + */ + private setInForcedVariationMap(userId: string, experimentId: string, variationId: string): void { + if (this.forcedVariationMap.hasOwnProperty(userId)) { + this.forcedVariationMap[userId][experimentId] = variationId; + } else { + this.forcedVariationMap[userId] = {}; + this.forcedVariationMap[userId][experimentId] = variationId; + } + + this.logger?.debug( + USER_MAPPED_TO_FORCED_VARIATION, + variationId, + experimentId, + userId, + ); + } + + /** + * Gets the forced variation key for the given user and experiment. + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} experimentKey Key for experiment. + * @param {string} userId The user Id. + * @return {DecisionResponse<string|null>} DecisionResponse containing variation which the given user and experiment + * should be forced into and the decide reasons. + */ + getForcedVariation( + configObj: ProjectConfig, + experimentKey: string, + userId: string + ): DecisionResponse<string | null> { + const decideReasons: DecisionReason[] = []; + const experimentToVariationMap = this.forcedVariationMap[userId]; + if (!experimentToVariationMap) { + this.logger?.debug( + USER_HAS_NO_FORCED_VARIATION, + userId, + ); + + return { + result: null, + reasons: decideReasons, + }; + } + + let experimentId; + try { + const experiment = getExperimentFromKey(configObj, experimentKey); + if (experiment.hasOwnProperty('id')) { + experimentId = experiment['id']; + } else { + // catching improperly formatted experiments + this.logger?.error( + IMPROPERLY_FORMATTED_EXPERIMENT, + experimentKey, + ); + decideReasons.push([ + IMPROPERLY_FORMATTED_EXPERIMENT, + experimentKey, + ]); + + return { + result: null, + reasons: decideReasons, + }; + } + } catch (ex: any) { + // catching experiment not in datafile + this.logger?.error(ex); + decideReasons.push(ex.message); + + return { + result: null, + reasons: decideReasons, + }; + } + + const variationId = experimentToVariationMap[experimentId]; + if (!variationId) { + this.logger?.debug( + USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, + experimentKey, + userId, + ); + return { + result: null, + reasons: decideReasons, + }; + } + + const variationKey = getVariationKeyFromId(configObj, variationId); + if (variationKey) { + this.logger?.debug( + USER_HAS_FORCED_VARIATION, + variationKey, + experimentKey, + userId, + ); + decideReasons.push([ + USER_HAS_FORCED_VARIATION, + variationKey, + experimentKey, + userId, + ]); + } else { + this.logger?.debug( + USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, + experimentKey, + userId, + ); + } + + return { + result: variationKey, + reasons: decideReasons, + }; + } + + /** + * Sets the forced variation for a user in a given experiment + * @param {ProjectConfig} configObj Object representing project configuration + * @param {string} experimentKey Key for experiment. + * @param {string} userId The user Id. + * @param {string|null} variationKey Key for variation. If null, then clear the existing experiment-to-variation mapping + * @return {boolean} A boolean value that indicates if the set completed successfully. + */ + setForcedVariation( + configObj: ProjectConfig, + experimentKey: string, + userId: string, + variationKey: string | null + ): boolean { + if (variationKey != null && !stringValidator.validate(variationKey)) { + this.logger?.error(INVALID_VARIATION_KEY); + return false; + } + + let experimentId; + try { + const experiment = getExperimentFromKey(configObj, experimentKey); + if (experiment.hasOwnProperty('id')) { + experimentId = experiment['id']; + } else { + // catching improperly formatted experiments + this.logger?.error( + IMPROPERLY_FORMATTED_EXPERIMENT, + experimentKey, + ); + return false; + } + } catch (ex: any) { + // catching experiment not in datafile + this.logger?.error(ex); + return false; + } + + if (variationKey == null) { + try { + this.removeForcedVariation(userId, experimentId, experimentKey); + return true; + } catch (ex: any) { + this.logger?.error(ex); + return false; + } + } + + const variationId = getVariationIdFromExperimentAndVariationKey(configObj, experimentKey, variationKey); + + if (!variationId) { + this.logger?.error( + NO_VARIATION_FOR_EXPERIMENT_KEY, + variationKey, + experimentKey, + ); + return false; + } + + try { + this.setInForcedVariationMap(userId, experimentId, variationId); + return true; + } catch (ex: any) { + this.logger?.error(ex); + return false; + } + } + + private getVariationFromExperimentRule<OP extends OpType>( + op: OP, + configObj: ProjectConfig, + flagKey: string, + rule: Experiment, + user: OptimizelyUserContext, + decideOptions: DecideOptionsMap, + userProfileTracker?: UserProfileTracker, + ): Value<OP, VariationResult> { + const decideReasons: DecisionReason[] = []; + + // check forced decision first + const forcedDecisionResponse = this.findValidatedForcedDecision(configObj, user, flagKey, rule.key); + decideReasons.push(...forcedDecisionResponse.reasons); + + const forcedVariation = forcedDecisionResponse.result; + if (forcedVariation) { + return Value.of(op, { + result: { variationKey: forcedVariation.key }, + reasons: decideReasons, + }); + } + const decisionVariationValue = this.resolveVariation(op, configObj, rule, user, decideOptions, userProfileTracker); + + return decisionVariationValue.then((variationResult) => { + decideReasons.push(...variationResult.reasons); + return Value.of(op, { + error: variationResult.error, + result: variationResult.result, + reasons: decideReasons, + }); + }); + + // return response; + + // decideReasons.push(...decisionVariation.reasons); + // const variationKey = decisionVariation.result; + + // return { + // result: variationKey, + // reasons: decideReasons, + // }; + } + + private getVariationFromDeliveryRule( + configObj: ProjectConfig, + flagKey: string, + rules: Experiment[], + ruleIndex: number, + user: OptimizelyUserContext + ): DeliveryRuleResponse<Variation | null, boolean> { + const decideReasons: DecisionReason[] = []; + let skipToEveryoneElse = false; + + // check forced decision first + const rule = rules[ruleIndex]; + const forcedDecisionResponse = this.findValidatedForcedDecision(configObj, user, flagKey, rule.key); + decideReasons.push(...forcedDecisionResponse.reasons); + + const forcedVariation = forcedDecisionResponse.result; + if (forcedVariation) { + return { + result: forcedVariation, + reasons: decideReasons, + skipToEveryoneElse, + }; + } + + const userId = user.getUserId(); + const attributes = user.getAttributes(); + const bucketingId = this.getBucketingId(userId, attributes); + const everyoneElse = ruleIndex === rules.length - 1; + const loggingKey = everyoneElse ? "Everyone Else" : ruleIndex + 1; + + let bucketedVariation = null; + let bucketerVariationId; + let bucketerParams; + let decisionVariation; + const decisionifUserIsInAudience = this.checkIfUserIsInAudience( + configObj, + rule, + AUDIENCE_EVALUATION_TYPES.RULE, + user, + loggingKey + ); + decideReasons.push(...decisionifUserIsInAudience.reasons); + if (decisionifUserIsInAudience.result) { + this.logger?.debug( + USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, + userId, + loggingKey + ); + decideReasons.push([ + USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, + userId, + loggingKey + ]); + + bucketerParams = this.buildBucketerParams(configObj, rule, bucketingId, userId); + decisionVariation = bucket(bucketerParams); + decideReasons.push(...decisionVariation.reasons); + bucketerVariationId = decisionVariation.result; + if (bucketerVariationId) { + bucketedVariation = getVariationFromId(configObj, bucketerVariationId); + } + if (bucketedVariation) { + this.logger?.debug( + USER_BUCKETED_INTO_TARGETING_RULE, + userId, + loggingKey + ); + decideReasons.push([ + USER_BUCKETED_INTO_TARGETING_RULE, + userId, + loggingKey]); + } else if (!everyoneElse) { + // skip this logging for EveryoneElse since this has a message not for EveryoneElse + this.logger?.debug( + USER_NOT_BUCKETED_INTO_TARGETING_RULE, + userId, + loggingKey + ); + decideReasons.push([ + USER_NOT_BUCKETED_INTO_TARGETING_RULE, + userId, + loggingKey + ]); + + // skip the rest of rollout rules to the everyone-else rule if audience matches but not bucketed + skipToEveryoneElse = true; + } + } else { + this.logger?.debug( + USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, + userId, + loggingKey + ); + decideReasons.push([ + USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, + userId, + loggingKey + ]); + } + + return { + result: bucketedVariation, + reasons: decideReasons, + skipToEveryoneElse, + }; + } +} + +/** + * Creates an instance of the DecisionService. + * @param {DecisionServiceOptions} options Configuration options + * @return {Object} An instance of the DecisionService + */ +export function createDecisionService(options: DecisionServiceOptions): DecisionService { + return new DecisionService(options); +} diff --git a/lib/entrypoint.test-d.ts b/lib/entrypoint.test-d.ts new file mode 100644 index 000000000..366889ea8 --- /dev/null +++ b/lib/entrypoint.test-d.ts @@ -0,0 +1,110 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expectTypeOf } from 'vitest'; + +import * as browser from './index.browser'; +import * as node from './index.node'; +import * as reactNative from './index.react_native'; + +type WithoutReadonly<T> = { -readonly [P in keyof T]: T[P] }; + +const nodeEntrypoint: WithoutReadonly<typeof node> = node; +const browserEntrypoint: WithoutReadonly<typeof browser> = browser; +const reactNativeEntrypoint: WithoutReadonly<typeof reactNative> = reactNative; + +import { + Config, + Client, + StaticConfigManagerConfig, + OpaqueConfigManager, + PollingConfigManagerConfig, + EventDispatcher, + OpaqueEventProcessor, + BatchEventProcessorOptions, + OdpManagerOptions, + OpaqueOdpManager, + VuidManagerOptions, + OpaqueVuidManager, + OpaqueLevelPreset, + LoggerConfig, + OpaqueLogger, + ErrorHandler, + OpaqueErrorNotifier, +} from './export_types'; + +import { + DECISION_SOURCES, +} from './utils/enums'; + +import { NOTIFICATION_TYPES, DECISION_NOTIFICATION_TYPES } from './notification_center/type'; + +import { LogLevel } from './logging/logger'; + +import { OptimizelyDecideOption } from './shared_types'; +import { Maybe } from './utils/type'; + +export type Entrypoint = { + // client factory + createInstance: (config: Config) => Client; + + // config manager related exports + createStaticProjectConfigManager: (config: StaticConfigManagerConfig) => OpaqueConfigManager; + createPollingProjectConfigManager: (config: PollingConfigManagerConfig) => OpaqueConfigManager; + + // event processor related exports + eventDispatcher: EventDispatcher; + getSendBeaconEventDispatcher: () => Maybe<EventDispatcher>; + createForwardingEventProcessor: (eventDispatcher?: EventDispatcher) => OpaqueEventProcessor; + createBatchEventProcessor: (options?: BatchEventProcessorOptions) => OpaqueEventProcessor; + + // odp manager related exports + createOdpManager: (options?: OdpManagerOptions) => OpaqueOdpManager; + + // vuid manager related exports + createVuidManager: (options?: VuidManagerOptions) => OpaqueVuidManager; + + // logger related exports + LogLevel: typeof LogLevel; + DEBUG: OpaqueLevelPreset, + INFO: OpaqueLevelPreset, + WARN: OpaqueLevelPreset, + ERROR: OpaqueLevelPreset, + createLogger: (config: LoggerConfig) => OpaqueLogger; + + // error related exports + createErrorNotifier: (errorHandler: ErrorHandler) => OpaqueErrorNotifier; + + // enums + DECISION_SOURCES: typeof DECISION_SOURCES; + NOTIFICATION_TYPES: typeof NOTIFICATION_TYPES; + DECISION_NOTIFICATION_TYPES: typeof DECISION_NOTIFICATION_TYPES; + + // decide options + OptimizelyDecideOption: typeof OptimizelyDecideOption; + + // client engine + clientEngine: string; +} + + +expectTypeOf(browserEntrypoint).toEqualTypeOf<Entrypoint>(); +expectTypeOf(nodeEntrypoint).toEqualTypeOf<Entrypoint>(); +expectTypeOf(reactNativeEntrypoint).toEqualTypeOf<Entrypoint>(); + +expectTypeOf(browserEntrypoint).toEqualTypeOf(nodeEntrypoint); +expectTypeOf(browserEntrypoint).toEqualTypeOf(reactNativeEntrypoint); +expectTypeOf(nodeEntrypoint).toEqualTypeOf(reactNativeEntrypoint); diff --git a/lib/entrypoint.universal.test-d.ts b/lib/entrypoint.universal.test-d.ts new file mode 100644 index 000000000..184583a35 --- /dev/null +++ b/lib/entrypoint.universal.test-d.ts @@ -0,0 +1,98 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { expectTypeOf } from 'vitest'; + +import * as universal from './index.universal'; + +type WithoutReadonly<T> = { -readonly [P in keyof T]: T[P] }; + +const universalEntrypoint: WithoutReadonly<typeof universal> = universal; + +import { + Config, + Client, + StaticConfigManagerConfig, + OpaqueConfigManager, + EventDispatcher, + OpaqueEventProcessor, + OpaqueLevelPreset, + LoggerConfig, + OpaqueLogger, + ErrorHandler, + OpaqueErrorNotifier, +} from './export_types'; + +import { UniversalPollingConfigManagerConfig } from './project_config/config_manager_factory.universal'; +import { RequestHandler } from './utils/http_request_handler/http'; +import { UniversalBatchEventProcessorOptions } from './event_processor/event_processor_factory.universal'; +import { + DECISION_SOURCES, +} from './utils/enums'; + +import { NOTIFICATION_TYPES, DECISION_NOTIFICATION_TYPES } from './notification_center/type'; + +import { LogLevel } from './logging/logger'; + +import { OptimizelyDecideOption } from './shared_types'; +import { UniversalConfig } from './index.universal'; +import { OpaqueOdpManager } from './odp/odp_manager_factory'; + +import { UniversalOdpManagerOptions } from './odp/odp_manager_factory.universal'; + +export type UniversalEntrypoint = { + // client factory + createInstance: (config: UniversalConfig) => Client; + + // config manager related exports + createStaticProjectConfigManager: (config: StaticConfigManagerConfig) => OpaqueConfigManager; + createPollingProjectConfigManager: (config: UniversalPollingConfigManagerConfig) => OpaqueConfigManager; + + // event processor related exports + createEventDispatcher: (requestHandler: RequestHandler) => EventDispatcher; + createForwardingEventProcessor: (eventDispatcher: EventDispatcher) => OpaqueEventProcessor; + createBatchEventProcessor: (options: UniversalBatchEventProcessorOptions) => OpaqueEventProcessor; + + createOdpManager: (options: UniversalOdpManagerOptions) => OpaqueOdpManager; + + // TODO: vuid manager related exports + // createVuidManager: (options: VuidManagerOptions) => OpaqueVuidManager; + + // logger related exports + LogLevel: typeof LogLevel; + DEBUG: OpaqueLevelPreset, + INFO: OpaqueLevelPreset, + WARN: OpaqueLevelPreset, + ERROR: OpaqueLevelPreset, + createLogger: (config: LoggerConfig) => OpaqueLogger; + + // error related exports + createErrorNotifier: (errorHandler: ErrorHandler) => OpaqueErrorNotifier; + + // enums + DECISION_SOURCES: typeof DECISION_SOURCES; + NOTIFICATION_TYPES: typeof NOTIFICATION_TYPES; + DECISION_NOTIFICATION_TYPES: typeof DECISION_NOTIFICATION_TYPES; + + // decide options + OptimizelyDecideOption: typeof OptimizelyDecideOption; + + // client engine + clientEngine: string; +} + + +expectTypeOf(universalEntrypoint).toEqualTypeOf<UniversalEntrypoint>(); diff --git a/packages/optimizely-sdk/lib/plugins/error_handler/index.ts b/lib/error/error_handler.ts similarity index 72% rename from packages/optimizely-sdk/lib/plugins/error_handler/index.ts rename to lib/error/error_handler.ts index 7afb8c5e3..4a772c71c 100644 --- a/packages/optimizely-sdk/lib/plugins/error_handler/index.ts +++ b/lib/error/error_handler.ts @@ -1,5 +1,5 @@ /** - * Copyright 2016, 2020-2021, Optimizely + * Copyright 2019, 2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - /** - * Default error handler implementation + * @export + * @interface ErrorHandler */ -export function handleError(): void { - // no-op -} - -export default { - handleError, +export interface ErrorHandler { + /** + * @param {Error} exception + * @memberof ErrorHandler + */ + handleError(exception: Error): void } diff --git a/lib/error/error_notifier.spec.ts b/lib/error/error_notifier.spec.ts new file mode 100644 index 000000000..7c2b19d89 --- /dev/null +++ b/lib/error/error_notifier.spec.ts @@ -0,0 +1,50 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, vi } from 'vitest'; + +import { DefaultErrorNotifier } from './error_notifier'; +import { OptimizelyError } from './optimizly_error'; + +const mockMessageResolver = (prefix = '') => { + return { + resolve: vi.fn().mockImplementation((message) => `${prefix} ${message}`), + }; +} + +describe('DefaultErrorNotifier', () => { + it('should call the error handler with the error if the error is not an OptimizelyError', () => { + const errorHandler = { handleError: vi.fn() }; + const messageResolver = mockMessageResolver(); + const errorNotifier = new DefaultErrorNotifier(errorHandler, messageResolver); + + const error = new Error('error'); + errorNotifier.notify(error); + + expect(errorHandler.handleError).toHaveBeenCalledWith(error); + }); + + it('should resolve the message of an OptimizelyError before calling the error handler', () => { + const errorHandler = { handleError: vi.fn() }; + const messageResolver = mockMessageResolver('err'); + const errorNotifier = new DefaultErrorNotifier(errorHandler, messageResolver); + + const error = new OptimizelyError('test %s', 'one'); + errorNotifier.notify(error); + + expect(errorHandler.handleError).toHaveBeenCalledWith(error); + expect(error.message).toBe('err test one'); + }); +}); diff --git a/lib/error/error_notifier.ts b/lib/error/error_notifier.ts new file mode 100644 index 000000000..174c163e2 --- /dev/null +++ b/lib/error/error_notifier.ts @@ -0,0 +1,46 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { MessageResolver } from "../message/message_resolver"; +import { ErrorHandler } from "./error_handler"; +import { OptimizelyError } from "./optimizly_error"; + +export interface ErrorNotifier { + notify(error: Error): void; + child(name: string): ErrorNotifier; +} + +export class DefaultErrorNotifier implements ErrorNotifier { + private name: string; + private errorHandler: ErrorHandler; + private messageResolver: MessageResolver; + + constructor(errorHandler: ErrorHandler, messageResolver: MessageResolver, name?: string) { + this.errorHandler = errorHandler; + this.messageResolver = messageResolver; + this.name = name || ''; + } + + notify(error: Error): void { + if (error instanceof OptimizelyError) { + error.setMessage(this.messageResolver); + } + this.errorHandler.handleError(error); + } + + child(name: string): ErrorNotifier { + return new DefaultErrorNotifier(this.errorHandler, this.messageResolver, name); + } +} diff --git a/lib/error/error_notifier_factory.spec.ts b/lib/error/error_notifier_factory.spec.ts new file mode 100644 index 000000000..556d7f2af --- /dev/null +++ b/lib/error/error_notifier_factory.spec.ts @@ -0,0 +1,33 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect } from 'vitest'; +import { createErrorNotifier } from './error_notifier_factory'; + +describe('createErrorNotifier', () => { + it('should throw errors for invalid error handlers', () => { + expect(() => createErrorNotifier(null as any)).toThrow('Invalid error handler'); + expect(() => createErrorNotifier(undefined as any)).toThrow('Invalid error handler'); + + + expect(() => createErrorNotifier('abc' as any)).toThrow('Invalid error handler'); + expect(() => createErrorNotifier(123 as any)).toThrow('Invalid error handler'); + + expect(() => createErrorNotifier({} as any)).toThrow('Invalid error handler'); + + expect(() => createErrorNotifier({ handleError: 'abc' } as any)).toThrow('Invalid error handler'); + }); +}); diff --git a/lib/error/error_notifier_factory.ts b/lib/error/error_notifier_factory.ts new file mode 100644 index 000000000..994564f1a --- /dev/null +++ b/lib/error/error_notifier_factory.ts @@ -0,0 +1,48 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { errorResolver } from "../message/message_resolver"; +import { Maybe } from "../utils/type"; +import { ErrorHandler } from "./error_handler"; +import { DefaultErrorNotifier } from "./error_notifier"; + +export const INVALID_ERROR_HANDLER = 'Invalid error handler'; + +const errorNotifierSymbol = Symbol(); + +export type OpaqueErrorNotifier = { + [errorNotifierSymbol]: unknown; +}; + +const validateErrorHandler = (errorHandler: ErrorHandler) => { + if (!errorHandler || typeof errorHandler !== 'object' || typeof errorHandler.handleError !== 'function') { + throw new Error(INVALID_ERROR_HANDLER); + } +} + +export const createErrorNotifier = (errorHandler: ErrorHandler): OpaqueErrorNotifier => { + validateErrorHandler(errorHandler); + return { + [errorNotifierSymbol]: new DefaultErrorNotifier(errorHandler, errorResolver), + } +} + +export const extractErrorNotifier = (errorNotifier: Maybe<OpaqueErrorNotifier>): Maybe<DefaultErrorNotifier> => { + if (!errorNotifier || typeof errorNotifier !== 'object') { + return undefined; + } + + return errorNotifier[errorNotifierSymbol] as Maybe<DefaultErrorNotifier>; +} diff --git a/lib/error/error_reporter.spec.ts b/lib/error/error_reporter.spec.ts new file mode 100644 index 000000000..abdd932d0 --- /dev/null +++ b/lib/error/error_reporter.spec.ts @@ -0,0 +1,60 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, vi } from 'vitest'; + +import { ErrorReporter } from './error_reporter'; + +import { OptimizelyError } from './optimizly_error'; + +const mockMessageResolver = (prefix = '') => { + return { + resolve: vi.fn().mockImplementation((message) => `${prefix} ${message}`), + }; +} + +describe('ErrorReporter', () => { + it('should call the logger and errorNotifier with the first argument if it is an Error object', () => { + const logger = { error: vi.fn() }; + const errorNotifier = { notify: vi.fn() }; + const errorReporter = new ErrorReporter(logger as any, errorNotifier as any); + + const error = new Error('error'); + errorReporter.report(error); + + expect(logger.error).toHaveBeenCalledWith(error); + expect(errorNotifier.notify).toHaveBeenCalledWith(error); + }); + + it('should create an OptimizelyError and call the logger and errorNotifier with it if the first argument is a string', () => { + const logger = { error: vi.fn() }; + const errorNotifier = { notify: vi.fn() }; + const errorReporter = new ErrorReporter(logger as any, errorNotifier as any); + + errorReporter.report('message', 1, 2); + + expect(logger.error).toHaveBeenCalled(); + const loggedError = logger.error.mock.calls[0][0]; + expect(loggedError).toBeInstanceOf(OptimizelyError); + expect(loggedError.baseMessage).toBe('message'); + expect(loggedError.params).toEqual([1, 2]); + + expect(errorNotifier.notify).toHaveBeenCalled(); + const notifiedError = errorNotifier.notify.mock.calls[0][0]; + expect(notifiedError).toBeInstanceOf(OptimizelyError); + expect(notifiedError.baseMessage).toBe('message'); + expect(notifiedError.params).toEqual([1, 2]); + }); +}); diff --git a/lib/error/error_reporter.ts b/lib/error/error_reporter.ts new file mode 100644 index 000000000..130527928 --- /dev/null +++ b/lib/error/error_reporter.ts @@ -0,0 +1,55 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LoggerFacade } from "../logging/logger"; +import { ErrorNotifier } from "./error_notifier"; +import { OptimizelyError } from "./optimizly_error"; + +export class ErrorReporter { + private logger?: LoggerFacade; + private errorNotifier?: ErrorNotifier; + + constructor(logger?: LoggerFacade, errorNotifier?: ErrorNotifier) { + this.logger = logger; + this.errorNotifier = errorNotifier; + } + + report(error: Error): void; + report(baseMessage: string, ...params: any[]): void; + + report(error: Error | string, ...params: any[]): void { + if (typeof error === 'string') { + error = new OptimizelyError(error, ...params); + this.report(error); + return; + } + + if (this.errorNotifier) { + this.errorNotifier.notify(error); + } + + if (this.logger) { + this.logger.error(error); + } + } + + setLogger(logger: LoggerFacade): void { + this.logger = logger; + } + + setErrorNotifier(errorNotifier: ErrorNotifier): void { + this.errorNotifier = errorNotifier; + } +} diff --git a/lib/error/optimizly_error.ts b/lib/error/optimizly_error.ts new file mode 100644 index 000000000..76a07511a --- /dev/null +++ b/lib/error/optimizly_error.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { MessageResolver } from "../message/message_resolver"; +import { sprintf } from "../utils/fns"; + +export class OptimizelyError extends Error { + baseMessage: string; + params: any[]; + private resolved = false; + constructor(baseMessage: string, ...params: any[]) { + super(); + this.name = 'OptimizelyError'; + this.baseMessage = baseMessage; + this.params = params; + + // this is needed cause instanceof doesn't work for + // custom Errors when TS is compiled to es5 + Object.setPrototypeOf(this, OptimizelyError.prototype); + } + + setMessage(resolver: MessageResolver): void { + if (!this.resolved) { + this.message = sprintf(resolver.resolve(this.baseMessage), ...this.params); + this.resolved = true; + } + } +} diff --git a/lib/event_processor/batch_event_processor.react_native.spec.ts b/lib/event_processor/batch_event_processor.react_native.spec.ts new file mode 100644 index 000000000..5e17ca966 --- /dev/null +++ b/lib/event_processor/batch_event_processor.react_native.spec.ts @@ -0,0 +1,169 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { vi, describe, it, expect, beforeEach } from 'vitest'; + +const mockNetInfo = vi.hoisted(() => { + const netInfo = { + listeners: [] as any[], + unsubs: [] as any[], + addEventListener(fn: any) { + this.listeners.push(fn); + const unsub = vi.fn(); + this.unsubs.push(unsub); + return unsub; + }, + pushState(state: boolean) { + for (const listener of this.listeners) { + listener({ isInternetReachable: state }); + } + }, + clear() { + this.listeners = []; + this.unsubs = []; + } + }; + return netInfo; +}); + +vi.mock('@react-native-community/netinfo', () => { + return { + addEventListener: mockNetInfo.addEventListener.bind(mockNetInfo), + }; +}); + +import { ReactNativeNetInfoEventProcessor } from './batch_event_processor.react_native'; +import { getMockRepeater } from '../tests/mock/mock_repeater'; +import { getMockAsyncCache } from '../tests/mock/mock_cache'; + +import { EventWithId } from './batch_event_processor'; +import { buildLogEvent } from './event_builder/log_event'; +import { createImpressionEvent } from '../tests/mock/create_event'; +import { ProcessableEvent } from './event_processor'; + +const getMockDispatcher = () => { + return { + dispatchEvent: vi.fn(), + }; +}; + +const exhaustMicrotasks = async (loop = 100) => { + for(let i = 0; i < loop; i++) { + await Promise.resolve(); + } +} + + +describe('ReactNativeNetInfoEventProcessor', () => { + beforeEach(() => { + mockNetInfo.clear(); + }); + + it('should not retry failed events when reachable state does not change', async () => { + const eventDispatcher = getMockDispatcher(); + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const cache = getMockAsyncCache<EventWithId>(); + const events: ProcessableEvent[] = []; + + for(let i = 0; i < 5; i++) { + const id = `id-${i}`; + const event = createImpressionEvent(id); + events.push(event); + await cache.set(id, { id, event }); + } + + const processor = new ReactNativeNetInfoEventProcessor({ + eventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 1000, + eventStore: cache, + }); + + processor.start(); + await processor.onRunning(); + + mockNetInfo.pushState(true); + expect(eventDispatcher.dispatchEvent).not.toHaveBeenCalled(); + + mockNetInfo.pushState(true); + expect(eventDispatcher.dispatchEvent).not.toHaveBeenCalled(); + }); + + it('should retry failed events when network becomes reachable', async () => { + const eventDispatcher = getMockDispatcher(); + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const cache = getMockAsyncCache<EventWithId>(); + const events: ProcessableEvent[] = []; + + for(let i = 0; i < 5; i++) { + const id = `id-${i}`; + const event = createImpressionEvent(id); + events.push(event); + await cache.set(id, { id, event }); + } + + const processor = new ReactNativeNetInfoEventProcessor({ + eventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 1000, + eventStore: cache, + }); + + processor.start(); + await processor.onRunning(); + + mockNetInfo.pushState(false); + expect(eventDispatcher.dispatchEvent).not.toHaveBeenCalled(); + + mockNetInfo.pushState(true); + + await exhaustMicrotasks(); + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledWith(buildLogEvent(events)); + }); + + it('should unsubscribe from netinfo listener when stopped', async () => { + const eventDispatcher = getMockDispatcher(); + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const cache = getMockAsyncCache<EventWithId>(); + + const processor = new ReactNativeNetInfoEventProcessor({ + eventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 1000, + eventStore: cache, + }); + + processor.start(); + await processor.onRunning(); + + mockNetInfo.pushState(false); + + processor.stop(); + await processor.onTerminated(); + + expect(mockNetInfo.unsubs[0]).toHaveBeenCalled(); + }); +}); diff --git a/lib/event_processor/batch_event_processor.react_native.ts b/lib/event_processor/batch_event_processor.react_native.ts new file mode 100644 index 000000000..28741380a --- /dev/null +++ b/lib/event_processor/batch_event_processor.react_native.ts @@ -0,0 +1,51 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NetInfoState, addEventListener } from '@react-native-community/netinfo'; + +import { BatchEventProcessor, BatchEventProcessorConfig } from './batch_event_processor'; +import { Fn } from '../utils/type'; + +export class ReactNativeNetInfoEventProcessor extends BatchEventProcessor { + private isInternetReachable = true; + private unsubscribeNetInfo?: Fn; + + constructor(config: BatchEventProcessorConfig) { + super(config); + } + + private async connectionListener(state: NetInfoState) { + if (this.isInternetReachable && !state.isInternetReachable) { + this.isInternetReachable = false; + return; + } + + if (!this.isInternetReachable && state.isInternetReachable) { + this.isInternetReachable = true; + this.retryFailedEvents(); + } + } + + start(): void { + super.start(); + this.unsubscribeNetInfo = addEventListener(this.connectionListener.bind(this)); + } + + stop(): void { + this.unsubscribeNetInfo?.(); + super.stop(); + } +} diff --git a/lib/event_processor/batch_event_processor.spec.ts b/lib/event_processor/batch_event_processor.spec.ts new file mode 100644 index 000000000..6d7674fd5 --- /dev/null +++ b/lib/event_processor/batch_event_processor.spec.ts @@ -0,0 +1,1483 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { expect, describe, it, vi, beforeEach, afterEach, MockInstance } from 'vitest'; + +import { EventWithId, BatchEventProcessor, LOGGER_NAME } from './batch_event_processor'; +import { getMockAsyncCache, getMockSyncCache } from '../tests/mock/mock_cache'; +import { createImpressionEvent } from '../tests/mock/create_event'; +import { ProcessableEvent } from './event_processor'; +import { buildLogEvent } from './event_builder/log_event'; +import { ResolvablePromise, resolvablePromise } from '../utils/promise/resolvablePromise'; +import { advanceTimersByTime } from '../tests/testUtils'; +import { getMockLogger } from '../tests/mock/mock_logger'; +import { getMockRepeater } from '../tests/mock/mock_repeater'; +import * as retry from '../utils/executor/backoff_retry_runner'; +import { ServiceState, StartupLog } from '../service'; +import { LogLevel } from '../logging/logger'; +import { IdGenerator } from '../utils/id_generator'; + +const getMockDispatcher = () => { + return { + dispatchEvent: vi.fn(), + }; +}; + +const exhaustMicrotasks = async (loop = 100) => { + for(let i = 0; i < loop; i++) { + await Promise.resolve(); + } +} + +describe('BatchEventProcessor', async () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('should set name on the logger passed into the constructor', () => { + const logger = getMockLogger(); + + const processor = new BatchEventProcessor({ + eventDispatcher: getMockDispatcher(), + dispatchRepeater: getMockRepeater(), + batchSize: 1000, + logger, + }); + + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME); + }); + + it('should set name on the logger set by setLogger', () => { + const logger = getMockLogger(); + + const processor = new BatchEventProcessor({ + eventDispatcher: getMockDispatcher(), + dispatchRepeater: getMockRepeater(), + batchSize: 1000, + }); + + processor.setLogger(logger); + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME); + }); + + describe('start', () => { + it('should log startupLogs on start', () => { + const startupLogs: StartupLog[] = [ + { + level: LogLevel.Warn, + message: 'warn message', + params: [1, 2] + }, + { + level: LogLevel.Error, + message: 'error message', + params: [3, 4] + }, + ]; + + const logger = getMockLogger(); + + const processor = new BatchEventProcessor({ + eventDispatcher: getMockDispatcher(), + dispatchRepeater: getMockRepeater(), + batchSize: 1000, + startupLogs, + }); + + processor.setLogger(logger); + processor.start(); + + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.warn).toHaveBeenCalledWith('warn message', 1, 2); + expect(logger.error).toHaveBeenCalledTimes(1); + expect(logger.error).toHaveBeenCalledWith('error message', 3, 4); + }); + + it('should resolve onRunning() when start() is called', async () => { + const eventDispatcher = getMockDispatcher(); + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater: getMockRepeater(), + batchSize: 1000, + }); + + processor.start(); + await expect(processor.onRunning()).resolves.not.toThrow(); + }); + + it('should start failedEventRepeater', () => { + const eventDispatcher = getMockDispatcher(); + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 1000, + }); + + processor.start(); + expect(failedEventRepeater.start).toHaveBeenCalledOnce(); + }); + + it('should dispatch failed events in correct batch sizes and order', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue({}); + + const cache = getMockSyncCache<EventWithId>(); + const events: ProcessableEvent[] = []; + + for(let i = 0; i < 5; i++) { + const id = `id-${i}`; + const event = createImpressionEvent(id); + events.push(event); + cache.set(id, { id, event }); + } + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater: getMockRepeater(), + batchSize: 2, + eventStore: cache, + }); + + processor.start(); + await processor.onRunning(); + + await exhaustMicrotasks(); + + expect(mockDispatch).toHaveBeenCalledTimes(3); + expect(mockDispatch.mock.calls[0][0]).toEqual(buildLogEvent([events[0], events[1]])); + expect(mockDispatch.mock.calls[1][0]).toEqual(buildLogEvent([events[2], events[3]])); + expect(mockDispatch.mock.calls[2][0]).toEqual(buildLogEvent([events[4]])); + }); + }); + + describe('process', () => { + it('should return a promise that rejects if processor is not running', async () => { + const eventDispatcher = getMockDispatcher(); + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater: getMockRepeater(), + batchSize: 100, + }); + + await expect(processor.process(createImpressionEvent('id-1'))).rejects.toThrow(); + }); + + it('should enqueue event without dispatching immediately', async () => { + const eventDispatcher = getMockDispatcher(); + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater: getMockRepeater(), + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + for(let i = 0; i < 99; i++) { + const event = createImpressionEvent(`id-${i}`); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + }); + + it('should start the dispatchRepeater if it is not running', async () => { + const eventDispatcher = getMockDispatcher(); + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + + const event = createImpressionEvent('id-1'); + await processor.process(event); + + expect(dispatchRepeater.start).toHaveBeenCalledOnce(); + }); + + it('should dispatch events if queue is full and clear queue', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue({}); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater: getMockRepeater(), + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + + let events: ProcessableEvent[] = []; + for(let i = 0; i < 99; i++){ + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + + let event = createImpressionEvent('id-99'); + events.push(event); + await processor.process(event); + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + expect(eventDispatcher.dispatchEvent.mock.calls[0][0]).toEqual(buildLogEvent(events)); + + events = []; + + for(let i = 100; i < 199; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + + event = createImpressionEvent('id-199'); + events.push(event); + await processor.process(event); + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(2); + expect(eventDispatcher.dispatchEvent.mock.calls[1][0]).toEqual(buildLogEvent(events)); + }); + + it('should flush queue is context of the new event is different and enqueue the new event', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue({}); + + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 80; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + + const newEvent = createImpressionEvent('id-a'); + newEvent.context.accountId = 'account-' + Math.random(); + await processor.process(newEvent); + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + expect(eventDispatcher.dispatchEvent.mock.calls[0][0]).toEqual(buildLogEvent(events)); + + await dispatchRepeater.execute(0); + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(2); + expect(eventDispatcher.dispatchEvent.mock.calls[1][0]).toEqual(buildLogEvent([newEvent])); + }); + + it('should flush queue immediately regardless of batchSize, if event processor is disposable', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue({}); + + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 100, + }); + + processor.makeDisposable(); + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + const event = createImpressionEvent('id-1'); + events.push(event); + await processor.process(event); + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + expect(eventDispatcher.dispatchEvent.mock.calls[0][0]).toEqual(buildLogEvent(events)); + expect(dispatchRepeater.reset).toHaveBeenCalledTimes(1); + expect(dispatchRepeater.start).not.toHaveBeenCalled(); + expect(failedEventRepeater.start).not.toHaveBeenCalled(); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(processor.retryConfig?.maxRetries).toEqual(5); + }); + + it('should store the event in the eventStore with increasing ids', async () => { + const eventDispatcher = getMockDispatcher(); + const eventStore = getMockSyncCache<EventWithId>(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater: getMockRepeater(), + batchSize: 100, + eventStore, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event) + } + + expect(eventStore.size()).toEqual(10); + + const eventsInStore = Array.from(eventStore.getAll().values()) + .sort((a, b) => a < b ? -1 : 1).map(e => e.event); + + expect(events).toEqual(eventsInStore); + }); + + it('should not store the event in the eventStore but still dispatch if the \ + number of pending events is greater than the limit', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue(resolvablePromise().promise); + + const eventStore = getMockSyncCache<EventWithId>(); + + const idGenerator = new IdGenerator(); + + for (let i = 0; i < 505; i++) { + const event = createImpressionEvent(`id-${i}`); + const cacheId = idGenerator.getId(); + await eventStore.set(cacheId, { id: cacheId, event }); + } + + expect(eventStore.size()).toEqual(505); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater: getMockRepeater(), + batchSize: 1, + eventStore, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 2; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event) + } + + expect(eventStore.size()).toEqual(505); + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(507); + expect(eventDispatcher.dispatchEvent.mock.calls[505][0]).toEqual(buildLogEvent([events[0]])); + expect(eventDispatcher.dispatchEvent.mock.calls[506][0]).toEqual(buildLogEvent([events[1]])); + }); + + it('should store events in the eventStore when the number of events in the store\ + becomes lower than the limit', async () => { + const eventDispatcher = getMockDispatcher(); + + const dispatchResponses: ResolvablePromise<any>[] = []; + + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockImplementation((arg) => { + const dispatchResponse = resolvablePromise(); + dispatchResponses.push(dispatchResponse); + return dispatchResponse.promise; + }); + + const eventStore = getMockSyncCache<EventWithId>(); + + const idGenerator = new IdGenerator(); + + for (let i = 0; i < 502; i++) { + const event = createImpressionEvent(`id-${i}`); + const cacheId = String(i); + await eventStore.set(cacheId, { id: cacheId, event }); + } + + expect(eventStore.size()).toEqual(502); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater: getMockRepeater(), + batchSize: 1, + eventStore, + }); + + processor.start(); + await processor.onRunning(); + + let events: ProcessableEvent[] = []; + for(let i = 0; i < 2; i++) { + const event = createImpressionEvent(`id-${i + 502}`); + events.push(event); + await processor.process(event) + } + + expect(eventStore.size()).toEqual(502); + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(504); + + expect(eventDispatcher.dispatchEvent.mock.calls[502][0]).toEqual(buildLogEvent([events[0]])); + expect(eventDispatcher.dispatchEvent.mock.calls[503][0]).toEqual(buildLogEvent([events[1]])); + + // resolve the dispatch for events not saved in the store + dispatchResponses[502].resolve({ statusCode: 200 }); + dispatchResponses[503].resolve({ statusCode: 200 }); + + await exhaustMicrotasks(); + expect(eventStore.size()).toEqual(502); + + // resolve the dispatch for 3 events in store, making the store size 499 which is lower than the limit + dispatchResponses[0].resolve({ statusCode: 200 }); + dispatchResponses[1].resolve({ statusCode: 200 }); + dispatchResponses[2].resolve({ statusCode: 200 }); + + await exhaustMicrotasks(); + expect(eventStore.size()).toEqual(499); + + // process 2 more events + events = []; + for(let i = 0; i < 2; i++) { + const event = createImpressionEvent(`id-${i + 504}`); + events.push(event); + await processor.process(event) + } + + expect(eventStore.size()).toEqual(500); + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(506); + expect(eventDispatcher.dispatchEvent.mock.calls[504][0]).toEqual(buildLogEvent([events[0]])); + expect(eventDispatcher.dispatchEvent.mock.calls[505][0]).toEqual(buildLogEvent([events[1]])); + }); + + it('should still dispatch events even if the store save fails', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue({}); + + const eventStore = getMockAsyncCache<EventWithId>(); + // Simulate failure in saving to store + eventStore.set = vi.fn().mockRejectedValue(new Error('Failed to save')); + + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + eventStore, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event) + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + + await dispatchRepeater.execute(0); + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + expect(eventDispatcher.dispatchEvent.mock.calls[0][0]).toEqual(buildLogEvent(events)); + }); + }); + + it('should dispatch events when dispatchRepeater is triggered', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue({}); + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + + let events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + await dispatchRepeater.execute(0); + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + expect(eventDispatcher.dispatchEvent.mock.calls[0][0]).toEqual(buildLogEvent(events)); + + events = []; + for(let i = 1; i < 15; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + await dispatchRepeater.execute(0); + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(2); + expect(eventDispatcher.dispatchEvent.mock.calls[1][0]).toEqual(buildLogEvent(events)); + }); + + it('should not retry failed dispatch if retryConfig is not provided', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockRejectedValue(new Error()); + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + await dispatchRepeater.execute(0); + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + }); + + it('should retry specified number of times using the provided backoffController', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockRejectedValue(new Error()); + const dispatchRepeater = getMockRepeater(); + + const backoffController = { + backoff: vi.fn().mockReturnValue(1000), + reset: vi.fn(), + }; + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + retryConfig: { + backoffProvider: () => backoffController, + maxRetries: 3, + }, + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + await dispatchRepeater.execute(0); + + for(let i = 0; i < 10; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(1000); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(4); + expect(backoffController.backoff).toHaveBeenCalledTimes(3); + + const request = buildLogEvent(events); + for(let i = 0; i < 4; i++) { + expect(eventDispatcher.dispatchEvent.mock.calls[i][0]).toEqual(request); + } + }); + + it('should remove the events from the eventStore after dispatch is successfull', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + const dispatchResponse = resolvablePromise(); + + mockDispatch.mockResolvedValue(dispatchResponse.promise); + + const eventStore = getMockSyncCache<EventWithId>(); + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + eventStore, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event) + } + + expect(eventStore.size()).toEqual(10); + await dispatchRepeater.execute(0); + + expect(mockDispatch).toHaveBeenCalledTimes(1); + // the dispatch is not resolved yet, so all the events should still be in the store + expect(eventStore.size()).toEqual(10); + + dispatchResponse.resolve({ statusCode: 200 }); + + await exhaustMicrotasks(); + + expect(eventStore.size()).toEqual(0); + }); + + it('should remove the events from the eventStore after dispatch is successfull', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + const dispatchResponse = resolvablePromise(); + + mockDispatch.mockResolvedValue(dispatchResponse.promise); + + const eventStore = getMockSyncCache<EventWithId>(); + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + eventStore, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event) + } + + expect(eventStore.size()).toEqual(10); + await dispatchRepeater.execute(0); + + expect(mockDispatch).toHaveBeenCalledTimes(1); + // the dispatch is not resolved yet, so all the events should still be in the store + expect(eventStore.size()).toEqual(10); + + dispatchResponse.resolve({ statusCode: 200 }); + + await exhaustMicrotasks(); + + expect(eventStore.size()).toEqual(0); + }); + + it('should remove the events from the eventStore after dispatch is successfull after retries', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + + mockDispatch.mockResolvedValueOnce({ statusCode: 500 }) + .mockResolvedValueOnce({ statusCode: 500 }) + .mockResolvedValueOnce({ statusCode: 200 }); + + const eventStore = getMockSyncCache<EventWithId>(); + const dispatchRepeater = getMockRepeater(); + + const backoffController = { + backoff: vi.fn().mockReturnValue(1000), + reset: vi.fn(), + }; + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + eventStore, + retryConfig: { + backoffProvider: () => backoffController, + maxRetries: 3, + }, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event) + } + + expect(eventStore.size()).toEqual(10); + await dispatchRepeater.execute(0); + + for(let i = 0; i < 10; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(1000); + } + + expect(mockDispatch).toHaveBeenCalledTimes(3); + expect(eventStore.size()).toEqual(0); + }); + + it('should log error and keep events in store if dispatch return 5xx response', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue({ statusCode: 500 }); + const dispatchRepeater = getMockRepeater(); + + const backoffController = { + backoff: vi.fn().mockReturnValue(1000), + reset: vi.fn(), + }; + + const eventStore = getMockSyncCache<EventWithId>(); + const logger = getMockLogger(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + eventStore, + retryConfig: { + backoffProvider: () => backoffController, + maxRetries: 3, + }, + batchSize: 100, + logger, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + expect(eventStore.size()).toEqual(10); + + await dispatchRepeater.execute(0); + + for(let i = 0; i < 10; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(1000); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(4); + expect(backoffController.backoff).toHaveBeenCalledTimes(3); + expect(eventStore.size()).toEqual(10); + expect(logger.error).toHaveBeenCalledOnce(); + }); + + it('should log error and keep events in store if dispatch promise fails', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockRejectedValue(new Error()); + const dispatchRepeater = getMockRepeater(); + + const backoffController = { + backoff: vi.fn().mockReturnValue(1000), + reset: vi.fn(), + }; + + const eventStore = getMockSyncCache<EventWithId>(); + const logger = getMockLogger(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + eventStore, + retryConfig: { + backoffProvider: () => backoffController, + maxRetries: 3, + }, + batchSize: 100, + logger, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + expect(eventStore.size()).toEqual(10); + + await dispatchRepeater.execute(0); + + for(let i = 0; i < 10; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(1000); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(4); + expect(backoffController.backoff).toHaveBeenCalledTimes(3); + expect(eventStore.size()).toEqual(10); + expect(logger.error).toHaveBeenCalledOnce(); + }); + + describe('retryFailedEvents', () => { + it('should dispatch only failed events from the store and not dispatch queued events', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue({}); + + const cache = getMockSyncCache<EventWithId>(); + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + eventStore: cache, + }); + + processor.start(); + await processor.onRunning(); + + // these events should be in queue and should not be reomoved from store or dispatched with failed events + const eventA = createImpressionEvent('id-A'); + const eventB = createImpressionEvent('id-B'); + await processor.process(eventA); + await processor.process(eventB); + + const failedEvents: ProcessableEvent[] = []; + + for(let i = 0; i < 5; i++) { + const id = `id-${i}`; + const event = createImpressionEvent(id); + failedEvents.push(event); + cache.set(id, { id, event }); + } + + await processor.retryFailedEvents(); + await exhaustMicrotasks(); + + expect(mockDispatch).toHaveBeenCalledTimes(1); + expect(mockDispatch.mock.calls[0][0]).toEqual(buildLogEvent(failedEvents)); + + const eventsInStore = [...cache.getAll().values()].sort((a, b) => a.id < b.id ? -1 : 1).map(e => e.event); + expect(eventsInStore).toEqual(expect.arrayContaining([ + expect.objectContaining(eventA), + expect.objectContaining(eventB), + ])); + }); + + it('should dispatch only failed events from the store and not dispatch events that are being dispatched', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + const mockResult1 = resolvablePromise(); + const mockResult2 = resolvablePromise(); + mockDispatch.mockResolvedValueOnce(mockResult1.promise).mockRejectedValueOnce(mockResult2.promise); + + const cache = getMockSyncCache<EventWithId>(); + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + eventStore: cache, + }); + + processor.start(); + await processor.onRunning(); + + // these events should be in dispatch and should not be reomoved from store or dispatched with failed events + const eventA = createImpressionEvent('id-A'); + const eventB = createImpressionEvent('id-B'); + await processor.process(eventA); + await processor.process(eventB); + + dispatchRepeater.execute(0); + await exhaustMicrotasks(); + expect(mockDispatch).toHaveBeenCalledTimes(1); + expect(mockDispatch.mock.calls[0][0]).toEqual(buildLogEvent([eventA, eventB])); + + const failedEvents: ProcessableEvent[] = []; + + for(let i = 0; i < 5; i++) { + const id = `id-${i}`; + const event = createImpressionEvent(id); + failedEvents.push(event); + cache.set(id, { id, event }); + } + + await processor.retryFailedEvents(); + await exhaustMicrotasks(); + + expect(mockDispatch).toHaveBeenCalledTimes(2); + expect(mockDispatch.mock.calls[1][0]).toEqual(buildLogEvent(failedEvents)); + + mockResult2.resolve({}); + await exhaustMicrotasks(); + + const eventsInStore = [...cache.getAll().values()].sort((a, b) => a.id < b.id ? -1 : 1).map(e => e.event); + expect(eventsInStore).toEqual(expect.arrayContaining([ + expect.objectContaining(eventA), + expect.objectContaining(eventB), + ])); + }); + + it('should dispatch events in correct batch size and separate events with differnt contexts in separate batch', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue({}); + + const cache = getMockSyncCache<EventWithId>(); + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 3, + eventStore: cache, + }); + + processor.start(); + await processor.onRunning(); + + const failedEvents: ProcessableEvent[] = []; + + for(let i = 0; i < 8; i++) { + const id = `id-${i}`; + const event = createImpressionEvent(id); + + if (i == 2 || i == 3) { + event.context.accountId = 'new-account'; + } + + failedEvents.push(event); + cache.set(id, { id, event }); + } + + await processor.retryFailedEvents(); + await exhaustMicrotasks(); + + // events 0 1 4 5 6 7 have one context, and 2 3 have different context + // batches should be [0, 1], [2, 3], [4, 5, 6], [7] + expect(mockDispatch).toHaveBeenCalledTimes(4); + expect(mockDispatch.mock.calls[0][0]).toEqual(buildLogEvent([failedEvents[0], failedEvents[1]])); + expect(mockDispatch.mock.calls[1][0]).toEqual(buildLogEvent([failedEvents[2], failedEvents[3]])); + expect(mockDispatch.mock.calls[2][0]).toEqual(buildLogEvent([failedEvents[4], failedEvents[5], failedEvents[6]])); + expect(mockDispatch.mock.calls[3][0]).toEqual(buildLogEvent([failedEvents[7]])); + }); + }); + + describe('when failedEventRepeater is fired', () => { + it('should dispatch only failed events from the store and not dispatch queued events', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue({}); + + const cache = getMockSyncCache<EventWithId>(); + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 100, + eventStore: cache, + }); + + processor.start(); + await processor.onRunning(); + + // these events should be in queue and should not be reomoved from store or dispatched with failed events + const eventA = createImpressionEvent('id-A'); + const eventB = createImpressionEvent('id-B'); + await processor.process(eventA); + await processor.process(eventB); + + const failedEvents: ProcessableEvent[] = []; + + for(let i = 0; i < 5; i++) { + const id = `id-${i}`; + const event = createImpressionEvent(id); + failedEvents.push(event); + cache.set(id, { id, event }); + } + + failedEventRepeater.execute(0); + await exhaustMicrotasks(); + + expect(mockDispatch).toHaveBeenCalledTimes(1); + expect(mockDispatch.mock.calls[0][0]).toEqual(buildLogEvent(failedEvents)); + + const eventsInStore = [...cache.getAll().values()].sort((a, b) => a.id < b.id ? -1 : 1).map(e => e.event); + expect(eventsInStore).toEqual(expect.arrayContaining([ + expect.objectContaining(eventA), + expect.objectContaining(eventB), + ])); + }); + + it('should dispatch only failed events from the store and not dispatch events that are being dispatched', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + const mockResult1 = resolvablePromise(); + const mockResult2 = resolvablePromise(); + mockDispatch.mockResolvedValueOnce(mockResult1.promise).mockRejectedValueOnce(mockResult2.promise); + + const cache = getMockSyncCache<EventWithId>(); + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 100, + eventStore: cache, + }); + + processor.start(); + await processor.onRunning(); + + // these events should be in dispatch and should not be reomoved from store or dispatched with failed events + const eventA = createImpressionEvent('id-A'); + const eventB = createImpressionEvent('id-B'); + await processor.process(eventA); + await processor.process(eventB); + + dispatchRepeater.execute(0); + await exhaustMicrotasks(); + expect(mockDispatch).toHaveBeenCalledTimes(1); + expect(mockDispatch.mock.calls[0][0]).toEqual(buildLogEvent([eventA, eventB])); + + const failedEvents: ProcessableEvent[] = []; + + for(let i = 0; i < 5; i++) { + const id = `id-${i}`; + const event = createImpressionEvent(id); + failedEvents.push(event); + cache.set(id, { id, event }); + } + + failedEventRepeater.execute(0); + await exhaustMicrotasks(); + + expect(mockDispatch).toHaveBeenCalledTimes(2); + expect(mockDispatch.mock.calls[1][0]).toEqual(buildLogEvent(failedEvents)); + + mockResult2.resolve({}); + await exhaustMicrotasks(); + + const eventsInStore = [...cache.getAll().values()].sort((a, b) => a.id < b.id ? -1 : 1).map(e => e.event); + expect(eventsInStore).toEqual(expect.arrayContaining([ + expect.objectContaining(eventA), + expect.objectContaining(eventB), + ])); + }); + + it('should dispatch events in correct batch size and separate events with differnt contexts in separate batch', async () => { + const eventDispatcher = getMockDispatcher(); + const mockDispatch: MockInstance<typeof eventDispatcher.dispatchEvent> = eventDispatcher.dispatchEvent; + mockDispatch.mockResolvedValue({}); + + const cache = getMockSyncCache<EventWithId>(); + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 3, + eventStore: cache, + }); + + processor.start(); + await processor.onRunning(); + + const failedEvents: ProcessableEvent[] = []; + + for(let i = 0; i < 8; i++) { + const id = `id-${i}`; + const event = createImpressionEvent(id); + + if (i == 2 || i == 3) { + event.context.accountId = 'new-account'; + } + + failedEvents.push(event); + cache.set(id, { id, event }); + } + + failedEventRepeater.execute(0); + await exhaustMicrotasks(); + + // events 0 1 4 5 6 7 have one context, and 2 3 have different context + // batches should be [0, 1], [2, 3], [4, 5, 6], [7] + expect(mockDispatch).toHaveBeenCalledTimes(4); + expect(mockDispatch.mock.calls[0][0]).toEqual(buildLogEvent([failedEvents[0], failedEvents[1]])); + expect(mockDispatch.mock.calls[1][0]).toEqual(buildLogEvent([failedEvents[2], failedEvents[3]])); + expect(mockDispatch.mock.calls[2][0]).toEqual(buildLogEvent([failedEvents[4], failedEvents[5], failedEvents[6]])); + expect(mockDispatch.mock.calls[3][0]).toEqual(buildLogEvent([failedEvents[7]])); + }); + }); + + it('should emit dispatch event when dispatching events', async () => { + const eventDispatcher = getMockDispatcher(); + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + }); + + const event = createImpressionEvent('id-1'); + const event2 = createImpressionEvent('id-2'); + + const dispatchListener = vi.fn(); + processor.onDispatch(dispatchListener); + + processor.start(); + await processor.onRunning(); + + await processor.process(event); + await processor.process(event2); + + await dispatchRepeater.execute(0); + + expect(dispatchListener).toHaveBeenCalledTimes(1); + expect(dispatchListener.mock.calls[0][0]).toEqual(buildLogEvent([event, event2])); + }); + + it('should remove event handler when function returned from onDispatch is called', async () => { + const eventDispatcher = getMockDispatcher(); + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + }); + + const dispatchListener = vi.fn(); + + const unsub = processor.onDispatch(dispatchListener); + + processor.start(); + await processor.onRunning(); + + const event = createImpressionEvent('id-1'); + const event2 = createImpressionEvent('id-2'); + + await processor.process(event); + await processor.process(event2); + + await dispatchRepeater.execute(0); + + expect(dispatchListener).toHaveBeenCalledTimes(1); + expect(dispatchListener.mock.calls[0][0]).toEqual(buildLogEvent([event, event2])); + + unsub(); + + const event3 = createImpressionEvent('id-3'); + const event4 = createImpressionEvent('id-4'); + + await dispatchRepeater.execute(0); + expect(dispatchListener).toHaveBeenCalledTimes(1); + }); + + describe('stop', () => { + it('should reject onRunning if stop is called before the processor is started', async () => { + const eventDispatcher = getMockDispatcher(); + const dispatchRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + }); + + processor.stop(); + + await expect(processor.onRunning()).rejects.toThrow(); + }); + + it('should stop dispatchRepeater and failedEventRepeater', async () => { + const eventDispatcher = getMockDispatcher(); + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + + processor.stop(); + expect(dispatchRepeater.stop).toHaveBeenCalledOnce(); + expect(failedEventRepeater.stop).toHaveBeenCalledOnce(); + }); + + it('should dispatch the events in queue using the closing dispatcher if available', async () => { + const eventDispatcher = getMockDispatcher(); + const closingEventDispatcher = getMockDispatcher(); + closingEventDispatcher.dispatchEvent.mockResolvedValue({}); + + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + closingEventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + expect(closingEventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + + processor.stop(); + expect(closingEventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + expect(closingEventDispatcher.dispatchEvent).toHaveBeenCalledWith(buildLogEvent(events)); + }); + + it('should cancel retry of active dispatches', async () => { + const runWithRetrySpy = vi.spyOn(retry, 'runWithRetry'); + const cancel1 = vi.fn(); + const cancel2 = vi.fn(); + runWithRetrySpy.mockReturnValueOnce({ + cancelRetry: cancel1, + result: resolvablePromise().promise, + }).mockReturnValueOnce({ + cancelRetry: cancel2, + result: resolvablePromise().promise, + }); + + const eventDispatcher = getMockDispatcher(); + const dispatchRepeater = getMockRepeater(); + + const backoffController = { + backoff: vi.fn().mockReturnValue(1000), + reset: vi.fn(), + }; + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + retryConfig: { + backoffProvider: () => backoffController, + maxRetries: 3, + } + }); + + processor.start(); + await processor.onRunning(); + + await processor.process(createImpressionEvent('id-1')); + await dispatchRepeater.execute(0); + + expect(runWithRetrySpy).toHaveBeenCalledTimes(1); + + await processor.process(createImpressionEvent('id-2')); + await dispatchRepeater.execute(0); + + expect(runWithRetrySpy).toHaveBeenCalledTimes(2); + + processor.stop(); + + expect(cancel1).toHaveBeenCalledOnce(); + expect(cancel2).toHaveBeenCalledOnce(); + + runWithRetrySpy.mockReset(); + }); + + it('should resolve onTerminated when all active dispatch requests settles' , async () => { + const eventDispatcher = getMockDispatcher(); + const dispatchRes1 = resolvablePromise<void>(); + const dispatchRes2 = resolvablePromise<void>(); + eventDispatcher.dispatchEvent.mockReturnValueOnce(dispatchRes1.promise) + .mockReturnValueOnce(dispatchRes2.promise); + + const dispatchRepeater = getMockRepeater(); + + const backoffController = { + backoff: vi.fn().mockReturnValue(1000), + reset: vi.fn(), + }; + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + batchSize: 100, + }); + + processor.start() + await processor.onRunning(); + + await processor.process(createImpressionEvent('id-1')); + await dispatchRepeater.execute(0); + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + + await processor.process(createImpressionEvent('id-2')); + await dispatchRepeater.execute(0); + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(2); + + const onStop = vi.fn(); + processor.onTerminated().then(onStop); + + processor.stop(); + + await exhaustMicrotasks(); + expect(onStop).not.toHaveBeenCalled(); + expect(processor.getState()).toEqual(ServiceState.Stopping); + + dispatchRes1.resolve(); + dispatchRes2.reject(new Error()); + + await expect(processor.onTerminated()).resolves.not.toThrow(); + }); + }); + + describe('flushImmediately', () => { + it('should dispatch the events in queue using the closing dispatcher if available', async () => { + const eventDispatcher = getMockDispatcher(); + const closingEventDispatcher = getMockDispatcher(); + closingEventDispatcher.dispatchEvent.mockResolvedValue({}); + + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + closingEventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + expect(closingEventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + + processor.flushImmediately(); + expect(closingEventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + expect(closingEventDispatcher.dispatchEvent).toHaveBeenCalledWith(buildLogEvent(events)); + + expect(processor.isRunning()).toBe(true); + }); + + + it('should dispatch the events in queue using eventDispatcher if closingEventDispatcher is not available', async () => { + const eventDispatcher = getMockDispatcher(); + eventDispatcher.dispatchEvent.mockResolvedValue({}); + + const dispatchRepeater = getMockRepeater(); + const failedEventRepeater = getMockRepeater(); + + const processor = new BatchEventProcessor({ + eventDispatcher, + dispatchRepeater, + failedEventRepeater, + batchSize: 100, + }); + + processor.start(); + await processor.onRunning(); + + const events: ProcessableEvent[] = []; + for(let i = 0; i < 10; i++) { + const event = createImpressionEvent(`id-${i}`); + events.push(event); + await processor.process(event); + } + + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(0); + + processor.flushImmediately(); + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledTimes(1); + expect(eventDispatcher.dispatchEvent).toHaveBeenCalledWith(buildLogEvent(events)); + + expect(processor.isRunning()).toBe(true); + }); + }); +}); diff --git a/lib/event_processor/batch_event_processor.ts b/lib/event_processor/batch_event_processor.ts new file mode 100644 index 000000000..86f7ff148 --- /dev/null +++ b/lib/event_processor/batch_event_processor.ts @@ -0,0 +1,365 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { EventProcessor, ProcessableEvent } from "./event_processor"; +import { getBatchedAsync, getBatchedSync, Store } from "../utils/cache/store"; +import { EventDispatcher, EventDispatcherResponse, LogEvent } from "./event_dispatcher/event_dispatcher"; +import { buildLogEvent } from "./event_builder/log_event"; +import { BackoffController, ExponentialBackoff, Repeater } from "../utils/repeater/repeater"; +import { LoggerFacade } from '../logging/logger'; +import { BaseService, ServiceState, StartupLog } from "../service"; +import { Consumer, Fn, Maybe, Producer } from "../utils/type"; +import { RunResult, runWithRetry } from "../utils/executor/backoff_retry_runner"; +import { isSuccessStatusCode } from "../utils/http_request_handler/http_util"; +import { EventEmitter } from "../utils/event_emitter/event_emitter"; +import { IdGenerator } from "../utils/id_generator"; +import { areEventContextsEqual } from "./event_builder/user_event"; +import { FAILED_TO_DISPATCH_EVENTS, SERVICE_NOT_RUNNING } from "error_message"; +import { OptimizelyError } from "../error/optimizly_error"; +import { sprintf } from "../utils/fns"; +import { SERVICE_STOPPED_BEFORE_RUNNING } from "../service"; +import { EVENT_STORE_FULL } from "../message/log_message"; + +export const DEFAULT_MIN_BACKOFF = 1000; +export const DEFAULT_MAX_BACKOFF = 32000; +export const MAX_EVENTS_IN_STORE = 500; + +export type EventWithId = { + id: string; + event: ProcessableEvent; + notStored?: boolean; +}; + +export type RetryConfig = { + maxRetries: number; + backoffProvider: Producer<BackoffController>; +} + +export type BatchEventProcessorConfig = { + dispatchRepeater: Repeater, + failedEventRepeater?: Repeater, + batchSize: number, + eventStore?: Store<EventWithId>, + eventDispatcher: EventDispatcher, + closingEventDispatcher?: EventDispatcher, + logger?: LoggerFacade, + retryConfig?: RetryConfig; + startupLogs?: StartupLog[]; +}; + +type EventBatch = { + request: LogEvent, + events: EventWithId[], +} + +export const LOGGER_NAME = 'BatchEventProcessor'; + +export class BatchEventProcessor extends BaseService implements EventProcessor { + private eventDispatcher: EventDispatcher; + private closingEventDispatcher?: EventDispatcher; + private eventQueue: EventWithId[] = []; + private batchSize: number; + private eventStore?: Store<EventWithId>; + private eventCountInStore: Maybe<number> = undefined; + private eventCountWaitPromise: Promise<unknown> = Promise.resolve(); + private maxEventsInStore: number = MAX_EVENTS_IN_STORE; + private dispatchRepeater: Repeater; + private failedEventRepeater?: Repeater; + private idGenerator: IdGenerator = new IdGenerator(); + private runningTask: Map<string, RunResult<EventDispatcherResponse>> = new Map(); + private dispatchingEvents: Map<string, EventWithId> = new Map(); + private eventEmitter: EventEmitter<{ dispatch: LogEvent }> = new EventEmitter(); + private retryConfig?: RetryConfig; + + constructor(config: BatchEventProcessorConfig) { + super(config.startupLogs); + this.eventDispatcher = config.eventDispatcher; + this.closingEventDispatcher = config.closingEventDispatcher; + this.batchSize = config.batchSize; + this.eventStore = config.eventStore; + + this.retryConfig = config.retryConfig; + + this.dispatchRepeater = config.dispatchRepeater; + this.dispatchRepeater.setTask(() => this.flush()); + + this.maxEventsInStore = Math.max(2 * config.batchSize, MAX_EVENTS_IN_STORE); + this.failedEventRepeater = config.failedEventRepeater; + this.failedEventRepeater?.setTask(() => this.retryFailedEvents()); + if (config.logger) { + this.setLogger(config.logger); + } + } + + setLogger(logger: LoggerFacade): void { + this.logger = logger; + this.logger.setName(LOGGER_NAME); + } + + onDispatch(handler: Consumer<LogEvent>): Fn { + return this.eventEmitter.on('dispatch', handler); + } + + public async retryFailedEvents(): Promise<void> { + if (!this.eventStore) { + return; + } + + const keys = (await this.eventStore.getKeys()).filter( + (k) => !this.dispatchingEvents.has(k) && !this.eventQueue.find((e) => e.id === k) + ); + + const events = await (this.eventStore.operation === 'sync' ? + getBatchedSync(this.eventStore, keys) : getBatchedAsync(this.eventStore, keys)); + + const failedEvents: EventWithId[] = []; + events.forEach((e) => { + if(e) { + failedEvents.push(e); + } + }); + + if (failedEvents.length == 0) { + return; + } + + failedEvents.sort((a, b) => a.id < b.id ? -1 : 1); + + const batches: EventBatch[] = []; + let currentBatch: EventWithId[] = []; + + failedEvents.forEach((event) => { + if (currentBatch.length === this.batchSize || + (currentBatch.length > 0 && !areEventContextsEqual(currentBatch[0].event, event.event))) { + batches.push({ + request: buildLogEvent(currentBatch.map((e) => e.event)), + events: currentBatch, + }); + currentBatch = []; + } + currentBatch.push(event); + }); + + if (currentBatch.length > 0) { + batches.push({ + request: buildLogEvent(currentBatch.map((e) => e.event)), + events: currentBatch, + }); + } + + batches.forEach((batch) => { + this.dispatchBatch(batch, false); + }); + } + + private createNewBatch(): EventBatch | undefined { + if (this.eventQueue.length == 0) { + return + } + + const events: ProcessableEvent[] = []; + const eventWithIds: EventWithId[] = []; + + this.eventQueue.forEach((event) => { + events.push(event.event); + eventWithIds.push(event); + }); + + this.eventQueue = []; + return { request: buildLogEvent(events), events: eventWithIds }; + } + + private async executeDispatch(request: LogEvent, closing = false): Promise<EventDispatcherResponse> { + const dispatcher = closing && this.closingEventDispatcher ? this.closingEventDispatcher : this.eventDispatcher; + return dispatcher.dispatchEvent(request).then((res) => { + if (res.statusCode && !isSuccessStatusCode(res.statusCode)) { + return Promise.reject(new OptimizelyError(FAILED_TO_DISPATCH_EVENTS, res.statusCode)); + } + return Promise.resolve(res); + }); + } + + private dispatchBatch(batch: EventBatch, closing: boolean): void { + const { request, events } = batch; + + events.forEach((event) => { + this.dispatchingEvents.set(event.id, event); + }); + + const runResult: RunResult<EventDispatcherResponse> = this.retryConfig + ? runWithRetry( + () => this.executeDispatch(request, closing), this.retryConfig.backoffProvider(), this.retryConfig.maxRetries + ) : { + result: this.executeDispatch(request, closing), + cancelRetry: () => {}, + }; + + this.eventEmitter.emit('dispatch', request); + + const taskId = this.idGenerator.getId(); + this.runningTask.set(taskId, runResult); + + runResult.result.then((res) => { + events.forEach((event) => { + this.eventStore?.remove(event.id); + if (!event.notStored && this.eventCountInStore) { + this.eventCountInStore--; + } + }); + return Promise.resolve(); + }).catch((err) => { + // if the dispatch fails, the events will still be + // in the store for future processing + this.logger?.error(err); + }).finally(() => { + this.runningTask.delete(taskId); + events.forEach((event) => this.dispatchingEvents.delete(event.id)); + }); + } + + private async flush(useClosingDispatcher = false): Promise<void> { + const batch = this.createNewBatch(); + if (!batch) { + return; + } + + this.dispatchRepeater.reset(); + this.dispatchBatch(batch, useClosingDispatcher); + } + + async process(event: ProcessableEvent): Promise<void> { + if (!this.isRunning()) { + return Promise.reject(new OptimizelyError(SERVICE_NOT_RUNNING, 'BatchEventProcessor')); + } + + const eventWithId: EventWithId = { + id: this.idGenerator.getId(), + event: event, + }; + + await this.storeEvent(eventWithId); + + if (this.eventQueue.length > 0 && !areEventContextsEqual(this.eventQueue[0].event, event)) { + this.flush(); + } + + this.eventQueue.push(eventWithId); + + if (this.eventQueue.length == this.batchSize) { + this.flush(); + } else if (!this.dispatchRepeater.isRunning()) { + this.dispatchRepeater.start(); + } + } + + private async readEventCountInStore(store: Store<EventWithId>): Promise<void> { + if (this.eventCountInStore !== undefined) { + return; + } + + try { + const keys = await store.getKeys(); + this.eventCountInStore = keys.length; + } catch (e) { + this.logger?.error(e); + } + } + + private async findEventCountInStore(): Promise<unknown> { + if (this.eventStore && this.eventCountInStore === undefined) { + const store = this.eventStore; + this.eventCountWaitPromise = this.eventCountWaitPromise.then(() => this.readEventCountInStore(store)); + return this.eventCountWaitPromise; + } + return Promise.resolve(); + } + + private async storeEvent(eventWithId: EventWithId): Promise<void> { + await this.findEventCountInStore(); + if (this.eventCountInStore !== undefined && this.eventCountInStore >= this.maxEventsInStore) { + this.logger?.info(EVENT_STORE_FULL, eventWithId.event.uuid); + eventWithId.notStored = true; + return; + } + + await Promise.resolve(this.eventStore?.set(eventWithId.id, eventWithId)).then(() => { + if (this.eventCountInStore !== undefined) { + this.eventCountInStore++; + } + }).catch((e) => { + eventWithId.notStored = true; + this.logger?.error(e); + }); + } + + start(): void { + if (!this.isNew()) { + return; + } + + super.start(); + this.state = ServiceState.Running; + + if(!this.disposable) { + this.failedEventRepeater?.start(); + } + + this.retryFailedEvents(); + this.startPromise.resolve(); + } + + makeDisposable(): void { + super.makeDisposable(); + this.batchSize = 1; + this.retryConfig = { + maxRetries: Math.min(this.retryConfig?.maxRetries ?? 5, 5), + backoffProvider: + this.retryConfig?.backoffProvider || + (() => new ExponentialBackoff(DEFAULT_MIN_BACKOFF, DEFAULT_MAX_BACKOFF, 500)), + } + } + + flushImmediately(): Promise<unknown> { + if (!this.isRunning()) { + return Promise.resolve(); + } + return this.flush(true); + } + + stop(): void { + if (this.isDone()) { + return; + } + + if (this.isNew()) { + this.startPromise.reject(new Error( + sprintf(SERVICE_STOPPED_BEFORE_RUNNING, 'BatchEventProcessor') + )); + } + + this.state = ServiceState.Stopping; + this.dispatchRepeater.stop(); + this.failedEventRepeater?.stop(); + + this.flush(true); + this.runningTask.forEach((task) => task.cancelRetry()); + + Promise.allSettled(Array.from(this.runningTask.values()).map((task) => task.result)).then(() => { + this.state = ServiceState.Terminated; + this.stopPromise.resolve(); + }); + } +} diff --git a/lib/event_processor/event_builder/log_event.spec.ts b/lib/event_processor/event_builder/log_event.spec.ts new file mode 100644 index 000000000..ad3b22b94 --- /dev/null +++ b/lib/event_processor/event_builder/log_event.spec.ts @@ -0,0 +1,870 @@ +/** + * Copyright 2022, 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect } from 'vitest'; + +import { + makeEventBatch, + buildLogEvent, +} from './log_event'; + +import { ImpressionEvent, ConversionEvent, UserEvent } from './user_event'; +import { Region } from '../../project_config/project_config'; + + +describe('makeEventBatch', () => { + it('should build a batch with single impression event when experiment and variation are defined', () => { + const impressionEvent: ImpressionEvent = { + type: 'impression', + timestamp: 69, + uuid: 'uuid', + + context: { + accountId: 'accountId', + projectId: 'projectId', + clientName: 'node-sdk', + clientVersion: '3.0.0', + revision: 'revision', + botFiltering: true, + anonymizeIP: true, + }, + + user: { + id: 'userId', + attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], + }, + + layer: { + id: 'layerId', + }, + + experiment: { + id: 'expId', + key: 'expKey', + }, + + variation: { + id: 'varId', + key: 'varKey', + }, + + ruleKey: 'expKey', + flagKey: 'flagKey1', + ruleType: 'experiment', + enabled: true, + } + + const result = makeEventBatch([impressionEvent]) + expect(result).toEqual({ + client_name: 'node-sdk', + client_version: '3.0.0', + account_id: 'accountId', + project_id: 'projectId', + revision: 'revision', + anonymize_ip: true, + enrich_decisions: true, + + visitors: [ + { + snapshots: [ + { + decisions: [ + { + campaign_id: 'layerId', + experiment_id: 'expId', + variation_id: 'varId', + metadata: { + flag_key: 'flagKey1', + rule_key: 'expKey', + rule_type: 'experiment', + variation_key: 'varKey', + enabled: true, + }, + }, + ], + events: [ + { + entity_id: 'layerId', + timestamp: 69, + key: 'campaign_activated', + uuid: 'uuid', + }, + ], + }, + ], + visitor_id: 'userId', + attributes: [ + { + entity_id: 'attr1-id', + key: 'attr1-key', + type: 'custom', + value: 'attr1-value', + }, + { + entity_id: '$opt_bot_filtering', + key: '$opt_bot_filtering', + type: 'custom', + value: true, + }, + ], + }, + ], + }) + }) + + it('should build a batch with simlge impression event when experiment and variation are not defined', () => { + const impressionEvent: ImpressionEvent = { + type: 'impression', + timestamp: 69, + uuid: 'uuid', + + context: { + accountId: 'accountId', + projectId: 'projectId', + clientName: 'node-sdk', + clientVersion: '3.0.0', + revision: 'revision', + botFiltering: true, + anonymizeIP: true, + }, + + user: { + id: 'userId', + attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], + }, + + layer: { + id: null, + }, + + experiment: { + id: null, + key: '', + }, + + variation: { + id: null, + key: '', + }, + + ruleKey: '', + flagKey: 'flagKey1', + ruleType: 'rollout', + enabled: true, + } + + const result = makeEventBatch([impressionEvent]) + expect(result).toEqual({ + client_name: 'node-sdk', + client_version: '3.0.0', + account_id: 'accountId', + project_id: 'projectId', + revision: 'revision', + anonymize_ip: true, + enrich_decisions: true, + + visitors: [ + { + snapshots: [ + { + decisions: [ + { + campaign_id: null, + experiment_id: "", + variation_id: "", + metadata: { + flag_key: 'flagKey1', + rule_key: '', + rule_type: 'rollout', + variation_key: '', + enabled: true, + }, + }, + ], + events: [ + { + entity_id: null, + timestamp: 69, + key: 'campaign_activated', + uuid: 'uuid', + }, + ], + }, + ], + visitor_id: 'userId', + attributes: [ + { + entity_id: 'attr1-id', + key: 'attr1-key', + type: 'custom', + value: 'attr1-value', + }, + { + entity_id: '$opt_bot_filtering', + key: '$opt_bot_filtering', + type: 'custom', + value: true, + }, + ], + }, + ], + }) + }); + + it('should build a batch with single conversion event when tags object is defined', () => { + const conversionEvent: ConversionEvent = { + type: 'conversion', + timestamp: 69, + uuid: 'uuid', + + context: { + accountId: 'accountId', + projectId: 'projectId', + clientName: 'node-sdk', + clientVersion: '3.0.0', + revision: 'revision', + botFiltering: true, + anonymizeIP: true, + }, + + user: { + id: 'userId', + attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], + }, + + event: { + id: 'event-id', + key: 'event-key', + }, + + tags: { + foo: 'bar', + value: '123', + revenue: '1000', + }, + + revenue: 1000, + value: 123, + } + + const result = makeEventBatch([conversionEvent]) + expect(result).toEqual({ + client_name: 'node-sdk', + client_version: '3.0.0', + account_id: 'accountId', + project_id: 'projectId', + revision: 'revision', + anonymize_ip: true, + enrich_decisions: true, + + visitors: [ + { + snapshots: [ + { + events: [ + { + entity_id: 'event-id', + timestamp: 69, + key: 'event-key', + uuid: 'uuid', + tags: { + foo: 'bar', + value: '123', + revenue: '1000', + }, + revenue: 1000, + value: 123, + }, + ], + }, + ], + visitor_id: 'userId', + attributes: [ + { + entity_id: 'attr1-id', + key: 'attr1-key', + type: 'custom', + value: 'attr1-value', + }, + { + entity_id: '$opt_bot_filtering', + key: '$opt_bot_filtering', + type: 'custom', + value: true, + }, + ], + }, + ], + }) + }) + + it('should build a batch with single conversion event when when tags object is undefined', () => { + const conversionEvent: ConversionEvent = { + type: 'conversion', + timestamp: 69, + uuid: 'uuid', + + context: { + accountId: 'accountId', + projectId: 'projectId', + clientName: 'node-sdk', + clientVersion: '3.0.0', + revision: 'revision', + botFiltering: true, + anonymizeIP: true, + }, + + user: { + id: 'userId', + attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], + }, + + event: { + id: 'event-id', + key: 'event-key', + }, + + tags: undefined, + + revenue: 1000, + value: 123, + } + + const result = makeEventBatch([conversionEvent]) + expect(result).toEqual({ + client_name: 'node-sdk', + client_version: '3.0.0', + account_id: 'accountId', + project_id: 'projectId', + revision: 'revision', + anonymize_ip: true, + enrich_decisions: true, + + visitors: [ + { + snapshots: [ + { + events: [ + { + entity_id: 'event-id', + timestamp: 69, + key: 'event-key', + uuid: 'uuid', + tags: undefined, + revenue: 1000, + value: 123, + }, + ], + }, + ], + visitor_id: 'userId', + attributes: [ + { + entity_id: 'attr1-id', + key: 'attr1-key', + type: 'custom', + value: 'attr1-value', + }, + { + entity_id: '$opt_bot_filtering', + key: '$opt_bot_filtering', + type: 'custom', + value: true, + }, + ], + }, + ], + }) + }) + + it('should build a batch with single conversion event when event id is null', () => { + const conversionEvent: ConversionEvent = { + type: 'conversion', + timestamp: 69, + uuid: 'uuid', + + context: { + accountId: 'accountId', + projectId: 'projectId', + clientName: 'node-sdk', + clientVersion: '3.0.0', + revision: 'revision', + botFiltering: true, + anonymizeIP: true, + }, + + user: { + id: 'userId', + attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], + }, + + event: { + id: null, + key: 'event-key', + }, + + tags: undefined, + + revenue: 1000, + value: 123, + } + + const result = makeEventBatch([conversionEvent]) + expect(result).toEqual({ + client_name: 'node-sdk', + client_version: '3.0.0', + account_id: 'accountId', + project_id: 'projectId', + revision: 'revision', + anonymize_ip: true, + enrich_decisions: true, + + visitors: [ + { + snapshots: [ + { + events: [ + { + entity_id: null, + timestamp: 69, + key: 'event-key', + uuid: 'uuid', + tags: undefined, + revenue: 1000, + value: 123, + }, + ], + }, + ], + visitor_id: 'userId', + attributes: [ + { + entity_id: 'attr1-id', + key: 'attr1-key', + type: 'custom', + value: 'attr1-value', + }, + { + entity_id: '$opt_bot_filtering', + key: '$opt_bot_filtering', + type: 'custom', + value: true, + }, + ], + }, + ], + }) + }) + + it('should include revenue and value for conversion events if they are 0', () => { + const conversionEvent: ConversionEvent = { + type: 'conversion', + timestamp: 69, + uuid: 'uuid', + + context: { + accountId: 'accountId', + projectId: 'projectId', + clientName: 'node-sdk', + clientVersion: '3.0.0', + revision: 'revision', + botFiltering: true, + anonymizeIP: true, + }, + + user: { + id: 'userId', + attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], + }, + + event: { + id: 'event-id', + key: 'event-key', + }, + + tags: { + foo: 'bar', + value: 0, + revenue: 0, + }, + + revenue: 0, + value: 0, + } + + const result = makeEventBatch([conversionEvent]) + expect(result).toEqual({ + client_name: 'node-sdk', + client_version: '3.0.0', + account_id: 'accountId', + project_id: 'projectId', + revision: 'revision', + anonymize_ip: true, + enrich_decisions: true, + + visitors: [ + { + snapshots: [ + { + events: [ + { + entity_id: 'event-id', + timestamp: 69, + key: 'event-key', + uuid: 'uuid', + tags: { + foo: 'bar', + value: 0, + revenue: 0, + }, + revenue: 0, + value: 0, + }, + ], + }, + ], + visitor_id: 'userId', + attributes: [ + { + entity_id: 'attr1-id', + key: 'attr1-key', + type: 'custom', + value: 'attr1-value', + }, + { + entity_id: '$opt_bot_filtering', + key: '$opt_bot_filtering', + type: 'custom', + value: true, + }, + ], + }, + ], + }) + }) + + it('should not include $opt_bot_filtering attribute if context.botFiltering is undefined', () => { + const conversionEvent: ConversionEvent = { + type: 'conversion', + timestamp: 69, + uuid: 'uuid', + + context: { + accountId: 'accountId', + projectId: 'projectId', + clientName: 'node-sdk', + clientVersion: '3.0.0', + revision: 'revision', + anonymizeIP: true, + }, + + user: { + id: 'userId', + attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], + }, + + event: { + id: 'event-id', + key: 'event-key', + }, + + tags: { + foo: 'bar', + value: '123', + revenue: '1000', + }, + + revenue: 1000, + value: 123, + } + + const result = makeEventBatch([conversionEvent]) + expect(result).toEqual({ + client_name: 'node-sdk', + client_version: '3.0.0', + account_id: 'accountId', + project_id: 'projectId', + revision: 'revision', + anonymize_ip: true, + enrich_decisions: true, + + visitors: [ + { + snapshots: [ + { + events: [ + { + entity_id: 'event-id', + timestamp: 69, + key: 'event-key', + uuid: 'uuid', + tags: { + foo: 'bar', + value: '123', + revenue: '1000', + }, + revenue: 1000, + value: 123, + }, + ], + }, + ], + visitor_id: 'userId', + attributes: [ + { + entity_id: 'attr1-id', + key: 'attr1-key', + type: 'custom', + value: 'attr1-value', + }, + ], + }, + ], + }) + }) + + it('should batch Conversion and Impression events together', () => { + const conversionEvent: ConversionEvent = { + type: 'conversion', + timestamp: 69, + uuid: 'uuid', + + context: { + accountId: 'accountId', + projectId: 'projectId', + clientName: 'node-sdk', + clientVersion: '3.0.0', + revision: 'revision', + botFiltering: true, + anonymizeIP: true, + }, + + user: { + id: 'userId', + attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], + }, + + event: { + id: 'event-id', + key: 'event-key', + }, + + tags: { + foo: 'bar', + value: '123', + revenue: '1000', + }, + + revenue: 1000, + value: 123, + } + + const impressionEvent: ImpressionEvent = { + type: 'impression', + timestamp: 69, + uuid: 'uuid', + + context: { + accountId: 'accountId', + projectId: 'projectId', + clientName: 'node-sdk', + clientVersion: '3.0.0', + revision: 'revision', + botFiltering: true, + anonymizeIP: true, + }, + + user: { + id: 'userId', + attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], + }, + + layer: { + id: 'layerId', + }, + + experiment: { + id: 'expId', + key: 'expKey', + }, + + variation: { + id: 'varId', + key: 'varKey', + }, + + ruleKey: 'expKey', + flagKey: 'flagKey1', + ruleType: 'experiment', + enabled: true, + } + + const result = makeEventBatch([impressionEvent, conversionEvent]) + + expect(result).toEqual({ + client_name: 'node-sdk', + client_version: '3.0.0', + account_id: 'accountId', + project_id: 'projectId', + revision: 'revision', + anonymize_ip: true, + enrich_decisions: true, + + visitors: [ + { + snapshots: [ + { + decisions: [ + { + campaign_id: 'layerId', + experiment_id: 'expId', + variation_id: 'varId', + metadata: { + flag_key: 'flagKey1', + rule_key: 'expKey', + rule_type: 'experiment', + variation_key: 'varKey', + enabled: true, + }, + }, + ], + events: [ + { + entity_id: 'layerId', + timestamp: 69, + key: 'campaign_activated', + uuid: 'uuid', + }, + ], + }, + ], + visitor_id: 'userId', + attributes: [ + { + entity_id: 'attr1-id', + key: 'attr1-key', + type: 'custom', + value: 'attr1-value', + }, + { + entity_id: '$opt_bot_filtering', + key: '$opt_bot_filtering', + type: 'custom', + value: true, + }, + ], + }, + { + snapshots: [ + { + events: [ + { + entity_id: 'event-id', + timestamp: 69, + key: 'event-key', + uuid: 'uuid', + tags: { + foo: 'bar', + value: '123', + revenue: '1000', + }, + revenue: 1000, + value: 123, + }, + ], + }, + ], + visitor_id: 'userId', + attributes: [ + { + entity_id: 'attr1-id', + key: 'attr1-key', + type: 'custom', + value: 'attr1-value', + }, + { + entity_id: '$opt_bot_filtering', + key: '$opt_bot_filtering', + type: 'custom', + value: true, + }, + ], + }, + ], + }) + }) +}) + +describe('buildLogEvent', () => { + it('should select the correct URL based on the event context region', () => { + const baseEvent: ImpressionEvent = { + type: 'impression', + timestamp: 69, + uuid: 'uuid', + context: { + accountId: 'accountId', + projectId: 'projectId', + clientName: 'node-sdk', + clientVersion: '3.0.0', + revision: 'revision', + botFiltering: true, + anonymizeIP: true + }, + user: { + id: 'userId', + attributes: [] + }, + layer: { + id: 'layerId' + }, + experiment: { + id: 'expId', + key: 'expKey' + }, + variation: { + id: 'varId', + key: 'varKey' + }, + ruleKey: 'expKey', + flagKey: 'flagKey1', + ruleType: 'experiment', + enabled: true + }; + + // Test for US region + const usEvent = { + ...baseEvent, + context: { + ...baseEvent.context, + region: 'US' as Region + } + }; + + const usResult = buildLogEvent([usEvent]); + expect(usResult.url).toBe('https://logx.optimizely.com/v1/events'); + + // Test for EU region + const euEvent = { + ...baseEvent, + context: { + ...baseEvent.context, + region: 'EU' as Region + } + }; + + const euResult = buildLogEvent([euEvent]); + expect(euResult.url).toBe('https://eu.logx.optimizely.com/v1/events'); + }); +}); diff --git a/packages/optimizely-sdk/lib/core/event_builder/build_event_v1.ts b/lib/event_processor/event_builder/log_event.ts similarity index 72% rename from packages/optimizely-sdk/lib/core/event_builder/build_event_v1.ts rename to lib/event_processor/event_builder/log_event.ts index b1f5b271d..4d4048950 100644 --- a/packages/optimizely-sdk/lib/core/event_builder/build_event_v1.ts +++ b/lib/event_processor/event_builder/log_event.ts @@ -1,5 +1,5 @@ /** - * Copyright 2021-2022, Optimizely + * Copyright 2021-2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,21 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - EventTags, - ConversionEvent, - ImpressionEvent, -} from '../../modules/event_processor'; +import { ConversionEvent, ImpressionEvent, UserEvent } from './user_event'; -import { Event } from '../../shared_types'; +import { CONTROL_ATTRIBUTES } from '../../utils/enums'; -type ProcessableEvent = ConversionEvent | ImpressionEvent +import { LogEvent } from '../event_dispatcher/event_dispatcher'; +import { EventTags } from '../../shared_types'; +import { Region } from '../../project_config/project_config'; const ACTIVATE_EVENT_KEY = 'campaign_activated' const CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom' -const BOT_FILTERING_KEY = '$opt_bot_filtering' -export type EventV1 = { +export const logxEndpoint: Record<Region, string> = { + US: 'https://logx.optimizely.com/v1/events', + EU: 'https://eu.logx.optimizely.com/v1/events', +} + +export type EventBatch = { account_id: string project_id: string revision: string @@ -73,6 +75,7 @@ type Metadata = { rule_type: string; variation_key: string; enabled: boolean; + cmab_uuid?: string; } export type SnapshotEvent = { @@ -89,10 +92,10 @@ export type SnapshotEvent = { * Given an array of batchable Decision or ConversionEvent events it returns * a single EventV1 with proper batching * - * @param {ProcessableEvent[]} events - * @returns {EventV1} + * @param {UserEvent[]} events + * @returns {EventBatch} */ -export function makeBatchedEventV1(events: ProcessableEvent[]): EventV1 { +export function makeEventBatch(events: UserEvent[]): EventBatch { const visitors: Visitor[] = [] const data = events[0] @@ -157,7 +160,7 @@ function makeConversionSnapshot(conversion: ConversionEvent): Snapshot { } function makeDecisionSnapshot(event: ImpressionEvent): Snapshot { - const { layer, experiment, variation, ruleKey, flagKey, ruleType, enabled } = event + const { layer, experiment, variation, ruleKey, flagKey, ruleType, enabled, cmabUuid } = event const layerId = layer ? layer.id : null const experimentId = experiment?.id ?? '' const variationId = variation?.id ?? '' @@ -175,6 +178,7 @@ function makeDecisionSnapshot(event: ImpressionEvent): Snapshot { rule_type: ruleType, variation_key: variationKey, enabled: enabled, + cmab_uuid: cmabUuid, }, }, ], @@ -207,8 +211,8 @@ function makeVisitor(data: ImpressionEvent | ConversionEvent): Visitor { if (typeof data.context.botFiltering === 'boolean') { visitor.attributes.push({ - entity_id: BOT_FILTERING_KEY, - key: BOT_FILTERING_KEY, + entity_id: CONTROL_ATTRIBUTES.BOT_FILTERING, + key: CONTROL_ATTRIBUTES.BOT_FILTERING, type: CUSTOM_ATTRIBUTE_FEATURE_TYPE, value: data.context.botFiltering, }) @@ -216,52 +220,13 @@ function makeVisitor(data: ImpressionEvent | ConversionEvent): Visitor { return visitor } -/** - * Event for usage with v1 logtier - * - * @export - * @interface EventBuilderV1 - */ -export function buildImpressionEventV1(data: ImpressionEvent): EventV1 { - const visitor = makeVisitor(data) - visitor.snapshots.push(makeDecisionSnapshot(data)) - - return { - client_name: data.context.clientName, - client_version: data.context.clientVersion, - - account_id: data.context.accountId, - project_id: data.context.projectId, - revision: data.context.revision, - anonymize_ip: data.context.anonymizeIP, - enrich_decisions: true, - - visitors: [visitor], - } -} - -export function buildConversionEventV1(data: ConversionEvent): EventV1 { - const visitor = makeVisitor(data) - visitor.snapshots.push(makeConversionSnapshot(data)) - - return { - client_name: data.context.clientName, - client_version: data.context.clientVersion, - - account_id: data.context.accountId, - project_id: data.context.projectId, - revision: data.context.revision, - anonymize_ip: data.context.anonymizeIP, - enrich_decisions: true, - - visitors: [visitor], - } -} +export function buildLogEvent(events: UserEvent[]): LogEvent { + const region = events[0]?.context.region || 'US'; + const url = logxEndpoint[region] || logxEndpoint['US']; -export function formatEvents(events: ProcessableEvent[]): Event { return { - url: 'https://logx.optimizely.com/v1/events', + url, httpVerb: 'POST', - params: makeBatchedEventV1(events), + params: makeEventBatch(events), } } diff --git a/lib/event_processor/event_builder/user_event.spec.ts b/lib/event_processor/event_builder/user_event.spec.ts new file mode 100644 index 000000000..e8cb373b3 --- /dev/null +++ b/lib/event_processor/event_builder/user_event.spec.ts @@ -0,0 +1,81 @@ +import { describe, it, expect, vi } from 'vitest'; +import { buildImpressionEvent, buildConversionEvent } from './user_event'; +import { createProjectConfig, ProjectConfig } from '../../project_config/project_config'; +import { DecisionObj } from '../../core/decision_service'; +import testData from '../../tests/test_data'; + +describe('buildImpressionEvent', () => { + it('should use correct region from projectConfig in event context', () => { + const projectConfig = createProjectConfig( + testData.getTestProjectConfig(), + ) + + const experiment = projectConfig.experiments[0]; + const variation = experiment.variations[0]; + + const decisionObj = { + experiment, + variation, + decisionSource: 'experiment', + } as DecisionObj; + + + const impressionEvent = buildImpressionEvent({ + configObj: projectConfig, + decisionObj, + userId: 'test_user', + flagKey: 'test_flag', + enabled: true, + clientEngine: 'node-sdk', + clientVersion: '1.0.0', + }); + + expect(impressionEvent.context.region).toBe('US'); + + projectConfig.region = 'EU'; + + const impressionEventEU = buildImpressionEvent({ + configObj: projectConfig, + decisionObj, + userId: 'test_user', + flagKey: 'test_flag', + enabled: true, + clientEngine: 'node-sdk', + clientVersion: '1.0.0', + }); + + expect(impressionEventEU.context.region).toBe('EU'); + }); +}); + +describe('buildConversionEvent', () => { + it('should use correct region from projectConfig in event context', () => { + const projectConfig = createProjectConfig( + testData.getTestProjectConfig(), + ) + + const conversionEvent = buildConversionEvent({ + configObj: projectConfig, + userId: 'test_user', + eventKey: 'test_event', + eventTags: { revenue: 1000 }, + clientEngine: 'node-sdk', + clientVersion: '1.0.0', + }); + + expect(conversionEvent.context.region).toBe('US'); + + projectConfig.region = 'EU'; + + const conversionEventEU = buildConversionEvent({ + configObj: projectConfig, + userId: 'test_user', + eventKey: 'test_event', + eventTags: { revenue: 1000 }, + clientEngine: 'node-sdk', + clientVersion: '1.0.0', + }); + + expect(conversionEventEU.context.region).toBe('EU'); + }); +}); diff --git a/packages/optimizely-sdk/lib/core/event_builder/event_helpers.tests.js b/lib/event_processor/event_builder/user_event.tests.js similarity index 95% rename from packages/optimizely-sdk/lib/core/event_builder/event_helpers.tests.js rename to lib/event_processor/event_builder/user_event.tests.js index 3d722b975..30f271d0e 100644 --- a/packages/optimizely-sdk/lib/core/event_builder/event_helpers.tests.js +++ b/lib/event_processor/event_builder/user_event.tests.js @@ -1,5 +1,5 @@ /** - * Copyright 2019-2020, Optimizely + * Copyright 2019-2020, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,15 +17,16 @@ import sinon from 'sinon'; import { assert } from 'chai'; import fns from '../../utils/fns'; -import * as projectConfig from '../project_config'; -import * as decision from '../decision'; -import { buildImpressionEvent, buildConversionEvent } from './event_helpers'; +import * as projectConfig from '../../project_config/project_config'; +import * as decision from '../../core/decision'; +import { buildImpressionEvent, buildConversionEvent } from './user_event'; -describe('lib/event_builder/event_helpers', function() { +describe('user_event', function() { var configObj; beforeEach(function() { configObj = { + region: 'US', accountId: 'accountId', projectId: 'projectId', revision: '69', @@ -106,6 +107,7 @@ describe('lib/event_builder/event_helpers', function() { timestamp: 100, uuid: 'uuid', context: { + region: 'US', accountId: 'accountId', projectId: 'projectId', revision: '69', @@ -142,6 +144,7 @@ describe('lib/event_builder/event_helpers', function() { flagKey: 'flagkey1', ruleType: 'experiment', enabled: true, + cmabUuid: undefined, }); }); }); @@ -199,6 +202,7 @@ describe('lib/event_builder/event_helpers', function() { timestamp: 100, uuid: 'uuid', context: { + region: 'US', accountId: 'accountId', projectId: 'projectId', revision: '69', @@ -235,6 +239,7 @@ describe('lib/event_builder/event_helpers', function() { flagKey: 'flagkey1', ruleType: 'experiment', enabled: false, + cmabUuid: undefined, }); }); }); @@ -268,6 +273,7 @@ describe('lib/event_builder/event_helpers', function() { timestamp: 100, uuid: 'uuid', context: { + region: 'US', accountId: 'accountId', projectId: 'projectId', revision: '69', @@ -334,6 +340,7 @@ describe('lib/event_builder/event_helpers', function() { timestamp: 100, uuid: 'uuid', context: { + region: 'US', accountId: 'accountId', projectId: 'projectId', revision: '69', diff --git a/packages/optimizely-sdk/lib/core/event_builder/event_helpers.ts b/lib/event_processor/event_builder/user_event.ts similarity index 54% rename from packages/optimizely-sdk/lib/core/event_builder/event_helpers.ts rename to lib/event_processor/event_builder/user_event.ts index e6f57fdbd..ae33d65da 100644 --- a/packages/optimizely-sdk/lib/core/event_builder/event_helpers.ts +++ b/lib/event_processor/event_builder/user_event.ts @@ -1,5 +1,5 @@ /** - * Copyright 2019-2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,106 +13,155 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getLogger } from '../../modules/logging'; - -import fns from '../../utils/fns'; +import { DecisionObj } from '../../core/decision_service'; +import * as decision from '../../core/decision'; +import { isAttributeValid } from '../../utils/attributes_validator'; import * as eventTagUtils from '../../utils/event_tag_utils'; -import * as attributesValidator from '../../utils/attributes_validator'; -import * as decision from '../decision'; - -import { EventTags, UserAttributes } from '../../shared_types'; -import { DecisionObj } from '../decision_service'; +import fns from '../../utils/fns'; import { getAttributeId, getEventId, getLayerId, ProjectConfig, -} from '../project_config'; + Region, +} from '../../project_config/project_config'; -const logger = getLogger('EVENT_BUILDER'); +import { EventTags, UserAttributes } from '../../shared_types'; +import { LoggerFacade } from '../../logging/logger'; +import { DECISION_SOURCES } from '../../common_exports'; -interface ImpressionConfig { - decisionObj: DecisionObj; - userId: string; - flagKey: string; - enabled: boolean; - userAttributes?: UserAttributes; - clientEngine: string; - clientVersion: string; - configObj: ProjectConfig; +export type VisitorAttribute = { + entityId: string + key: string + value: string | number | boolean } -type VisitorAttribute = { - entityId: string; - key: string; - value: string | number | boolean; +type EventContext = { + region?: Region; + accountId: string; + projectId: string; + revision: string; + clientName: string; + clientVersion: string; + anonymizeIP: boolean; + botFiltering?: boolean; } -interface ImpressionEvent { - type: 'impression'; +type EventType = 'impression' | 'conversion'; + + +export type BaseUserEvent<T extends EventType> = { + type: T; timestamp: number; uuid: string; + context: EventContext; user: { id: string; attributes: VisitorAttribute[]; }; - context: EventContext; +}; + +export type ImpressionEvent = BaseUserEvent<'impression'> & { layer: { id: string | null; - }; + } | null; + experiment: { id: string | null; key: string; } | null; + variation: { id: string | null; key: string; } | null; - ruleKey: string, - flagKey: string, - ruleType: string, - enabled: boolean, + ruleKey: string; + flagKey: string; + ruleType: string; + enabled: boolean; + cmabUuid?: string; +}; + +export type ConversionEvent = BaseUserEvent<'conversion'> & { + event: { + id: string | null; + key: string; + } + + revenue: number | null; + value: number | null; + tags?: EventTags; } -type EventContext = { - accountId: string; - projectId: string; - revision: string; - clientName: string; - clientVersion: string; - anonymizeIP: boolean; - botFiltering: boolean | undefined; +export type UserEvent = ImpressionEvent | ConversionEvent; + +export const areEventContextsEqual = (eventA: UserEvent, eventB: UserEvent): boolean => { + const contextA = eventA.context + const contextB = eventB.context + + const regionA: Region = contextA.region || 'US'; + const regionB: Region = contextB.region || 'US'; + + return ( + regionA === regionB && + contextA.accountId === contextB.accountId && + contextA.projectId === contextB.projectId && + contextA.clientName === contextB.clientName && + contextA.clientVersion === contextB.clientVersion && + contextA.revision === contextB.revision && + contextA.anonymizeIP === contextB.anonymizeIP && + contextA.botFiltering === contextB.botFiltering + ) } -interface ConversionConfig { - eventKey: string; - eventTags?: EventTags; +const buildBaseEvent = <T extends EventType>({ + configObj, + userId, + userAttributes, + clientEngine, + clientVersion, + type, +}: { + configObj: ProjectConfig; userId: string; userAttributes?: UserAttributes; clientEngine: string; clientVersion: string; - configObj: ProjectConfig; -} - -interface ConversionEvent { - type: 'conversion'; - timestamp: number; - uuid: string; - user: { - id: string; - attributes: VisitorAttribute[]; - }; - context: EventContext; - event: { - id: string | null; - key: string; + type: T; +}): BaseUserEvent<T> => { + return { + type, + timestamp: fns.currentTimestamp(), + uuid: fns.uuid(), + context: { + region: configObj.region, + accountId: configObj.accountId, + projectId: configObj.projectId, + revision: configObj.revision, + clientName: clientEngine, + clientVersion: clientVersion, + anonymizeIP: configObj.anonymizeIP || false, + botFiltering: configObj.botFiltering, + }, + user: { + id: userId, + attributes: buildVisitorAttributes(configObj, userAttributes), + }, }; - revenue: number | null; - value: number | null; - tags: EventTags | undefined; + } +export type ImpressionConfig = { + decisionObj: DecisionObj; + userId: string; + flagKey: string; + enabled: boolean; + userAttributes?: UserAttributes; + clientEngine: string; + clientVersion: string; + configObj: ProjectConfig; +} /** * Creates an ImpressionEvent object from decision data @@ -135,28 +184,19 @@ export const buildImpressionEvent = function({ const experimentId = decision.getExperimentId(decisionObj); const variationKey = decision.getVariationKey(decisionObj); const variationId = decision.getVariationId(decisionObj); - - const layerId = experimentId !== null ? getLayerId(configObj, experimentId) : null; + const cmabUuid = decisionObj.cmabUuid; + const layerId = + experimentId !== null ? (ruleType === DECISION_SOURCES.HOLDOUT ? '' : getLayerId(configObj, experimentId)) : null; return { - type: 'impression', - timestamp: fns.currentTimestamp(), - uuid: fns.uuid(), - - user: { - id: userId, - attributes: buildVisitorAttributes(configObj, userAttributes), - }, - - context: { - accountId: configObj.accountId, - projectId: configObj.projectId, - revision: configObj.revision, - clientName: clientEngine, - clientVersion: clientVersion, - anonymizeIP: configObj.anonymizeIP || false, - botFiltering: configObj.botFiltering, - }, + ...buildBaseEvent({ + configObj, + userId, + userAttributes, + clientEngine, + clientVersion, + type: 'impression', + }), layer: { id: layerId, @@ -176,9 +216,20 @@ export const buildImpressionEvent = function({ flagKey: flagKey, ruleType: ruleType, enabled: enabled, + cmabUuid, }; }; +export type ConversionConfig = { + eventKey: string; + eventTags?: EventTags; + userId: string; + userAttributes?: UserAttributes; + clientEngine: string; + clientVersion: string; + configObj: ProjectConfig; +} + /** * Creates a ConversionEvent object from track * @param {ConversionConfig} config @@ -191,8 +242,8 @@ export const buildConversionEvent = function({ clientEngine, clientVersion, eventKey, - eventTags, -}: ConversionConfig): ConversionEvent { + eventTags, +}: ConversionConfig, logger?: LoggerFacade): ConversionEvent { const eventId = getEventId(configObj, eventKey); @@ -200,24 +251,14 @@ export const buildConversionEvent = function({ const eventValue = eventTags ? eventTagUtils.getEventValue(eventTags, logger) : null; return { - type: 'conversion', - timestamp: fns.currentTimestamp(), - uuid: fns.uuid(), - - user: { - id: userId, - attributes: buildVisitorAttributes(configObj, userAttributes), - }, - - context: { - accountId: configObj.accountId, - projectId: configObj.projectId, - revision: configObj.revision, - clientName: clientEngine, - clientVersion: clientVersion, - anonymizeIP: configObj.anonymizeIP || false, - botFiltering: configObj.botFiltering, - }, + ...buildBaseEvent({ + configObj, + userId, + userAttributes, + clientEngine, + clientVersion, + type: 'conversion', + }), event: { id: eventId, @@ -230,27 +271,36 @@ export const buildConversionEvent = function({ }; }; -function buildVisitorAttributes( + +const buildVisitorAttributes = ( configObj: ProjectConfig, - attributes?: UserAttributes -): VisitorAttribute[] { - const builtAttributes: VisitorAttribute[] = []; + attributes?: UserAttributes, + logger?: LoggerFacade +): VisitorAttribute[] => { + if (!attributes) { + return []; + } + // Omit attribute values that are not supported by the log endpoint. - if (attributes) { - Object.keys(attributes || {}).forEach(function(attributeKey) { - const attributeValue = attributes[attributeKey]; - if (attributesValidator.isAttributeValid(attributeKey, attributeValue)) { - const attributeId = getAttributeId(configObj, attributeKey, logger); - if (attributeId) { - builtAttributes.push({ - entityId: attributeId, - key: attributeKey, - value: attributes[attributeKey], - }); - } + const builtAttributes: VisitorAttribute[] = []; + Object.keys(attributes).forEach(function(attributeKey) { + const attributeValue = attributes[attributeKey]; + + if (typeof attributeValue === 'object' || typeof attributeValue === 'undefined') { + return; + } + + if (isAttributeValid(attributeKey, attributeValue)) { + const attributeId = getAttributeId(configObj, attributeKey, logger); + if (attributeId) { + builtAttributes.push({ + entityId: attributeId, + key: attributeKey, + value: attributeValue, + }); } - }); - } + } + }); return builtAttributes; } diff --git a/lib/event_processor/event_dispatcher/default_dispatcher.browser.spec.ts b/lib/event_processor/event_dispatcher/default_dispatcher.browser.spec.ts new file mode 100644 index 000000000..82314cbc7 --- /dev/null +++ b/lib/event_processor/event_dispatcher/default_dispatcher.browser.spec.ts @@ -0,0 +1,49 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { vi, expect, it, describe, afterAll } from 'vitest'; + +vi.mock('./default_dispatcher', () => { + const DefaultEventDispatcher = vi.fn(); + return { DefaultEventDispatcher }; +}); + +vi.mock('../../utils/http_request_handler/request_handler.browser', () => { + const BrowserRequestHandler = vi.fn(); + return { BrowserRequestHandler }; +}); + +import { DefaultEventDispatcher } from './default_dispatcher'; +import { BrowserRequestHandler } from '../../utils/http_request_handler/request_handler.browser'; +import eventDispatcher from './default_dispatcher.browser'; + +describe('eventDispatcher', () => { + afterAll(() => { + MockDefaultEventDispatcher.mockReset(); + MockBrowserRequestHandler.mockReset(); + }); + const MockBrowserRequestHandler = vi.mocked(BrowserRequestHandler); + const MockDefaultEventDispatcher = vi.mocked(DefaultEventDispatcher); + + it('creates and returns the instance by calling DefaultEventDispatcher', () => { + expect(Object.is(eventDispatcher, MockDefaultEventDispatcher.mock.instances[0])).toBe(true); + }); + + it('uses a BrowserRequestHandler', () => { + expect(Object.is(eventDispatcher, MockDefaultEventDispatcher.mock.instances[0])).toBe(true); + expect(Object.is(MockDefaultEventDispatcher.mock.calls[0][0], MockBrowserRequestHandler.mock.instances[0])).toBe(true); + }); +}); diff --git a/packages/optimizely-sdk/lib/plugins/error_handler/index.tests.js b/lib/event_processor/event_dispatcher/default_dispatcher.browser.ts similarity index 60% rename from packages/optimizely-sdk/lib/plugins/error_handler/index.tests.js rename to lib/event_processor/event_dispatcher/default_dispatcher.browser.ts index b3a632b92..d38d266aa 100644 --- a/packages/optimizely-sdk/lib/plugins/error_handler/index.tests.js +++ b/lib/event_processor/event_dispatcher/default_dispatcher.browser.ts @@ -1,5 +1,5 @@ /** - * Copyright 2016, 2020 Optimizely + * Copyright 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,16 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { assert } from 'chai'; -import { handleError } from './'; +import { BrowserRequestHandler } from "../../utils/http_request_handler/request_handler.browser"; +import { EventDispatcher } from './event_dispatcher'; +import { DefaultEventDispatcher } from './default_dispatcher'; -describe('lib/plugins/error_handler', function() { - describe('APIs', function() { - describe('handleError', function() { - it('should just be a no-op function', function() { - assert.isFunction(handleError); - }); - }); - }); -}); +const eventDispatcher: EventDispatcher = new DefaultEventDispatcher(new BrowserRequestHandler()); + +export default eventDispatcher; diff --git a/lib/event_processor/event_dispatcher/default_dispatcher.node.spec.ts b/lib/event_processor/event_dispatcher/default_dispatcher.node.spec.ts new file mode 100644 index 000000000..084fcce67 --- /dev/null +++ b/lib/event_processor/event_dispatcher/default_dispatcher.node.spec.ts @@ -0,0 +1,49 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { vi, expect, it, describe, afterAll } from 'vitest'; + +vi.mock('./default_dispatcher', () => { + const DefaultEventDispatcher = vi.fn(); + return { DefaultEventDispatcher }; +}); + +vi.mock('../../utils/http_request_handler/request_handler.node', () => { + const NodeRequestHandler = vi.fn(); + return { NodeRequestHandler }; +}); + +import { DefaultEventDispatcher } from './default_dispatcher'; +import { NodeRequestHandler } from '../../utils/http_request_handler/request_handler.node'; +import eventDispatcher from './default_dispatcher.node'; + +describe('eventDispatcher', () => { + const MockNodeRequestHandler = vi.mocked(NodeRequestHandler); + const MockDefaultEventDispatcher = vi.mocked(DefaultEventDispatcher); + + afterAll(() => { + MockDefaultEventDispatcher.mockReset(); + MockNodeRequestHandler.mockReset(); + }) + + it('creates and returns the instance by calling DefaultEventDispatcher', () => { + expect(Object.is(eventDispatcher, MockDefaultEventDispatcher.mock.instances[0])).toBe(true); + }); + + it('uses a NodeRequestHandler', () => { + expect(Object.is(eventDispatcher, MockDefaultEventDispatcher.mock.instances[0])).toBe(true); + expect(Object.is(MockDefaultEventDispatcher.mock.calls[0][0], MockNodeRequestHandler.mock.instances[0])).toBe(true); + }); +}); diff --git a/packages/optimizely-sdk/lib/core/odp/lru_cache/index.ts b/lib/event_processor/event_dispatcher/default_dispatcher.node.ts similarity index 60% rename from packages/optimizely-sdk/lib/core/odp/lru_cache/index.ts rename to lib/event_processor/event_dispatcher/default_dispatcher.node.ts index cb21e5693..65dc115af 100644 --- a/packages/optimizely-sdk/lib/core/odp/lru_cache/index.ts +++ b/lib/event_processor/event_dispatcher/default_dispatcher.node.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2024 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,11 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { EventDispatcher } from './event_dispatcher'; +import { NodeRequestHandler } from '../../utils/http_request_handler/request_handler.node'; +import { DefaultEventDispatcher } from './default_dispatcher'; -import { LRUCache, ClientLRUCache, ServerLRUCache } from "./LRUCache"; +const eventDispatcher: EventDispatcher = new DefaultEventDispatcher(new NodeRequestHandler()); -export { - LRUCache, - ClientLRUCache, - ServerLRUCache, -} \ No newline at end of file +export default eventDispatcher; diff --git a/lib/event_processor/event_dispatcher/default_dispatcher.spec.ts b/lib/event_processor/event_dispatcher/default_dispatcher.spec.ts new file mode 100644 index 000000000..45a198788 --- /dev/null +++ b/lib/event_processor/event_dispatcher/default_dispatcher.spec.ts @@ -0,0 +1,116 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { expect, vi, describe, it } from 'vitest'; +import { DefaultEventDispatcher } from './default_dispatcher'; +import { EventBatch } from '../event_builder/log_event'; + +const getEvent = (): EventBatch => { + return { + account_id: 'string', + project_id: 'string', + revision: 'string', + client_name: 'string', + client_version: 'string', + anonymize_ip: true, + enrich_decisions: false, + visitors: [], + }; +}; + +describe('DefaultEventDispatcher', () => { + it('reject the response promise if the eventObj.httpVerb is not POST', async () => { + const eventObj = { + url: 'https://cdn.com/event', + params: getEvent(), + httpVerb: 'GET' as const, + }; + + const requestHnadler = { + makeRequest: vi.fn().mockReturnValue({ + abort: vi.fn(), + responsePromise: Promise.resolve({ statusCode: 203 }), + }), + }; + + const dispatcher = new DefaultEventDispatcher(requestHnadler); + await expect(dispatcher.dispatchEvent(eventObj)).rejects.toThrow(); + }); + + it('sends correct headers and data to the requestHandler', async () => { + const eventObj = { + url: 'https://cdn.com/event', + params: getEvent(), + httpVerb: 'POST' as const, + }; + + const requestHnadler = { + makeRequest: vi.fn().mockReturnValue({ + abort: vi.fn(), + responsePromise: Promise.resolve({ statusCode: 203 }), + }), + }; + + const dispatcher = new DefaultEventDispatcher(requestHnadler); + await dispatcher.dispatchEvent(eventObj); + + expect(requestHnadler.makeRequest).toHaveBeenCalledWith( + eventObj.url, + { + 'content-type': 'application/json' + }, + 'POST', + JSON.stringify(eventObj.params) + ); + }); + + it('returns a promise that resolves with correct value if the response of the requestHandler resolves', async () => { + const eventObj = { + url: 'https://cdn.com/event', + params: getEvent(), + httpVerb: 'POST' as const, + }; + + const requestHnadler = { + makeRequest: vi.fn().mockReturnValue({ + abort: vi.fn(), + responsePromise: Promise.resolve({ statusCode: 203 }), + }), + }; + + const dispatcher = new DefaultEventDispatcher(requestHnadler); + const response = await dispatcher.dispatchEvent(eventObj); + + expect(response.statusCode).toEqual(203); + }); + + it('returns a promise that rejects if the response of the requestHandler rejects', async () => { + const eventObj = { + url: 'https://cdn.com/event', + params: getEvent(), + httpVerb: 'POST' as const, + }; + + const requestHnadler = { + makeRequest: vi.fn().mockReturnValue({ + abort: vi.fn(), + responsePromise: Promise.reject(new Error('error')), + }), + }; + + const dispatcher = new DefaultEventDispatcher(requestHnadler); + await expect(dispatcher.dispatchEvent(eventObj)).rejects.toThrow(); + }); +}); diff --git a/lib/event_processor/event_dispatcher/default_dispatcher.ts b/lib/event_processor/event_dispatcher/default_dispatcher.ts new file mode 100644 index 000000000..b786ffda2 --- /dev/null +++ b/lib/event_processor/event_dispatcher/default_dispatcher.ts @@ -0,0 +1,45 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { OptimizelyError } from '../../error/optimizly_error'; +import { ONLY_POST_REQUESTS_ARE_SUPPORTED } from 'error_message'; +import { RequestHandler } from '../../utils/http_request_handler/http'; +import { EventDispatcher, EventDispatcherResponse, LogEvent } from './event_dispatcher'; + +export class DefaultEventDispatcher implements EventDispatcher { + private requestHandler: RequestHandler; + + constructor(requestHandler: RequestHandler) { + this.requestHandler = requestHandler; + } + + async dispatchEvent( + eventObj: LogEvent + ): Promise<EventDispatcherResponse> { + // Non-POST requests not supported + if (eventObj.httpVerb !== 'POST') { + return Promise.reject(new OptimizelyError(ONLY_POST_REQUESTS_ARE_SUPPORTED)); + } + + const dataString = JSON.stringify(eventObj.params); + + const headers = { + 'content-type': 'application/json', + }; + + const abortableRequest = this.requestHandler.makeRequest(eventObj.url, headers, 'POST', dataString); + return abortableRequest.responsePromise; + } +} diff --git a/packages/optimizely-sdk/lib/modules/event_processor/eventDispatcher.ts b/lib/event_processor/event_dispatcher/event_dispatcher.ts similarity index 69% rename from packages/optimizely-sdk/lib/modules/event_processor/eventDispatcher.ts rename to lib/event_processor/event_dispatcher/event_dispatcher.ts index 15d261cf2..4dfda8f30 100644 --- a/packages/optimizely-sdk/lib/modules/event_processor/eventDispatcher.ts +++ b/lib/event_processor/event_dispatcher/event_dispatcher.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,20 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { EventV1 } from "./v1/buildEventV1"; +import { EventBatch } from "../event_builder/log_event"; export type EventDispatcherResponse = { - statusCode: number + statusCode?: number } -export type EventDispatcherCallback = (response: EventDispatcherResponse) => void - export interface EventDispatcher { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void + dispatchEvent(event: LogEvent): Promise<EventDispatcherResponse> } -export interface EventV1Request { +export interface LogEvent { url: string httpVerb: 'POST' | 'PUT' | 'GET' | 'PATCH' - params: EventV1, + params: EventBatch, } diff --git a/lib/event_processor/event_dispatcher/event_dispatcher_factory.ts b/lib/event_processor/event_dispatcher/event_dispatcher_factory.ts new file mode 100644 index 000000000..383ad8380 --- /dev/null +++ b/lib/event_processor/event_dispatcher/event_dispatcher_factory.ts @@ -0,0 +1,26 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RequestHandler } from '../../utils/http_request_handler/http'; +import { DefaultEventDispatcher } from './default_dispatcher'; +import { EventDispatcher } from './event_dispatcher'; + +import { validateRequestHandler } from '../../utils/http_request_handler/request_handler_validator'; + +export const createEventDispatcher = (requestHander: RequestHandler): EventDispatcher => { + validateRequestHandler(requestHander); + return new DefaultEventDispatcher(requestHander); +} diff --git a/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.spec.ts b/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.spec.ts new file mode 100644 index 000000000..06bd5bd1f --- /dev/null +++ b/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.spec.ts @@ -0,0 +1,81 @@ +/** + * Copyright 2023-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, beforeEach, it, expect, vi, MockInstance } from 'vitest'; + +import sendBeaconDispatcher, { Event } from './send_beacon_dispatcher.browser'; + +describe('dispatchEvent', function() { + let sendBeaconSpy: MockInstance<typeof navigator.sendBeacon>; + + beforeEach(() => { + sendBeaconSpy = vi.fn(); + navigator.sendBeacon = sendBeaconSpy as any; + }); + + it('should call sendBeacon with correct url, data and type', async () => { + const eventParams = { testParam: 'testParamValue' }; + const eventObj: Event = { + url: 'https://cdn.com/event', + httpVerb: 'POST', + params: eventParams, + }; + + sendBeaconSpy.mockReturnValue(true); + + sendBeaconDispatcher.dispatchEvent(eventObj) + + const [url, data] = sendBeaconSpy.mock.calls[0]; + const blob = data as Blob; + + const reader = new FileReader(); + reader.readAsBinaryString(blob); + + const sentParams = await new Promise((resolve) => { + reader.onload = () => { + resolve(reader.result); + }; + }); + + + expect(url).toEqual(eventObj.url); + expect(blob.type).toEqual('application/json'); + expect(sentParams).toEqual(JSON.stringify(eventObj.params)); + }); + + it('should resolve the response on sendBeacon success', async () => { + const eventParams = { testParam: 'testParamValue' }; + const eventObj: Event = { + url: 'https://cdn.com/event', + httpVerb: 'POST', + params: eventParams, + }; + + sendBeaconSpy.mockReturnValue(true); + await expect(sendBeaconDispatcher.dispatchEvent(eventObj)).resolves.not.toThrow(); + }); + + it('should reject the response on sendBeacon success', async () => { + const eventParams = { testParam: 'testParamValue' }; + const eventObj: Event = { + url: 'https://cdn.com/event', + httpVerb: 'POST', + params: eventParams, + }; + + sendBeaconSpy.mockReturnValue(false); + await expect(sendBeaconDispatcher.dispatchEvent(eventObj)).rejects.toThrow(); + }); +}); diff --git a/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.ts b/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.ts new file mode 100644 index 000000000..006adedd6 --- /dev/null +++ b/lib/event_processor/event_dispatcher/send_beacon_dispatcher.browser.ts @@ -0,0 +1,53 @@ +/** + * Copyright 2023-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { OptimizelyError } from '../../error/optimizly_error'; +import { SEND_BEACON_FAILED } from 'error_message'; +import { EventDispatcher, EventDispatcherResponse } from './event_dispatcher'; + +export type Event = { + url: string; + httpVerb: 'POST'; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + params: any; +} + +/** + * Sample event dispatcher implementation for tracking impression and conversions + * Users of the SDK can provide their own implementation + * @param {Event} eventObj + * @param {Function} callback + */ +export const dispatchEvent = function( + eventObj: Event, +): Promise<EventDispatcherResponse> { + const { params, url } = eventObj; + const blob = new Blob([JSON.stringify(params)], { + type: "application/json", + }); + + const success = navigator.sendBeacon(url, blob); + if(success) { + return Promise.resolve({}); + } + return Promise.reject(new OptimizelyError(SEND_BEACON_FAILED)); +} + +const eventDispatcher : EventDispatcher = { + dispatchEvent, +} + +export default eventDispatcher; diff --git a/lib/event_processor/event_processor.ts b/lib/event_processor/event_processor.ts new file mode 100644 index 000000000..585c71f68 --- /dev/null +++ b/lib/event_processor/event_processor.ts @@ -0,0 +1,32 @@ +/** + * Copyright 2022-2024 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ConversionEvent, ImpressionEvent } from './event_builder/user_event' +import { LogEvent } from './event_dispatcher/event_dispatcher' +import { Service } from '../service' +import { Consumer, Fn } from '../utils/type'; +import { LoggerFacade } from '../logging/logger'; + +export const DEFAULT_FLUSH_INTERVAL = 30000 // Unit is ms - default flush interval is 30s +export const DEFAULT_BATCH_SIZE = 10 + +export type ProcessableEvent = ConversionEvent | ImpressionEvent + +export interface EventProcessor extends Service { + process(event: ProcessableEvent): Promise<unknown>; + onDispatch(handler: Consumer<LogEvent>): Fn; + setLogger(logger: LoggerFacade): void; + flushImmediately(): Promise<unknown>; +} diff --git a/lib/event_processor/event_processor_factory.browser.spec.ts b/lib/event_processor/event_processor_factory.browser.spec.ts new file mode 100644 index 000000000..a5d2a6af3 --- /dev/null +++ b/lib/event_processor/event_processor_factory.browser.spec.ts @@ -0,0 +1,179 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { vi, describe, it, expect, beforeEach } from 'vitest'; + +vi.mock('./default_dispatcher.browser', () => { + return { default: {} }; +}); + +vi.mock('./event_processor_factory', async (importOriginal) => { + const getBatchEventProcessor = vi.fn().mockImplementation(() => { + return {}; + }); + const getOpaqueBatchEventProcessor = vi.fn().mockImplementation(() => { + return {}; + }); + const getForwardingEventProcessor = vi.fn().mockImplementation(() => { + return {}; + }); + const original: any = await importOriginal(); + return { ...original, getBatchEventProcessor, getOpaqueBatchEventProcessor, getForwardingEventProcessor }; +}); + +vi.mock('../utils/cache/local_storage_cache.browser', () => { + return { LocalStorageCache: vi.fn() }; +}); + +vi.mock('../utils/cache/store', () => { + return { SyncPrefixStore: vi.fn() }; +}); + + +import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; +import { LocalStorageCache } from '../utils/cache/local_storage_cache.browser'; +import { SyncPrefixStore } from '../utils/cache/store'; +import { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor_factory.browser'; +import { EVENT_STORE_PREFIX, extractEventProcessor, getForwardingEventProcessor, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; +import sendBeaconEventDispatcher from './event_dispatcher/send_beacon_dispatcher.browser'; +import browserDefaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; +import { getOpaqueBatchEventProcessor } from './event_processor_factory'; + +describe('createForwardingEventProcessor', () => { + const mockGetForwardingEventProcessor = vi.mocked(getForwardingEventProcessor); + + beforeEach(() => { + mockGetForwardingEventProcessor.mockClear(); + }); + + it('returns forwarding event processor by calling getForwardingEventProcessor with the provided dispatcher', () => { + const eventDispatcher = { + dispatchEvent: vi.fn(), + }; + + const processor = extractEventProcessor(createForwardingEventProcessor(eventDispatcher)); + + expect(Object.is(processor, mockGetForwardingEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, eventDispatcher); + }); + + it('uses the browser default event dispatcher if none is provided', () => { + const processor = extractEventProcessor(createForwardingEventProcessor()); + + expect(Object.is(processor, mockGetForwardingEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, browserDefaultEventDispatcher); + }); +}); + +describe('createBatchEventProcessor', () => { + const mockGetOpaqueBatchEventProcessor = vi.mocked(getOpaqueBatchEventProcessor); + const MockLocalStorageCache = vi.mocked(LocalStorageCache); + const MockSyncPrefixStore = vi.mocked(SyncPrefixStore); + + beforeEach(() => { + mockGetOpaqueBatchEventProcessor.mockClear(); + MockLocalStorageCache.mockClear(); + MockSyncPrefixStore.mockClear(); + }); + + it('uses LocalStorageCache and SyncPrefixStore to create eventStore', () => { + const processor = createBatchEventProcessor({}); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + const eventStore = mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore; + expect(Object.is(eventStore, MockSyncPrefixStore.mock.results[0].value)).toBe(true); + + const [cache, prefix, transformGet, transformSet] = MockSyncPrefixStore.mock.calls[0]; + expect(Object.is(cache, MockLocalStorageCache.mock.results[0].value)).toBe(true); + expect(prefix).toBe(EVENT_STORE_PREFIX); + + // transformGet and transformSet should be identity functions + expect(transformGet('value')).toBe('value'); + expect(transformSet('value')).toBe('value'); + }); + + it('uses the provided eventDispatcher', () => { + const eventDispatcher = { + dispatchEvent: vi.fn(), + }; + + const processor = createBatchEventProcessor({ eventDispatcher }); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(eventDispatcher); + }); + + it('uses the default browser event dispatcher if none is provided', () => { + const processor = createBatchEventProcessor({ }); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(defaultEventDispatcher); + }); + + it('uses the provided closingEventDispatcher', () => { + const closingEventDispatcher = { + dispatchEvent: vi.fn(), + }; + + const processor = createBatchEventProcessor({ closingEventDispatcher }); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(closingEventDispatcher); + }); + + it('does not use any closingEventDispatcher if eventDispatcher is provided but closingEventDispatcher is not', () => { + const eventDispatcher = { + dispatchEvent: vi.fn(), + }; + + const processor = createBatchEventProcessor({ eventDispatcher }); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(undefined); + }); + + it('uses the default sendBeacon event dispatcher if neither eventDispatcher nor closingEventDispatcher is provided', () => { + const processor = createBatchEventProcessor({ }); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(sendBeaconEventDispatcher); + }); + + it('uses the provided flushInterval', () => { + const processor1 = createBatchEventProcessor({ flushInterval: 2000 }); + expect(Object.is(processor1, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].flushInterval).toBe(2000); + + const processor2 = createBatchEventProcessor({ }); + expect(Object.is(processor2, mockGetOpaqueBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[1][0].flushInterval).toBe(undefined); + }); + + it('uses the provided batchSize', () => { + const processor1 = createBatchEventProcessor({ batchSize: 20 }); + expect(Object.is(processor1, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].batchSize).toBe(20); + + const processor2 = createBatchEventProcessor({ }); + expect(Object.is(processor2, mockGetOpaqueBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[1][0].batchSize).toBe(undefined); + }); + + it('uses maxRetries value of 5', () => { + const processor = createBatchEventProcessor({ }); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].retryOptions?.maxRetries).toBe(5); + }); + + it('uses the default failedEventRetryInterval', () => { + const processor = createBatchEventProcessor({ }); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].failedEventRetryInterval).toBe(FAILED_EVENT_RETRY_INTERVAL); + }); +}); diff --git a/lib/event_processor/event_processor_factory.browser.ts b/lib/event_processor/event_processor_factory.browser.ts new file mode 100644 index 000000000..e73b8bf24 --- /dev/null +++ b/lib/event_processor/event_processor_factory.browser.ts @@ -0,0 +1,67 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { EventDispatcher } from './event_dispatcher/event_dispatcher'; +import { EventProcessor } from './event_processor'; +import { EventWithId } from './batch_event_processor'; +import { + getOpaqueBatchEventProcessor, + BatchEventProcessorOptions, + OpaqueEventProcessor, + wrapEventProcessor, + getForwardingEventProcessor, +} from './event_processor_factory'; +import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; +import sendBeaconEventDispatcher from './event_dispatcher/send_beacon_dispatcher.browser'; +import { LocalStorageCache } from '../utils/cache/local_storage_cache.browser'; +import { SyncPrefixStore } from '../utils/cache/store'; +import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; + +export const DEFAULT_EVENT_BATCH_SIZE = 10; +export const DEFAULT_EVENT_FLUSH_INTERVAL = 1_000; + +export const createForwardingEventProcessor = ( + eventDispatcher: EventDispatcher = defaultEventDispatcher, +): OpaqueEventProcessor => { + return wrapEventProcessor(getForwardingEventProcessor(eventDispatcher)); +}; + +const identity = <T>(v: T): T => v; + +export const createBatchEventProcessor = ( + options: BatchEventProcessorOptions = {} +): OpaqueEventProcessor => { + const localStorageCache = new LocalStorageCache<EventWithId>(); + const eventStore = new SyncPrefixStore<EventWithId, EventWithId>( + localStorageCache, EVENT_STORE_PREFIX, + identity, + identity, + ); + + return getOpaqueBatchEventProcessor({ + eventDispatcher: options.eventDispatcher || defaultEventDispatcher, + closingEventDispatcher: options.closingEventDispatcher || + (options.eventDispatcher ? undefined : sendBeaconEventDispatcher), + flushInterval: options.flushInterval, + batchSize: options.batchSize, + defaultFlushInterval: DEFAULT_EVENT_FLUSH_INTERVAL, + defaultBatchSize: DEFAULT_EVENT_BATCH_SIZE, + retryOptions: { + maxRetries: 5, + }, + failedEventRetryInterval: FAILED_EVENT_RETRY_INTERVAL, + eventStore, + }); +}; diff --git a/lib/event_processor/event_processor_factory.node.spec.ts b/lib/event_processor/event_processor_factory.node.spec.ts new file mode 100644 index 000000000..22b943f19 --- /dev/null +++ b/lib/event_processor/event_processor_factory.node.spec.ts @@ -0,0 +1,202 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { vi, describe, it, expect, beforeEach } from 'vitest'; + +vi.mock('./default_dispatcher.node', () => { + return { default: {} }; +}); + +vi.mock('./event_processor_factory', async (importOriginal) => { + const getBatchEventProcessor = vi.fn().mockImplementation(() => { + return {}; + }); + const getOpaqueBatchEventProcessor = vi.fn().mockImplementation(() => { + return {}; + }); + const getForwardingEventProcessor = vi.fn().mockReturnValue({}); + const original: any = await importOriginal(); + return { ...original, getBatchEventProcessor, getOpaqueBatchEventProcessor, getForwardingEventProcessor }; +}); + +vi.mock('../utils/cache/async_storage_cache.react_native', () => { + return { AsyncStorageCache: vi.fn() }; +}); + +vi.mock('../utils/cache/store', () => { + return { SyncPrefixStore: vi.fn(), AsyncPrefixStore: vi.fn() }; +}); + +import { createBatchEventProcessor, createForwardingEventProcessor } from './event_processor_factory.node'; +import nodeDefaultEventDispatcher from './event_dispatcher/default_dispatcher.node'; +import { EVENT_STORE_PREFIX, extractEventProcessor, getForwardingEventProcessor, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; +import { getOpaqueBatchEventProcessor } from './event_processor_factory'; +import { AsyncStore, AsyncPrefixStore, SyncStore, SyncPrefixStore } from '../utils/cache/store'; +import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; + +describe('createForwardingEventProcessor', () => { + const mockGetForwardingEventProcessor = vi.mocked(getForwardingEventProcessor); + + beforeEach(() => { + mockGetForwardingEventProcessor.mockClear(); + }); + + it('returns forwarding event processor by calling getForwardingEventProcessor with the provided dispatcher', () => { + const eventDispatcher = { + dispatchEvent: vi.fn(), + }; + + const processor = extractEventProcessor(createForwardingEventProcessor(eventDispatcher)); + + expect(Object.is(processor, mockGetForwardingEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, eventDispatcher); + }); + + it('uses the node default event dispatcher if none is provided', () => { + const processor = extractEventProcessor(createForwardingEventProcessor()); + + expect(Object.is(processor, mockGetForwardingEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, nodeDefaultEventDispatcher); + }); +}); + +describe('createBatchEventProcessor', () => { + const mockGetOpaqueBatchEventProcessor = vi.mocked(getOpaqueBatchEventProcessor); + const MockAsyncStorageCache = vi.mocked(AsyncStorageCache); + const MockSyncPrefixStore = vi.mocked(SyncPrefixStore); + const MockAsyncPrefixStore = vi.mocked(AsyncPrefixStore); + + beforeEach(() => { + mockGetOpaqueBatchEventProcessor.mockClear(); + MockAsyncStorageCache.mockClear(); + MockSyncPrefixStore.mockClear(); + MockAsyncPrefixStore.mockClear(); + }); + + it('uses no default event store if no eventStore is provided', () => { + const processor = createBatchEventProcessor({}); + + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + const eventStore = mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore; + expect(eventStore).toBe(undefined); + }); + + it('wraps the provided eventStore in a SyncPrefixStore if a SyncCache is provided as eventStore', () => { + const eventStore = { + operation: 'sync', + } as SyncStore<string>; + + const processor = createBatchEventProcessor({ eventStore }); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockSyncPrefixStore.mock.results[0].value); + const [cache, prefix, transformGet, transformSet] = MockSyncPrefixStore.mock.calls[0]; + + expect(cache).toBe(eventStore); + expect(prefix).toBe(EVENT_STORE_PREFIX); + + // transformGet and transformSet should be JSON.parse and JSON.stringify + expect(transformGet('{"value": 1}')).toEqual({ value: 1 }); + expect(transformSet({ value: 1 })).toBe('{"value":1}'); + }); + + it('wraps the provided eventStore in a AsyncPrefixStore if a AsyncCache is provided as eventStore', () => { + const eventStore = { + operation: 'async', + } as AsyncStore<string>; + + const processor = createBatchEventProcessor({ eventStore }); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockAsyncPrefixStore.mock.results[0].value); + const [cache, prefix, transformGet, transformSet] = MockAsyncPrefixStore.mock.calls[0]; + + expect(cache).toBe(eventStore); + expect(prefix).toBe(EVENT_STORE_PREFIX); + + // transformGet and transformSet should be JSON.parse and JSON.stringify + expect(transformGet('{"value": 1}')).toEqual({ value: 1 }); + expect(transformSet({ value: 1 })).toBe('{"value":1}'); + }); + + + it('uses the provided eventDispatcher', () => { + const eventDispatcher = { + dispatchEvent: vi.fn(), + }; + + const processor = createBatchEventProcessor({ eventDispatcher }); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(eventDispatcher); + }); + + it('uses the default node event dispatcher if none is provided', () => { + const processor = createBatchEventProcessor({ }); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(nodeDefaultEventDispatcher); + }); + + it('uses the provided closingEventDispatcher', () => { + const closingEventDispatcher = { + dispatchEvent: vi.fn(), + }; + + const processor = createBatchEventProcessor({ closingEventDispatcher }); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(closingEventDispatcher); + + const processor2 = createBatchEventProcessor({ }); + expect(Object.is(processor2, mockGetOpaqueBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[1][0].closingEventDispatcher).toBe(undefined); + }); + + it('uses the provided flushInterval', () => { + const processor1 = createBatchEventProcessor({ flushInterval: 2000 }); + expect(Object.is(processor1, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].flushInterval).toBe(2000); + + const processor2 = createBatchEventProcessor({ }); + expect(Object.is(processor2, mockGetOpaqueBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[1][0].flushInterval).toBe(undefined); + }); + + it('uses the provided batchSize', () => { + const processor1 = createBatchEventProcessor({ batchSize: 20 }); + expect(Object.is(processor1, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].batchSize).toBe(20); + + const processor2 = createBatchEventProcessor({ }); + expect(Object.is(processor2, mockGetOpaqueBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[1][0].batchSize).toBe(undefined); + }); + + it('uses maxRetries value of 5', () => { + const processor = createBatchEventProcessor({ }); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].retryOptions?.maxRetries).toBe(5); + }); + + it('uses no failed event retry if an eventStore is not provided', () => { + const processor = createBatchEventProcessor({ }); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].failedEventRetryInterval).toBe(undefined); + }); + + it('uses the default failedEventRetryInterval if an eventStore is provided', () => { + const processor = createBatchEventProcessor({ eventStore: {} as any }); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].failedEventRetryInterval).toBe(FAILED_EVENT_RETRY_INTERVAL); + }); +}); diff --git a/lib/event_processor/event_processor_factory.node.ts b/lib/event_processor/event_processor_factory.node.ts new file mode 100644 index 000000000..b0ed4ffde --- /dev/null +++ b/lib/event_processor/event_processor_factory.node.ts @@ -0,0 +1,56 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { EventDispatcher } from './event_dispatcher/event_dispatcher'; +import defaultEventDispatcher from './event_dispatcher/default_dispatcher.node'; +import { + BatchEventProcessorOptions, + FAILED_EVENT_RETRY_INTERVAL, + getOpaqueBatchEventProcessor, + getPrefixEventStore, + OpaqueEventProcessor, + wrapEventProcessor, + getForwardingEventProcessor, +} from './event_processor_factory'; + +export const DEFAULT_EVENT_BATCH_SIZE = 10; +export const DEFAULT_EVENT_FLUSH_INTERVAL = 30_000; + +export const createForwardingEventProcessor = ( + eventDispatcher: EventDispatcher = defaultEventDispatcher, +): OpaqueEventProcessor => { + return wrapEventProcessor(getForwardingEventProcessor(eventDispatcher)); +}; + +export const createBatchEventProcessor = ( + options: BatchEventProcessorOptions = {} +): OpaqueEventProcessor => { + const eventStore = options.eventStore ? getPrefixEventStore(options.eventStore) : undefined; + + return getOpaqueBatchEventProcessor({ + eventDispatcher: options.eventDispatcher || defaultEventDispatcher, + closingEventDispatcher: options.closingEventDispatcher, + flushInterval: options.flushInterval, + batchSize: options.batchSize, + defaultFlushInterval: DEFAULT_EVENT_FLUSH_INTERVAL, + defaultBatchSize: DEFAULT_EVENT_BATCH_SIZE, + retryOptions: { + maxRetries: 5, + + }, + failedEventRetryInterval: eventStore ? FAILED_EVENT_RETRY_INTERVAL : undefined, + eventStore, + }); +}; diff --git a/lib/event_processor/event_processor_factory.react_native.spec.ts b/lib/event_processor/event_processor_factory.react_native.spec.ts new file mode 100644 index 000000000..630417a5e --- /dev/null +++ b/lib/event_processor/event_processor_factory.react_native.spec.ts @@ -0,0 +1,265 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { vi, describe, it, expect, beforeEach } from 'vitest'; + +vi.mock('./default_dispatcher.browser', () => { + return { default: {} }; +}); + + +vi.mock('./event_processor_factory', async importOriginal => { + const getForwardingEventProcessor = vi.fn().mockReturnValue({}); + const getBatchEventProcessor = vi.fn().mockImplementation(() => { + return {}; + }); + const getOpaqueBatchEventProcessor = vi.fn().mockImplementation(() => { + return {}; + }); + const original: any = await importOriginal(); + return { ...original, getBatchEventProcessor, getOpaqueBatchEventProcessor, getForwardingEventProcessor }; +}); + +vi.mock('../utils/cache/async_storage_cache.react_native', () => { + return { AsyncStorageCache: vi.fn() }; +}); + +vi.mock('../utils/cache/store', () => { + return { SyncPrefixStore: vi.fn(), AsyncPrefixStore: vi.fn() }; +}); + +vi.mock('@react-native-community/netinfo', () => { + return { NetInfoState: {}, addEventListener: vi.fn() }; +}); +let isAsyncStorageAvailable = true; + +await vi.hoisted(async () => { + await mockRequireNetInfo(); +}); + +async function mockRequireNetInfo() { + const { Module } = await import('module'); + const M: any = Module; + + M._load_original = M._load; + M._load = (uri: string, parent: string) => { + if (uri === '@react-native-async-storage/async-storage') { + if (isAsyncStorageAvailable) return {}; + throw new Error("Module not found: @react-native-async-storage/async-storage"); + } + return M._load_original(uri, parent); + }; +} + +import { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor_factory.react_native'; +import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; +import { EVENT_STORE_PREFIX, extractEventProcessor, getForwardingEventProcessor, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; +import { getOpaqueBatchEventProcessor } from './event_processor_factory'; +import { AsyncStore, AsyncPrefixStore, SyncStore, SyncPrefixStore } from '../utils/cache/store'; +import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; +import { MODULE_NOT_FOUND_REACT_NATIVE_ASYNC_STORAGE } from '../utils/import.react_native/@react-native-async-storage/async-storage'; + +describe('createForwardingEventProcessor', () => { + const mockGetForwardingEventProcessor = vi.mocked(getForwardingEventProcessor); + + beforeEach(() => { + mockGetForwardingEventProcessor.mockClear(); + }); + + it('returns forwarding event processor by calling getForwardingEventProcessor with the provided dispatcher', () => { + const eventDispatcher = { + dispatchEvent: vi.fn(), + }; + + const processor = extractEventProcessor(createForwardingEventProcessor(eventDispatcher)); + + expect(Object.is(processor, mockGetForwardingEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, eventDispatcher); + }); + + it('uses the browser default event dispatcher if none is provided', () => { + const processor = extractEventProcessor(createForwardingEventProcessor()); + + expect(Object.is(processor, mockGetForwardingEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetForwardingEventProcessor).toHaveBeenNthCalledWith(1, defaultEventDispatcher); + }); +}); + +describe('createBatchEventProcessor', () => { + const mockGetOpaqueBatchEventProcessor = vi.mocked(getOpaqueBatchEventProcessor); + const MockAsyncStorageCache = vi.mocked(AsyncStorageCache); + const MockSyncPrefixStore = vi.mocked(SyncPrefixStore); + const MockAsyncPrefixStore = vi.mocked(AsyncPrefixStore); + + beforeEach(() => { + mockGetOpaqueBatchEventProcessor.mockClear(); + MockAsyncStorageCache.mockClear(); + MockSyncPrefixStore.mockClear(); + MockAsyncPrefixStore.mockClear(); + }); + + it('uses AsyncStorageCache and AsyncPrefixStore to create eventStore if no eventStore is provided', () => { + const processor = createBatchEventProcessor({}); + + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + const eventStore = mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore; + expect(Object.is(eventStore, MockAsyncPrefixStore.mock.results[0].value)).toBe(true); + + const [cache, prefix, transformGet, transformSet] = MockAsyncPrefixStore.mock.calls[0]; + expect(Object.is(cache, MockAsyncStorageCache.mock.results[0].value)).toBe(true); + expect(prefix).toBe(EVENT_STORE_PREFIX); + + // transformGet and transformSet should be identity functions + expect(transformGet('value')).toBe('value'); + expect(transformSet('value')).toBe('value'); + }); + + it('should throw error if @react-native-async-storage/async-storage is not available', async () => { + isAsyncStorageAvailable = false; + const { AsyncStorageCache } = await vi.importActual< + typeof import('../utils/cache/async_storage_cache.react_native') + >('../utils/cache/async_storage_cache.react_native'); + + MockAsyncStorageCache.mockImplementationOnce(() => { + return new AsyncStorageCache(); + }); + + expect(() => createBatchEventProcessor({})).toThrowError( + MODULE_NOT_FOUND_REACT_NATIVE_ASYNC_STORAGE + ); + + isAsyncStorageAvailable = true; + }); + + it('should not throw error if eventStore is provided and @react-native-async-storage/async-storage is not available', async () => { + isAsyncStorageAvailable = false; + const eventStore = { + operation: 'sync', + } as SyncStore<string>; + + const { AsyncStorageCache } = await vi.importActual< + typeof import('../utils/cache/async_storage_cache.react_native') + >('../utils/cache/async_storage_cache.react_native'); + + MockAsyncStorageCache.mockImplementationOnce(() => { + return new AsyncStorageCache(); + }); + + expect(() => createBatchEventProcessor({ eventStore })).not.toThrow(); + + isAsyncStorageAvailable = true; + }); + + it('wraps the provided eventStore in a SyncPrefixStore if a SyncCache is provided as eventStore', () => { + const eventStore = { + operation: 'sync', + } as SyncStore<string>; + + const processor = createBatchEventProcessor({ eventStore }); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockSyncPrefixStore.mock.results[0].value); + const [cache, prefix, transformGet, transformSet] = MockSyncPrefixStore.mock.calls[0]; + + expect(cache).toBe(eventStore); + expect(prefix).toBe(EVENT_STORE_PREFIX); + + // transformGet and transformSet should be JSON.parse and JSON.stringify + expect(transformGet('{"value": 1}')).toEqual({ value: 1 }); + expect(transformSet({ value: 1 })).toBe('{"value":1}'); + }); + + it('wraps the provided eventStore in a AsyncPrefixStore if a AsyncCache is provided as eventStore', () => { + const eventStore = { + operation: 'async', + } as AsyncStore<string>; + + const processor = createBatchEventProcessor({ eventStore }); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventStore).toBe(MockAsyncPrefixStore.mock.results[0].value); + const [cache, prefix, transformGet, transformSet] = MockAsyncPrefixStore.mock.calls[0]; + + expect(cache).toBe(eventStore); + expect(prefix).toBe(EVENT_STORE_PREFIX); + + // transformGet and transformSet should be JSON.parse and JSON.stringify + expect(transformGet('{"value": 1}')).toEqual({ value: 1 }); + expect(transformSet({ value: 1 })).toBe('{"value":1}'); + }); + + it('uses the provided eventDispatcher', () => { + const eventDispatcher = { + dispatchEvent: vi.fn(), + }; + + const processor = createBatchEventProcessor({ eventDispatcher }); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(eventDispatcher); + }); + + it('uses the default browser event dispatcher if none is provided', () => { + const processor = createBatchEventProcessor({}); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(defaultEventDispatcher); + }); + + it('uses the provided closingEventDispatcher', () => { + const closingEventDispatcher = { + dispatchEvent: vi.fn(), + }; + + const processor = createBatchEventProcessor({ closingEventDispatcher }); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(closingEventDispatcher); + + const processor2 = createBatchEventProcessor({}); + expect(Object.is(processor2, mockGetOpaqueBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[1][0].closingEventDispatcher).toBe(undefined); + }); + + it('uses the provided flushInterval', () => { + const processor1 = createBatchEventProcessor({ flushInterval: 2000 }); + expect(Object.is(processor1, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].flushInterval).toBe(2000); + + const processor2 = createBatchEventProcessor({}); + expect(Object.is(processor2, mockGetOpaqueBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[1][0].flushInterval).toBe(undefined); + }); + + it('uses the provided batchSize', () => { + const processor1 = createBatchEventProcessor({ batchSize: 20 }); + expect(Object.is(processor1, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].batchSize).toBe(20); + + const processor2 = createBatchEventProcessor({}); + expect(Object.is(processor2, mockGetOpaqueBatchEventProcessor.mock.results[1].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[1][0].batchSize).toBe(undefined); + }); + + it('uses maxRetries value of 5', () => { + const processor = createBatchEventProcessor({}); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].retryOptions?.maxRetries).toBe(5); + }); + + it('uses the default failedEventRetryInterval', () => { + const processor = createBatchEventProcessor({}); + expect(Object.is(processor, mockGetOpaqueBatchEventProcessor.mock.results[0].value)).toBe(true); + expect(mockGetOpaqueBatchEventProcessor.mock.calls[0][0].failedEventRetryInterval).toBe(FAILED_EVENT_RETRY_INTERVAL); + }); +}); diff --git a/lib/event_processor/event_processor_factory.react_native.ts b/lib/event_processor/event_processor_factory.react_native.ts new file mode 100644 index 000000000..b46b594a4 --- /dev/null +++ b/lib/event_processor/event_processor_factory.react_native.ts @@ -0,0 +1,77 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { EventDispatcher } from './event_dispatcher/event_dispatcher'; +import defaultEventDispatcher from './event_dispatcher/default_dispatcher.browser'; +import { + BatchEventProcessorOptions, + getOpaqueBatchEventProcessor, + getPrefixEventStore, + OpaqueEventProcessor, + wrapEventProcessor, + getForwardingEventProcessor, +} from './event_processor_factory'; +import { EVENT_STORE_PREFIX, FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; +import { AsyncPrefixStore } from '../utils/cache/store'; +import { EventWithId } from './batch_event_processor'; +import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; +import { ReactNativeNetInfoEventProcessor } from './batch_event_processor.react_native'; + +export const DEFAULT_EVENT_BATCH_SIZE = 10; +export const DEFAULT_EVENT_FLUSH_INTERVAL = 1_000; + +export const createForwardingEventProcessor = ( + eventDispatcher: EventDispatcher = defaultEventDispatcher, +): OpaqueEventProcessor => { + return wrapEventProcessor(getForwardingEventProcessor(eventDispatcher)); +}; + +const identity = <T>(v: T): T => v; + +const getDefaultEventStore = () => { + const asyncStorageCache = new AsyncStorageCache<EventWithId>(); + + const eventStore = new AsyncPrefixStore<EventWithId, EventWithId>( + asyncStorageCache, + EVENT_STORE_PREFIX, + identity, + identity, + ); + + return eventStore; +} + +export const createBatchEventProcessor = ( + options: BatchEventProcessorOptions = {} +): OpaqueEventProcessor => { + const eventStore = options.eventStore ? getPrefixEventStore(options.eventStore) : getDefaultEventStore(); + + return getOpaqueBatchEventProcessor( + { + eventDispatcher: options.eventDispatcher || defaultEventDispatcher, + closingEventDispatcher: options.closingEventDispatcher, + flushInterval: options.flushInterval, + batchSize: options.batchSize, + defaultFlushInterval: DEFAULT_EVENT_FLUSH_INTERVAL, + defaultBatchSize: DEFAULT_EVENT_BATCH_SIZE, + retryOptions: { + maxRetries: 5, + }, + failedEventRetryInterval: FAILED_EVENT_RETRY_INTERVAL, + eventStore, + }, + ReactNativeNetInfoEventProcessor + ); +}; diff --git a/lib/event_processor/event_processor_factory.spec.ts b/lib/event_processor/event_processor_factory.spec.ts new file mode 100644 index 000000000..9aaa97f55 --- /dev/null +++ b/lib/event_processor/event_processor_factory.spec.ts @@ -0,0 +1,417 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, beforeEach, vi, MockInstance } from 'vitest'; +import { getBatchEventProcessor } from './event_processor_factory'; +import { BatchEventProcessor, BatchEventProcessorConfig, EventWithId,DEFAULT_MAX_BACKOFF, DEFAULT_MIN_BACKOFF } from './batch_event_processor'; +import { ExponentialBackoff, IntervalRepeater } from '../utils/repeater/repeater'; +import { getMockSyncCache } from '../tests/mock/mock_cache'; +import { LogLevel } from '../logging/logger'; + +vi.mock('./batch_event_processor'); +vi.mock('../utils/repeater/repeater'); + +const getMockEventDispatcher = () => { + return { + dispatchEvent: vi.fn(), + } +}; + +describe('getBatchEventProcessor', () => { + const MockBatchEventProcessor = vi.mocked(BatchEventProcessor); + const MockExponentialBackoff = vi.mocked(ExponentialBackoff); + const MockIntervalRepeater = vi.mocked(IntervalRepeater); + + beforeEach(() => { + MockBatchEventProcessor.mockReset(); + MockExponentialBackoff.mockReset(); + MockIntervalRepeater.mockReset(); + }); + + it('should throw an error if provided eventDispatcher is not valid', () => { + expect(() => getBatchEventProcessor({ + eventDispatcher: undefined as any, + defaultFlushInterval: 10000, + defaultBatchSize: 10, + })).toThrow('Invalid event dispatcher'); + + expect(() => getBatchEventProcessor({ + eventDispatcher: null as any, + defaultFlushInterval: 10000, + defaultBatchSize: 10, + })).toThrow('Invalid event dispatcher'); + + expect(() => getBatchEventProcessor({ + eventDispatcher: 'abc' as any, + defaultFlushInterval: 10000, + defaultBatchSize: 10, + })).toThrow('Invalid event dispatcher'); + + expect(() => getBatchEventProcessor({ + eventDispatcher: {} as any, + defaultFlushInterval: 10000, + defaultBatchSize: 10, + })).toThrow('Invalid event dispatcher'); + + expect(() => getBatchEventProcessor({ + eventDispatcher: { dispatchEvent: 'abc' } as any, + defaultFlushInterval: 10000, + defaultBatchSize: 10, + })).toThrow('Invalid event dispatcher'); + }); + + it('should throw and error if provided event store is invalid', () => { + expect(() => getBatchEventProcessor({ + eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 10000, + defaultBatchSize: 10, + eventStore: 'abc' as any, + })).toThrow('Invalid store'); + + expect(() => getBatchEventProcessor({ + eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 10000, + defaultBatchSize: 10, + eventStore: 123 as any, + })).toThrow('Invalid store'); + + expect(() => getBatchEventProcessor({ + eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 10000, + defaultBatchSize: 10, + eventStore: {} as any, + })).toThrow('Invalid store method set, Invalid store method get, Invalid store method remove, Invalid store method getKeys'); + + expect(() => getBatchEventProcessor({ + eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 10000, + defaultBatchSize: 10, + eventStore: { set: 'abc', get: 'abc', remove: 'abc', getKeys: 'abc' } as any, + })).toThrow('Invalid store method set, Invalid store method get, Invalid store method remove, Invalid store method getKeys'); + + const noop = () => {}; + + expect(() => getBatchEventProcessor({ + eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 10000, + defaultBatchSize: 10, + eventStore: { set: noop, get: 'abc' } as any, + })).toThrow('Invalid store method get, Invalid store method remove, Invalid store method getKeys'); + + expect(() => getBatchEventProcessor({ + eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 10000, + defaultBatchSize: 10, + eventStore: { set: noop, get: noop, remove: 'abc' } as any, + })).toThrow('Invalid store method remove, Invalid store method getKeys'); + + expect(() => getBatchEventProcessor({ + eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 10000, + defaultBatchSize: 10, + eventStore: { set: noop, get: noop, remove: noop, getKeys: 'abc' } as any, + })).toThrow('Invalid store method getKeys'); + }); + + it('returns an instane of BatchEventProcessor if no subclass constructor is provided', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 1000, + defaultBatchSize: 10, + }; + + const processor = getBatchEventProcessor(options); + + expect(processor instanceof BatchEventProcessor).toBe(true); + }); + + it('returns an instane of the provided subclass constructor', () => { + class CustomEventProcessor extends BatchEventProcessor { + constructor(opts: BatchEventProcessorConfig) { + super(opts); + } + } + + const options = { + eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 1000, + defaultBatchSize: 10, + }; + + const processor = getBatchEventProcessor(options, CustomEventProcessor); + + expect(processor instanceof CustomEventProcessor).toBe(true); + }); + + it('does not use retry if retryOptions is not provided', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 1000, + defaultBatchSize: 10, + }; + + const processor = getBatchEventProcessor(options); + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + expect(MockBatchEventProcessor.mock.calls[0][0].retryConfig).toBe(undefined); + }); + + it('uses the correct maxRetries value when retryOptions is provided', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 1000, + defaultBatchSize: 10, + retryOptions: { + maxRetries: 10, + }, + }; + + const processor = getBatchEventProcessor(options); + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + expect(MockBatchEventProcessor.mock.calls[0][0].retryConfig?.maxRetries).toBe(10); + }); + + it('uses exponential backoff with default parameters when retryOptions is provided without backoff values', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 1000, + defaultBatchSize: 10, + retryOptions: { maxRetries: 2 }, + }; + + const processor = getBatchEventProcessor(options); + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + + expect(MockBatchEventProcessor.mock.calls[0][0].retryConfig?.maxRetries).toBe(2); + + const backoffProvider = MockBatchEventProcessor.mock.calls[0][0].retryConfig?.backoffProvider; + expect(backoffProvider).not.toBe(undefined); + const backoff = backoffProvider?.(); + expect(Object.is(backoff, MockExponentialBackoff.mock.instances[0])).toBe(true); + expect(MockExponentialBackoff).toHaveBeenNthCalledWith(1, DEFAULT_MIN_BACKOFF, DEFAULT_MAX_BACKOFF, 500); + }); + + it('uses exponential backoff with provided backoff values in retryOptions', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 1000, + defaultBatchSize: 10, + retryOptions: { maxRetries: 2, minBackoff: 1000, maxBackoff: 2000 }, + }; + + const processor = getBatchEventProcessor(options); + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + + expect(MockBatchEventProcessor.mock.calls[0][0].retryConfig?.maxRetries).toBe(2); + + const backoffProvider = MockBatchEventProcessor.mock.calls[0][0].retryConfig?.backoffProvider; + + expect(backoffProvider).not.toBe(undefined); + const backoff = backoffProvider?.(); + expect(Object.is(backoff, MockExponentialBackoff.mock.instances[0])).toBe(true); + expect(MockExponentialBackoff).toHaveBeenNthCalledWith(1, 1000, 2000, 500); + }); + + it('uses a IntervalRepeater with default flush interval and adds a startup log if flushInterval is not provided', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + defaultFlushInterval: 12345, + defaultBatchSize: 77, + }; + + const processor = getBatchEventProcessor(options); + + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + const usedRepeater = MockBatchEventProcessor.mock.calls[0][0].dispatchRepeater; + expect(Object.is(usedRepeater, MockIntervalRepeater.mock.instances[0])).toBe(true); + expect(MockIntervalRepeater).toHaveBeenNthCalledWith(1, 12345); + + const startupLogs = MockBatchEventProcessor.mock.calls[0][0].startupLogs; + expect(startupLogs).toEqual(expect.arrayContaining([{ + level: LogLevel.Warn, + message: 'Invalid flushInterval %s, defaulting to %s', + params: [undefined, 12345], + }])); + }); + + it('uses default flush interval and adds a startup log if flushInterval is less than 1', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + flushInterval: -1, + defaultFlushInterval: 12345, + defaultBatchSize: 77, + }; + + const processor = getBatchEventProcessor(options); + + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + const usedRepeater = MockBatchEventProcessor.mock.calls[0][0].dispatchRepeater; + expect(Object.is(usedRepeater, MockIntervalRepeater.mock.instances[0])).toBe(true); + expect(MockIntervalRepeater).toHaveBeenNthCalledWith(1, 12345); + + const startupLogs = MockBatchEventProcessor.mock.calls[0][0].startupLogs; + expect(startupLogs).toEqual(expect.arrayContaining([{ + level: LogLevel.Warn, + message: 'Invalid flushInterval %s, defaulting to %s', + params: [-1, 12345], + }])); + }); + + it('uses a IntervalRepeater with provided flushInterval and adds no startup log if provided flushInterval is valid', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + flushInterval: 12345, + defaultFlushInterval: 1000, + defaultBatchSize: 77, + }; + + const processor = getBatchEventProcessor(options); + + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + const usedRepeater = MockBatchEventProcessor.mock.calls[0][0].dispatchRepeater; + expect(Object.is(usedRepeater, MockIntervalRepeater.mock.instances[0])).toBe(true); + expect(MockIntervalRepeater).toHaveBeenNthCalledWith(1, 12345); + + const startupLogs = MockBatchEventProcessor.mock.calls[0][0].startupLogs; + expect(startupLogs?.find((log) => log.message === 'Invalid flushInterval %s, defaulting to %s')).toBe(undefined); + }); + + + it('uses default batch size and adds a startup log if batchSize is not provided', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + defaultBatchSize: 77, + defaultFlushInterval: 12345, + }; + + const processor = getBatchEventProcessor(options); + + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + expect(MockBatchEventProcessor.mock.calls[0][0].batchSize).toBe(77); + + const startupLogs = MockBatchEventProcessor.mock.calls[0][0].startupLogs; + expect(startupLogs).toEqual(expect.arrayContaining([{ + level: LogLevel.Warn, + message: 'Invalid batchSize %s, defaulting to %s', + params: [undefined, 77], + }])); + }); + + it('uses default size and adds a startup log if provided batchSize is less than 1', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + batchSize: -1, + defaultBatchSize: 77, + defaultFlushInterval: 12345, + }; + + const processor = getBatchEventProcessor(options); + + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + expect(MockBatchEventProcessor.mock.calls[0][0].batchSize).toBe(77); + + const startupLogs = MockBatchEventProcessor.mock.calls[0][0].startupLogs; + expect(startupLogs).toEqual(expect.arrayContaining([{ + level: LogLevel.Warn, + message: 'Invalid batchSize %s, defaulting to %s', + params: [-1, 77], + }])); + }); + + it('does not use a failedEventRepeater if failedEventRetryInterval is not provided', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + defaultBatchSize: 77, + defaultFlushInterval: 12345, + }; + + const processor = getBatchEventProcessor(options); + + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + expect(MockBatchEventProcessor.mock.calls[0][0].failedEventRepeater).toBe(undefined); + }); + + it('uses a IntervalRepeater with provided failedEventRetryInterval as failedEventRepeater', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + failedEventRetryInterval: 12345, + defaultBatchSize: 77, + defaultFlushInterval: 12345, + }; + + const processor = getBatchEventProcessor(options); + + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + expect(Object.is(MockBatchEventProcessor.mock.calls[0][0].failedEventRepeater, MockIntervalRepeater.mock.instances[1])).toBe(true); + expect(MockIntervalRepeater).toHaveBeenNthCalledWith(2, 12345); + }); + + it('uses the provided eventDispatcher', () => { + const eventDispatcher = getMockEventDispatcher(); + const options = { + eventDispatcher, + defaultBatchSize: 77, + defaultFlushInterval: 12345, + }; + + const processor = getBatchEventProcessor(options); + + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + expect(MockBatchEventProcessor.mock.calls[0][0].eventDispatcher).toBe(eventDispatcher); + }); + + it('does not use any closingEventDispatcher if not provided', () => { + const options = { + eventDispatcher: getMockEventDispatcher(), + defaultBatchSize: 77, + defaultFlushInterval: 12345, + }; + + const processor = getBatchEventProcessor(options); + + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + expect(MockBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(undefined); + }); + + it('uses the provided closingEventDispatcher', () => { + const closingEventDispatcher = getMockEventDispatcher(); + const options = { + eventDispatcher: getMockEventDispatcher(), + closingEventDispatcher, + defaultBatchSize: 77, + defaultFlushInterval: 12345, + }; + + const processor = getBatchEventProcessor(options); + + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + expect(MockBatchEventProcessor.mock.calls[0][0].closingEventDispatcher).toBe(closingEventDispatcher); + }); + + it('uses the provided eventStore', () => { + const eventStore = getMockSyncCache<EventWithId>(); + const options = { + eventDispatcher: getMockEventDispatcher(), + eventStore, + defaultBatchSize: 77, + defaultFlushInterval: 12345, + }; + + const processor = getBatchEventProcessor(options); + + expect(Object.is(processor, MockBatchEventProcessor.mock.instances[0])).toBe(true); + expect(MockBatchEventProcessor.mock.calls[0][0].eventStore).toBe(eventStore); + }); +}); diff --git a/lib/event_processor/event_processor_factory.ts b/lib/event_processor/event_processor_factory.ts new file mode 100644 index 000000000..393ce436a --- /dev/null +++ b/lib/event_processor/event_processor_factory.ts @@ -0,0 +1,175 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LogLevel } from "../logging/logger"; +import { StartupLog } from "../service"; +import { ExponentialBackoff, IntervalRepeater } from "../utils/repeater/repeater"; +import { EventDispatcher } from "./event_dispatcher/event_dispatcher"; +import { EventProcessor } from "./event_processor"; +import { ForwardingEventProcessor } from "./forwarding_event_processor"; +import { BatchEventProcessor, DEFAULT_MAX_BACKOFF, DEFAULT_MIN_BACKOFF, EventWithId, RetryConfig } from "./batch_event_processor"; +import { AsyncPrefixStore, Store, SyncPrefixStore } from "../utils/cache/store"; +import { Maybe } from "../utils/type"; +import { validateStore } from "../utils/cache/store_validator"; + +export const INVALID_EVENT_DISPATCHER = 'Invalid event dispatcher'; + +export const FAILED_EVENT_RETRY_INTERVAL = 20 * 1000; +export const EVENT_STORE_PREFIX = 'optly_event:'; + +export const getPrefixEventStore = (store: Store<string>): Store<EventWithId> => { + if (store.operation === 'async') { + return new AsyncPrefixStore<string, EventWithId>( + store, + EVENT_STORE_PREFIX, + JSON.parse, + JSON.stringify, + ); + } else { + return new SyncPrefixStore<string, EventWithId>( + store, + EVENT_STORE_PREFIX, + JSON.parse, + JSON.stringify, + ); + } +}; + +const eventProcessorSymbol: unique symbol = Symbol(); + +export type OpaqueEventProcessor = { + [eventProcessorSymbol]: unknown; +}; + +export type BatchEventProcessorOptions = { + eventDispatcher?: EventDispatcher; + closingEventDispatcher?: EventDispatcher; + flushInterval?: number; + batchSize?: number; + eventStore?: Store<string>; +}; + +export type BatchEventProcessorFactoryOptions = Omit<BatchEventProcessorOptions, 'eventDispatcher' | 'eventStore' > & { + eventDispatcher: EventDispatcher; + closingEventDispatcher?: EventDispatcher; + failedEventRetryInterval?: number; + defaultFlushInterval: number; + defaultBatchSize: number; + eventStore?: Store<EventWithId>; + retryOptions?: { + maxRetries: number; + minBackoff?: number; + maxBackoff?: number; + }; +} + +export const validateEventDispatcher = (eventDispatcher: EventDispatcher): void => { + if (!eventDispatcher || typeof eventDispatcher !== 'object' || typeof eventDispatcher.dispatchEvent !== 'function') { + throw new Error(INVALID_EVENT_DISPATCHER); + } +} + +export const getBatchEventProcessor = ( + options: BatchEventProcessorFactoryOptions, + EventProcessorConstructor: typeof BatchEventProcessor = BatchEventProcessor + ): EventProcessor => { + const { eventDispatcher, closingEventDispatcher, retryOptions, eventStore } = options; + + validateEventDispatcher(eventDispatcher); + if (closingEventDispatcher) { + validateEventDispatcher(closingEventDispatcher); + } + + if (eventStore) { + validateStore(eventStore); + } + + const retryConfig: RetryConfig | undefined = retryOptions ? { + maxRetries: retryOptions.maxRetries, + backoffProvider: () => { + const minBackoff = retryOptions?.minBackoff ?? DEFAULT_MIN_BACKOFF; + const maxBackoff = retryOptions?.maxBackoff ?? DEFAULT_MAX_BACKOFF; + return new ExponentialBackoff(minBackoff, maxBackoff, 500); + } + } : undefined; + + const startupLogs: StartupLog[] = []; + + const { defaultFlushInterval, defaultBatchSize } = options; + + let flushInterval = defaultFlushInterval; + if (options.flushInterval === undefined || options.flushInterval <= 0) { + startupLogs.push({ + level: LogLevel.Warn, + message: 'Invalid flushInterval %s, defaulting to %s', + params: [options.flushInterval, defaultFlushInterval], + }); + } else { + flushInterval = options.flushInterval; + } + + let batchSize = defaultBatchSize; + if (options.batchSize === undefined || options.batchSize <= 0) { + startupLogs.push({ + level: LogLevel.Warn, + message: 'Invalid batchSize %s, defaulting to %s', + params: [options.batchSize, defaultBatchSize], + }); + } else { + batchSize = options.batchSize; + } + + const dispatchRepeater = new IntervalRepeater(flushInterval); + const failedEventRepeater = options.failedEventRetryInterval ? + new IntervalRepeater(options.failedEventRetryInterval) : undefined; + + return new EventProcessorConstructor({ + eventDispatcher, + closingEventDispatcher, + dispatchRepeater, + failedEventRepeater, + retryConfig, + batchSize, + eventStore, + startupLogs, + }); +} + +export const wrapEventProcessor = (eventProcessor: EventProcessor): OpaqueEventProcessor => { + return { + [eventProcessorSymbol]: eventProcessor, + }; +} + +export const getOpaqueBatchEventProcessor = ( + options: BatchEventProcessorFactoryOptions, + EventProcessorConstructor: typeof BatchEventProcessor = BatchEventProcessor +): OpaqueEventProcessor => { + return wrapEventProcessor(getBatchEventProcessor(options, EventProcessorConstructor)); +} + +export const extractEventProcessor = (eventProcessor: Maybe<OpaqueEventProcessor>): Maybe<EventProcessor> => { + if (!eventProcessor || typeof eventProcessor !== 'object') { + return undefined; + } + return eventProcessor[eventProcessorSymbol] as Maybe<EventProcessor>; +} + + +export function getForwardingEventProcessor(dispatcher: EventDispatcher): EventProcessor { + validateEventDispatcher(dispatcher); + return new ForwardingEventProcessor(dispatcher); +} diff --git a/lib/event_processor/event_processor_factory.universal.ts b/lib/event_processor/event_processor_factory.universal.ts new file mode 100644 index 000000000..0a3b2ec56 --- /dev/null +++ b/lib/event_processor/event_processor_factory.universal.ts @@ -0,0 +1,61 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getForwardingEventProcessor } from './event_processor_factory'; +import { EventDispatcher } from './event_dispatcher/event_dispatcher'; + +import { + getOpaqueBatchEventProcessor, + BatchEventProcessorOptions, + OpaqueEventProcessor, + wrapEventProcessor, + getPrefixEventStore, +} from './event_processor_factory'; + +export const DEFAULT_EVENT_BATCH_SIZE = 10; +export const DEFAULT_EVENT_FLUSH_INTERVAL = 1_000; + +import { FAILED_EVENT_RETRY_INTERVAL } from './event_processor_factory'; + +export const createForwardingEventProcessor = ( + eventDispatcher: EventDispatcher +): OpaqueEventProcessor => { + return wrapEventProcessor(getForwardingEventProcessor(eventDispatcher)); +}; + +export type UniversalBatchEventProcessorOptions = Omit<BatchEventProcessorOptions, 'eventDispatcher'> & { + eventDispatcher: EventDispatcher; +} + +export const createBatchEventProcessor = ( + options: UniversalBatchEventProcessorOptions +): OpaqueEventProcessor => { + const eventStore = options.eventStore ? getPrefixEventStore(options.eventStore) : undefined; + + return getOpaqueBatchEventProcessor({ + eventDispatcher: options.eventDispatcher, + closingEventDispatcher: options.closingEventDispatcher, + flushInterval: options.flushInterval, + batchSize: options.batchSize, + defaultFlushInterval: DEFAULT_EVENT_FLUSH_INTERVAL, + defaultBatchSize: DEFAULT_EVENT_BATCH_SIZE, + retryOptions: { + maxRetries: 5, + }, + failedEventRetryInterval: FAILED_EVENT_RETRY_INTERVAL, + eventStore: eventStore, + }); +}; diff --git a/lib/event_processor/forwarding_event_processor.spec.ts b/lib/event_processor/forwarding_event_processor.spec.ts new file mode 100644 index 000000000..65d571cb9 --- /dev/null +++ b/lib/event_processor/forwarding_event_processor.spec.ts @@ -0,0 +1,120 @@ +/** + * Copyright 2021, 2024-2025 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { expect, describe, it, vi } from 'vitest'; + +import { EventDispatcher } from './event_dispatcher/event_dispatcher'; +import { buildLogEvent, makeEventBatch } from './event_builder/log_event'; +import { createImpressionEvent } from '../tests/mock/create_event'; +import { ServiceState } from '../service'; +import { ForwardingEventProcessor } from './forwarding_event_processor'; + +const getMockEventDispatcher = (): EventDispatcher => { + return { + dispatchEvent: vi.fn().mockResolvedValue({ statusCode: 200 }), + }; +}; + +describe('ForwardingEventProcessor', () => { + it('should resolve onRunning() when start is called', async () => { + const dispatcher = getMockEventDispatcher(); + + const processor = new ForwardingEventProcessor(dispatcher); + + processor.start(); + await expect(processor.onRunning()).resolves.not.toThrow(); + }); + + it('should dispatch event immediately when process is called', async() => { + const dispatcher = getMockEventDispatcher(); + const mockDispatch = vi.mocked(dispatcher.dispatchEvent); + + const processor = new ForwardingEventProcessor(dispatcher); + + processor.start(); + await processor.onRunning(); + + const event = createImpressionEvent(); + processor.process(event); + expect(dispatcher.dispatchEvent).toHaveBeenCalledOnce(); + const data = mockDispatch.mock.calls[0][0].params; + expect(data).toEqual(makeEventBatch([event])); + }); + + it('should emit dispatch event when event is dispatched', async() => { + const dispatcher = getMockEventDispatcher(); + + const processor = new ForwardingEventProcessor(dispatcher); + + processor.start(); + await processor.onRunning(); + + const listener = vi.fn(); + processor.onDispatch(listener); + + const event = createImpressionEvent(); + processor.process(event); + expect(dispatcher.dispatchEvent).toHaveBeenCalledOnce(); + expect(dispatcher.dispatchEvent).toHaveBeenCalledWith(buildLogEvent([event])); + expect(listener).toHaveBeenCalledOnce(); + expect(listener).toHaveBeenCalledWith(buildLogEvent([event])); + }); + + it('should remove dispatch listener when the function returned from onDispatch is called', async() => { + const dispatcher = getMockEventDispatcher(); + + const processor = new ForwardingEventProcessor(dispatcher); + + processor.start(); + await processor.onRunning(); + + const listener = vi.fn(); + const unsub = processor.onDispatch(listener); + + let event = createImpressionEvent(); + processor.process(event); + expect(dispatcher.dispatchEvent).toHaveBeenCalledOnce(); + expect(dispatcher.dispatchEvent).toHaveBeenCalledWith(buildLogEvent([event])); + expect(listener).toHaveBeenCalledOnce(); + expect(listener).toHaveBeenCalledWith(buildLogEvent([event])); + + unsub(); + event = createImpressionEvent('id-a'); + processor.process(event); + expect(listener).toHaveBeenCalledOnce(); + }); + + it('should resolve onTerminated promise when stop is called', async () => { + const dispatcher = getMockEventDispatcher(); + const processor = new ForwardingEventProcessor(dispatcher); + processor.start(); + await processor.onRunning(); + + expect(processor.getState()).toEqual(ServiceState.Running); + + processor.stop(); + await expect(processor.onTerminated()).resolves.not.toThrow(); + }); + + it('should reject onRunning promise when stop is called in New state', async () => { + const dispatcher = getMockEventDispatcher(); + const processor = new ForwardingEventProcessor(dispatcher); + + expect(processor.getState()).toEqual(ServiceState.New); + + processor.stop(); + await expect(processor.onRunning()).rejects.toThrow(); + }); + }); diff --git a/lib/event_processor/forwarding_event_processor.ts b/lib/event_processor/forwarding_event_processor.ts new file mode 100644 index 000000000..f578992c7 --- /dev/null +++ b/lib/event_processor/forwarding_event_processor.ts @@ -0,0 +1,76 @@ +/** + * Copyright 2021-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +import { LogEvent } from './event_dispatcher/event_dispatcher'; +import { EventProcessor, ProcessableEvent } from './event_processor'; + +import { EventDispatcher } from '../shared_types'; +import { buildLogEvent } from './event_builder/log_event'; +import { BaseService, ServiceState } from '../service'; +import { EventEmitter } from '../utils/event_emitter/event_emitter'; +import { Consumer, Fn } from '../utils/type'; +import { SERVICE_STOPPED_BEFORE_RUNNING } from '../service'; +import { sprintf } from '../utils/fns'; + +export class ForwardingEventProcessor extends BaseService implements EventProcessor { + private dispatcher: EventDispatcher; + private eventEmitter: EventEmitter<{ dispatch: LogEvent }>; + + constructor(dispatcher: EventDispatcher) { + super(); + this.dispatcher = dispatcher; + this.eventEmitter = new EventEmitter(); + } + + process(event: ProcessableEvent): Promise<unknown> { + const formattedEvent = buildLogEvent([event]); + const res = this.dispatcher.dispatchEvent(formattedEvent); + this.eventEmitter.emit('dispatch', formattedEvent); + return res; + } + + start(): void { + if (!this.isNew()) { + return; + } + this.state = ServiceState.Running; + this.startPromise.resolve(); + } + + stop(): void { + if (this.isDone()) { + return; + } + + if (this.isNew()) { + this.startPromise.reject(new Error( + sprintf(SERVICE_STOPPED_BEFORE_RUNNING, 'ForwardingEventProcessor')) + ); + } + + this.state = ServiceState.Terminated; + this.stopPromise.resolve(); + } + + onDispatch(handler: Consumer<LogEvent>): Fn { + return this.eventEmitter.on('dispatch', handler); + } + + flushImmediately(): Promise<unknown> { + return Promise.resolve(); + } +} diff --git a/lib/export_types.ts b/lib/export_types.ts new file mode 100644 index 000000000..b620fbb8e --- /dev/null +++ b/lib/export_types.ts @@ -0,0 +1,105 @@ +/** + * Copyright 2022-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// config manager related types +export type { + StaticConfigManagerConfig, + PollingConfigManagerConfig, + OpaqueConfigManager, +} from './project_config/config_manager_factory'; + +// event processor related types +export type { + LogEvent, + EventDispatcherResponse, + EventDispatcher, +} from './event_processor/event_dispatcher/event_dispatcher'; + +export type { + BatchEventProcessorOptions, + OpaqueEventProcessor, +} from './event_processor/event_processor_factory'; + +// Odp manager related types +export type { + OdpManagerOptions, + OpaqueOdpManager, +} from './odp/odp_manager_factory'; + +export type { + UserAgentParser, +} from './odp/ua_parser/user_agent_parser'; + +// Vuid manager related types +export type { + VuidManagerOptions, + OpaqueVuidManager, +} from './vuid/vuid_manager_factory'; + +// Logger related types +export type { + LogHandler, +} from './logging/logger'; + +export type { + OpaqueLevelPreset, + LoggerConfig, + OpaqueLogger, +} from './logging/logger_factory'; + +// Error related types +export type { ErrorHandler } from './error/error_handler'; +export type { OpaqueErrorNotifier } from './error/error_notifier_factory'; + +export type { SyncCache, AsyncCache, Cache, SyncCacheWithRemove, AsyncCacheWithRemove, CacheWithRemove } from './utils/cache/cache'; +export type { SyncStore, AsyncStore, Store } from './utils/cache/store' + +export type { + NotificationType, + NotificationPayload, + ActivateListenerPayload as ActivateNotificationPayload, + DecisionListenerPayload as DecisionNotificationPayload, + TrackListenerPayload as TrackNotificationPayload, + LogEventListenerPayload as LogEventNotificationPayload, + OptimizelyConfigUpdateListenerPayload as OptimizelyConfigUpdateNotificationPayload, +} from './notification_center/type'; + +export type { + UserAttributeValue, + UserAttributes, + OptimizelyConfig, + FeatureVariableValue, + OptimizelyVariable, + OptimizelyVariation, + OptimizelyExperiment, + OptimizelyFeature, + OptimizelyDecisionContext, + OptimizelyForcedDecision, + EventTags, + Event, + DatafileOptions, + UserProfileService, + UserProfile, + ListenerPayload, + OptimizelyDecision, + OptimizelyUserContext, + Config, + Client, + ActivateListenerPayload, + TrackListenerPayload, + NotificationCenter, + OptimizelySegmentOption, +} from './shared_types'; diff --git a/lib/feature_toggle.ts b/lib/feature_toggle.ts new file mode 100644 index 000000000..54da8afff --- /dev/null +++ b/lib/feature_toggle.ts @@ -0,0 +1,36 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ + +/** + * This module contains feature flags that control the availability of features under development. + * Each flag represents a feature that is not yet ready for production release. These flags + * serve multiple purposes in our development workflow: + * + * When a new feature is in development, it can be safely merged into the main branch + * while remaining disabled in production. This allows continuous integration without + * affecting the stability of production releases. The feature code will be automatically + * removed in production builds through tree-shaking when the flag is disabled. + * + * During development and testing, these flags can be easily mocked to enable/disable + * specific features. Once a feature is complete and ready for release, its corresponding + * flag and all associated checks can be removed from the codebase. + */ + +export const holdout = () => false as const; + +export type IfActive<T extends () => boolean, Y, N = unknown> = ReturnType<T> extends true ? Y : N; diff --git a/lib/index.browser.tests.js b/lib/index.browser.tests.js new file mode 100644 index 000000000..645739cdb --- /dev/null +++ b/lib/index.browser.tests.js @@ -0,0 +1,353 @@ +/** + * Copyright 2016-2020, 2022-2025 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { assert } from 'chai'; +import sinon from 'sinon'; +import Optimizely from './optimizely'; +import testData from './tests/test_data'; +import packageJSON from '../package.json'; +import * as optimizelyFactory from './index.browser'; +import configValidator from './utils/config_validator'; +import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; +import { createProjectConfig } from './project_config/project_config'; +import { wrapConfigManager } from './project_config/config_manager_factory'; +import { wrapLogger } from './logging/logger_factory'; + +class MockLocalStorage { + store = {}; + + constructor() {} + + getItem(key) { + return this.store[key]; + } + + setItem(key, value) { + this.store[key] = value.toString(); + } + + clear() { + this.store = {}; + } + + removeItem(key) { + delete this.store[key]; + } +} + +if (!global.window) { + try { + global.window = { + localStorage: new MockLocalStorage(), + }; + } catch (e) { + console.error("Unable to overwrite global.window"); + } +} + +const pause = timeoutMilliseconds => { + return new Promise(resolve => setTimeout(resolve, timeoutMilliseconds)); +}; + +var getLogger = () => ({ + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + child: () => getLogger(), + setName: () => {}, +}) + +describe('javascript-sdk (Browser)', function() { + var clock; + + before(() => { + window.addEventListener = () => {}; + // sinon.spy(window, 'addEventListener') + }); + + beforeEach(function() { + clock = sinon.useFakeTimers(new Date()); + }); + + afterEach(function() { + clock.restore(); + }); + + describe('APIs', function() { + // it('should expose logger, errorHandler, eventDispatcher and enums', function() { + // assert.isDefined(optimizelyFactory.logging); + // assert.isDefined(optimizelyFactory.logging.createLogger); + // assert.isDefined(optimizelyFactory.logging.createNoOpLogger); + // assert.isDefined(optimizelyFactory.errorHandler); + // assert.isDefined(optimizelyFactory.eventDispatcher); + // assert.isDefined(optimizelyFactory.enums); + // }); + + describe('createInstance', function() { + var fakeErrorHandler = { handleError: function() {} }; + var fakeEventDispatcher = { dispatchEvent: function() {} }; + var mockLogger; + + beforeEach(function() { + mockLogger = getLogger(); + sinon.stub(mockLogger, 'error'); + + global.XMLHttpRequest = sinon.useFakeXMLHttpRequest(); + }); + + afterEach(function() { + mockLogger.error.restore(); + delete global.XMLHttpRequest; + }); + + + // TODO: pending event handling should be part of the event processor + // logic, not the dispatcher. Refactor accordingly. + // it('should invoke resendPendingEvents at most once', function() { + // var optlyInstance = optimizelyFactory.createInstance({ + // projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), + // errorHandler: fakeErrorHandler, + // logger: silentLogger, + // }); + + // sinon.assert.calledOnce(LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents); + + // optlyInstance = optimizelyFactory.createInstance({ + // projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), + // errorHandler: fakeErrorHandler, + // logger: silentLogger, + // }); + // optlyInstance.onReady().catch(function() {}); + + // sinon.assert.calledOnce(LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents); + // }); + + // it('should not throw if the provided config is not valid', function() { + // configValidator.validate.throws(new Error('INVALID_CONFIG_OR_SOMETHING')); + // assert.doesNotThrow(function() { + // var optlyInstance = optimizelyFactory.createInstance({ + // projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), + // logger: wrapLogger(mockLogger), + // }); + // }); + // }); + + it('should create an instance of optimizely', function() { + var optlyInstance = optimizelyFactory.createInstance({ + projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), + errorHandler: fakeErrorHandler, + logger: wrapLogger(mockLogger), + }); + + assert.instanceOf(optlyInstance, Optimizely); + assert.equal(optlyInstance.clientVersion, '6.2.0'); + }); + + it('should set the JavaScript client engine and version', function() { + var optlyInstance = optimizelyFactory.createInstance({ + projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), + errorHandler: fakeErrorHandler, + logger: wrapLogger(mockLogger), + }); + + assert.equal('javascript-sdk', optlyInstance.clientEngine); + assert.equal(packageJSON.version, optlyInstance.clientVersion); + }); + + it('should allow passing of "react-sdk" as the clientEngine', function() { + var optlyInstance = optimizelyFactory.createInstance({ + clientEngine: 'react-sdk', + projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), + errorHandler: fakeErrorHandler, + logger: wrapLogger(mockLogger), + }); + assert.equal('react-sdk', optlyInstance.clientEngine); + }); + + it('should activate with provided event dispatcher', function() { + var optlyInstance = optimizelyFactory.createInstance({ + projectConfigManager: wrapConfigManager(getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + })), + logger: wrapLogger(mockLogger), + }); + var activate = optlyInstance.activate('testExperiment', 'testUser'); + assert.strictEqual(activate, 'control'); + }); + + it('should be able to set and get a forced variation', function() { + var optlyInstance = optimizelyFactory.createInstance({ + projectConfigManager: wrapConfigManager(getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + })), + logger: wrapLogger(mockLogger), + }); + + var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); + assert.strictEqual(didSetVariation, true); + + var variation = optlyInstance.getForcedVariation('testExperiment', 'testUser'); + assert.strictEqual(variation, 'control'); + }); + + it('should be able to set and unset a forced variation', function() { + var optlyInstance = optimizelyFactory.createInstance({ + projectConfigManager: wrapConfigManager(getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + })), + logger: wrapLogger(mockLogger), + }); + + var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); + assert.strictEqual(didSetVariation, true); + + var variation = optlyInstance.getForcedVariation('testExperiment', 'testUser'); + assert.strictEqual(variation, 'control'); + + var didSetVariation2 = optlyInstance.setForcedVariation('testExperiment', 'testUser', null); + assert.strictEqual(didSetVariation2, true); + + var variation2 = optlyInstance.getForcedVariation('testExperiment', 'testUser'); + assert.strictEqual(variation2, null); + }); + + it('should be able to set multiple experiments for one user', function() { + var optlyInstance = optimizelyFactory.createInstance({ + projectConfigManager: wrapConfigManager(getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + })), + logger: wrapLogger(mockLogger), + }); + + var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); + assert.strictEqual(didSetVariation, true); + + var didSetVariation2 = optlyInstance.setForcedVariation( + 'testExperimentLaunched', + 'testUser', + 'controlLaunched' + ); + assert.strictEqual(didSetVariation2, true); + + var variation = optlyInstance.getForcedVariation('testExperiment', 'testUser'); + assert.strictEqual(variation, 'control'); + + var variation2 = optlyInstance.getForcedVariation('testExperimentLaunched', 'testUser'); + assert.strictEqual(variation2, 'controlLaunched'); + }); + + it('should be able to set multiple experiments for one user, and unset one', function() { + var optlyInstance = optimizelyFactory.createInstance({ + projectConfigManager: wrapConfigManager(getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + })), + logger: wrapLogger(mockLogger), + }); + + var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); + assert.strictEqual(didSetVariation, true); + + var didSetVariation2 = optlyInstance.setForcedVariation( + 'testExperimentLaunched', + 'testUser', + 'controlLaunched' + ); + assert.strictEqual(didSetVariation2, true); + + var didSetVariation2 = optlyInstance.setForcedVariation('testExperimentLaunched', 'testUser', null); + assert.strictEqual(didSetVariation2, true); + + var variation = optlyInstance.getForcedVariation('testExperiment', 'testUser'); + assert.strictEqual(variation, 'control'); + + var variation2 = optlyInstance.getForcedVariation('testExperimentLaunched', 'testUser'); + assert.strictEqual(variation2, null); + }); + + it('should be able to set multiple experiments for one user, and reset one', function() { + var optlyInstance = optimizelyFactory.createInstance({ + projectConfigManager: wrapConfigManager(getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + })), + logger: wrapLogger(mockLogger), + }); + + var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); + assert.strictEqual(didSetVariation, true); + + var didSetVariation2 = optlyInstance.setForcedVariation( + 'testExperimentLaunched', + 'testUser', + 'controlLaunched' + ); + assert.strictEqual(didSetVariation2, true); + + var didSetVariation2 = optlyInstance.setForcedVariation( + 'testExperimentLaunched', + 'testUser', + 'variationLaunched' + ); + assert.strictEqual(didSetVariation2, true); + + var variation = optlyInstance.getForcedVariation('testExperiment', 'testUser'); + assert.strictEqual(variation, 'control'); + + var variation2 = optlyInstance.getForcedVariation('testExperimentLaunched', 'testUser'); + assert.strictEqual(variation2, 'variationLaunched'); + }); + + it('should override bucketing when setForcedVariation is called', function() { + var optlyInstance = optimizelyFactory.createInstance({ + projectConfigManager: wrapConfigManager(getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + })), + logger: wrapLogger(mockLogger), + }); + + var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); + assert.strictEqual(didSetVariation, true); + + var variation = optlyInstance.getVariation('testExperiment', 'testUser'); + assert.strictEqual(variation, 'control'); + + var didSetVariation2 = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'variation'); + assert.strictEqual(didSetVariation2, true); + + var variation = optlyInstance.getVariation('testExperiment', 'testUser'); + assert.strictEqual(variation, 'variation'); + }); + + it('should override bucketing when setForcedVariation is called for a not running experiment', function() { + var optlyInstance = optimizelyFactory.createInstance({ + projectConfigManager: wrapConfigManager(getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + })), + logger: wrapLogger(mockLogger), + }); + + var didSetVariation = optlyInstance.setForcedVariation( + 'testExperimentNotRunning', + 'testUser', + 'controlNotRunning' + ); + assert.strictEqual(didSetVariation, true); + + var variation = optlyInstance.getVariation('testExperimentNotRunning', 'testUser'); + assert.strictEqual(variation, null); + }); + }); + }); +}); diff --git a/lib/index.browser.ts b/lib/index.browser.ts new file mode 100644 index 000000000..0f644a844 --- /dev/null +++ b/lib/index.browser.ts @@ -0,0 +1,64 @@ +/** + * Copyright 2016-2017, 2019-2025 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Config, Client } from './shared_types'; +import sendBeaconEventDispatcher from './event_processor/event_dispatcher/send_beacon_dispatcher.browser'; +import { getOptimizelyInstance } from './client_factory'; +import { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; +import { JAVASCRIPT_CLIENT_ENGINE } from './utils/enums'; +import { BrowserRequestHandler } from './utils/http_request_handler/request_handler.browser'; + +/** + * Creates an instance of the Optimizely class + * @param {Config} config + * @return {Client|null} the Optimizely client object + * null on error + */ +export const createInstance = function(config: Config): Client { + const client = getOptimizelyInstance({ + ...config, + requestHandler: new BrowserRequestHandler(), + }); + + if (client) { + const unloadEvent = 'onpagehide' in window ? 'pagehide' : 'unload'; + window.addEventListener( + unloadEvent, + () => { + client.flushImmediately(); + }, + ); + } + + return client; +}; + +export const getSendBeaconEventDispatcher = (): EventDispatcher | undefined => { + return sendBeaconEventDispatcher; +}; + +export { default as eventDispatcher } from './event_processor/event_dispatcher/default_dispatcher.browser'; + +export { createPollingProjectConfigManager } from './project_config/config_manager_factory.browser'; +export { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor/event_processor_factory.browser'; + +export { createOdpManager } from './odp/odp_manager_factory.browser'; +export { createVuidManager } from './vuid/vuid_manager_factory.browser'; + +export * from './common_exports'; + +export * from './export_types'; + +export const clientEngine: string = JAVASCRIPT_CLIENT_ENGINE; diff --git a/packages/optimizely-sdk/lib/index.browser.umdtests.js b/lib/index.browser.umdtests.js similarity index 98% rename from packages/optimizely-sdk/lib/index.browser.umdtests.js rename to lib/index.browser.umdtests.js index f8be89aca..a13f5046b 100644 --- a/packages/optimizely-sdk/lib/index.browser.umdtests.js +++ b/lib/index.browser.umdtests.js @@ -23,6 +23,7 @@ import Optimizely from './optimizely'; import testData from './tests/test_data'; import packageJSON from '../package.json'; import eventDispatcher from './plugins/event_dispatcher/index.browser'; +import { INVALID_CONFIG_OR_SOMETHING } from './exception_messages'; describe('javascript-sdk', function() { describe('APIs', function() { @@ -92,7 +93,7 @@ describe('javascript-sdk', function() { }); it('should not throw if the provided config is not valid', function() { - configValidator.validate.throws(new Error('Invalid config or something')); + configValidator.validate.throws(new Error(INVALID_CONFIG_OR_SOMETHING)); assert.doesNotThrow(function() { var optlyInstance = window.optimizelySdk.createInstance({ datafile: {}, diff --git a/lib/index.node.tests.js b/lib/index.node.tests.js new file mode 100644 index 000000000..b52a0f15c --- /dev/null +++ b/lib/index.node.tests.js @@ -0,0 +1,213 @@ +/** + * Copyright 2016-2020, 2022-2025 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { assert } from 'chai'; +import sinon from 'sinon'; + +import Optimizely from './optimizely'; +import testData from './tests/test_data'; +import * as optimizelyFactory from './index.node'; +import configValidator from './utils/config_validator'; +import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; +import { wrapConfigManager } from './project_config/config_manager_factory'; +import { wrapLogger } from './logging/logger_factory'; + +var createLogger = () => ({ + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + child: () => createLogger(), +}) + +describe('optimizelyFactory', function() { + describe('APIs', function() { + // it('should expose logger, errorHandler, eventDispatcher and enums', function() { + // assert.isDefined(optimizelyFactory.logging); + // assert.isDefined(optimizelyFactory.logging.createLogger); + // assert.isDefined(optimizelyFactory.logging.createNoOpLogger); + // assert.isDefined(optimizelyFactory.errorHandler); + // assert.isDefined(optimizelyFactory.eventDispatcher); + // assert.isDefined(optimizelyFactory.enums); + // }); + + describe('createInstance', function() { + var fakeErrorHandler = { handleError: function() {} }; + var fakeEventDispatcher = { dispatchEvent: function() {} }; + var fakeLogger = createLogger(); + + beforeEach(function() { + sinon.stub(fakeLogger, 'error'); + }); + + afterEach(function() { + fakeLogger.error.restore(); + }); + + // it('should not throw if the provided config is not valid and log an error if logger is passed in', function() { + // configValidator.validate.throws(new Error('Invalid config or something')); + // var localLogger = loggerPlugin.createLogger({ logLevel: enums.LOG_LEVEL.INFO }); + // assert.doesNotThrow(function() { + // var optlyInstance = optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager(), + // logger: localLogger, + // }); + // }); + // sinon.assert.calledWith(localLogger.log, enums.LOG_LEVEL.ERROR); + // }); + + // it('should not throw if the provided config is not valid', function() { + // configValidator.validate.throws(new Error('INVALID_CONFIG_OR_SOMETHING')); + // assert.doesNotThrow(function() { + // var optlyInstance = optimizelyFactory.createInstance({ + // projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), + // logger: wrapLogger(fakeLogger), + // }); + // }); + // // sinon.assert.calledOnce(fakeLogger.error); + // }); + + it('should create an instance of optimizely', function() { + var optlyInstance = optimizelyFactory.createInstance({ + projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // logger: fakeLogger, + }); + + assert.instanceOf(optlyInstance, Optimizely); + assert.equal(optlyInstance.clientVersion, '6.2.0'); + }); + // TODO: user will create and inject an event processor + // these tests will be refactored accordingly + // describe('event processor configuration', function() { + // var eventProcessorSpy; + // beforeEach(function() { + // eventProcessorSpy = sinon.stub(eventProcessor, 'createEventProcessor').callThrough(); + // }); + + // afterEach(function() { + // eventProcessor.createEventProcessor.restore(); + // }); + + // it('should ignore invalid event flush interval and use default instead', function() { + // optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + // }), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // logger: fakeLogger, + // eventFlushInterval: ['invalid', 'flush', 'interval'], + // }); + // sinon.assert.calledWithExactly( + // eventProcessorSpy, + // sinon.match({ + // flushInterval: 30000, + // }) + // ); + // }); + + // it('should use default event flush interval when none is provided', function() { + // optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + // }), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // logger: fakeLogger, + // }); + // sinon.assert.calledWithExactly( + // eventProcessorSpy, + // sinon.match({ + // flushInterval: 30000, + // }) + // ); + // }); + + // it('should use provided event flush interval when valid', function() { + // optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + // }), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // logger: fakeLogger, + // eventFlushInterval: 10000, + // }); + // sinon.assert.calledWithExactly( + // eventProcessorSpy, + // sinon.match({ + // flushInterval: 10000, + // }) + // ); + // }); + + // it('should ignore invalid event batch size and use default instead', function() { + // optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + // }), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // logger: fakeLogger, + // eventBatchSize: null, + // }); + // sinon.assert.calledWithExactly( + // eventProcessorSpy, + // sinon.match({ + // batchSize: 10, + // }) + // ); + // }); + + // it('should use default event batch size when none is provided', function() { + // optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + // }), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // logger: fakeLogger, + // }); + // sinon.assert.calledWithExactly( + // eventProcessorSpy, + // sinon.match({ + // batchSize: 10, + // }) + // ); + // }); + + // it('should use provided event batch size when valid', function() { + // optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + // }), + // errorHandler: fakeErrorHandler, + // eventDispatcher: fakeEventDispatcher, + // logger: fakeLogger, + // eventBatchSize: 300, + // }); + // sinon.assert.calledWithExactly( + // eventProcessorSpy, + // sinon.match({ + // batchSize: 300, + // }) + // ); + // }); + // }); + }); + }); +}); diff --git a/lib/index.node.ts b/lib/index.node.ts new file mode 100644 index 000000000..02d162ed6 --- /dev/null +++ b/lib/index.node.ts @@ -0,0 +1,54 @@ +/** + * Copyright 2016-2017, 2019-2025 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { NODE_CLIENT_ENGINE } from './utils/enums'; +import { Client, Config } from './shared_types'; +import { getOptimizelyInstance, OptimizelyFactoryConfig } from './client_factory'; +import { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; +import { NodeRequestHandler } from './utils/http_request_handler/request_handler.node'; + +/** + * Creates an instance of the Optimizely class + * @param {Config} config + * @return {Client|null} the Optimizely client object + * null on error + */ +export const createInstance = function(config: Config): Client { + const nodeConfig: OptimizelyFactoryConfig = { + ...config, + clientEngine: config.clientEngine || NODE_CLIENT_ENGINE, + requestHandler: new NodeRequestHandler(), + } + + return getOptimizelyInstance(nodeConfig); +}; + +export const getSendBeaconEventDispatcher = function(): EventDispatcher | undefined { + return undefined; +}; + +export { default as eventDispatcher } from './event_processor/event_dispatcher/default_dispatcher.node'; + +export { createPollingProjectConfigManager } from './project_config/config_manager_factory.node'; +export { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor/event_processor_factory.node'; + +export { createOdpManager } from './odp/odp_manager_factory.node'; +export { createVuidManager } from './vuid/vuid_manager_factory.node'; + +export * from './common_exports'; + +export * from './export_types'; + +export const clientEngine: string = NODE_CLIENT_ENGINE; diff --git a/lib/index.react_native.spec.ts b/lib/index.react_native.spec.ts new file mode 100644 index 000000000..d8ed60bea --- /dev/null +++ b/lib/index.react_native.spec.ts @@ -0,0 +1,174 @@ +/** + * Copyright 2019-2020, 2022-2025 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest'; + +import Optimizely from './optimizely'; +import testData from './tests/test_data'; +import packageJSON from '../package.json'; +import * as optimizelyFactory from './index.react_native'; +import configValidator from './utils/config_validator'; +import { getMockProjectConfigManager } from './tests/mock/mock_project_config_manager'; +import { createProjectConfig } from './project_config/project_config'; +import { getMockLogger } from './tests/mock/mock_logger'; +import { wrapConfigManager } from './project_config/config_manager_factory'; +import { wrapLogger } from './logging/logger_factory'; + +vi.mock('@react-native-community/netinfo'); +vi.mock('react-native-get-random-values') +vi.mock('fast-text-encoding') + +describe('javascript-sdk/react-native', () => { + beforeEach(() => { + vi.spyOn(optimizelyFactory.eventDispatcher, 'dispatchEvent'); + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + describe('APIs', () => { + // it('should expose logger, errorHandler, eventDispatcher and enums', () => { + // expect(optimizelyFactory.eventDispatcher).toBeDefined(); + // expect(optimizelyFactory.enums).toBeDefined(); + // }); + + describe('createInstance', () => { + const fakeErrorHandler = { handleError: function() {} }; + const fakeEventDispatcher = { dispatchEvent: async function() { + return Promise.resolve({}); + } }; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + let mockLogger; + + beforeEach(() => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + mockLogger = getMockLogger(); + vi.spyOn(console, 'error'); + }); + + afterEach(() => { + vi.resetAllMocks(); + }); + + // it('should not throw if the provided config is not valid', () => { + // vi.spyOn(configValidator, 'validate').mockImplementation(() => { + // throw new Error('Invalid config or something'); + // }); + // expect(function() { + // const optlyInstance = optimizelyFactory.createInstance({ + // projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), + // // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // // @ts-ignore + // logger: wrapLogger(mockLogger), + // }); + // }).not.toThrow(); + // }); + + it('should create an instance of optimizely', () => { + const optlyInstance = optimizelyFactory.createInstance({ + projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), + // errorHandler: fakeErrorHandler, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // logger: mockLogger, + }); + + expect(optlyInstance).toBeInstanceOf(Optimizely); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(optlyInstance.clientVersion).toEqual('6.2.0'); + }); + + it('should set the React Native JS client engine and javascript SDK version', () => { + const optlyInstance = optimizelyFactory.createInstance({ + projectConfigManager: wrapConfigManager(getMockProjectConfigManager()), + // errorHandler: fakeErrorHandler, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + // logger: mockLogger, + }); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect('react-native-js-sdk').toEqual(optlyInstance.clientEngine); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(packageJSON.version).toEqual(optlyInstance.clientVersion); + }); + + // it('should allow passing of "react-sdk" as the clientEngine and convert it to "react-native-sdk"', () => { + // const optlyInstance = optimizelyFactory.createInstance({ + // clientEngine: 'react-sdk', + // projectConfigManager: getMockProjectConfigManager(), + // errorHandler: fakeErrorHandler, + // // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // // @ts-ignore + // logger: mockLogger, + // }); + // // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // // @ts-ignore + // expect('react-native-sdk').toEqual(optlyInstance.clientEngine); + // }); + + // describe('when passing in logLevel', () => { + // beforeEach(() => { + // vi.spyOn(logging, 'setLogLevel'); + // }); + + // afterEach(() => { + // vi.resetAllMocks(); + // }); + + // it('should call logging.setLogLevel', () => { + // optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestProjectConfig()), + // }), + // logLevel: optimizelyFactory.enums.LOG_LEVEL.ERROR, + // }); + // expect(logging.setLogLevel).toBeCalledTimes(1); + // expect(logging.setLogLevel).toBeCalledWith(optimizelyFactory.enums.LOG_LEVEL.ERROR); + // }); + // }); + + // describe('when passing in logger', () => { + // beforeEach(() => { + // vi.spyOn(logging, 'setLogHandler'); + // }); + + // afterEach(() => { + // vi.resetAllMocks(); + // }); + + // it('should call logging.setLogHandler with the supplied logger', () => { + // const fakeLogger = { log: function() {} }; + // optimizelyFactory.createInstance({ + // projectConfigManager: getMockProjectConfigManager({ + // initConfig: createProjectConfig(testData.getTestProjectConfig()), + // }), + // // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // // @ts-ignore + // logger: fakeLogger, + // }); + // expect(logging.setLogHandler).toBeCalledTimes(1); + // expect(logging.setLogHandler).toBeCalledWith(fakeLogger); + // }); + // }); + }); + }); +}); diff --git a/lib/index.react_native.ts b/lib/index.react_native.ts new file mode 100644 index 000000000..c393261b7 --- /dev/null +++ b/lib/index.react_native.ts @@ -0,0 +1,57 @@ +/** + * Copyright 2019-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import 'fast-text-encoding'; +import 'react-native-get-random-values'; + +import { Client, Config } from './shared_types'; +import { getOptimizelyInstance, OptimizelyFactoryConfig } from './client_factory'; +import { REACT_NATIVE_JS_CLIENT_ENGINE } from './utils/enums'; +import { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; +import { BrowserRequestHandler } from './utils/http_request_handler/request_handler.browser'; + +/** + * Creates an instance of the Optimizely class + * @param {Config} config + * @return {Client|null} the Optimizely client object + * null on error + */ +export const createInstance = function(config: Config): Client { + const rnConfig: OptimizelyFactoryConfig = { + ...config, + clientEngine: config.clientEngine || REACT_NATIVE_JS_CLIENT_ENGINE, + requestHandler: new BrowserRequestHandler(), + } + + return getOptimizelyInstance(rnConfig); +}; + +export const getSendBeaconEventDispatcher = function(): EventDispatcher | undefined { + return undefined; +}; + +export { default as eventDispatcher } from './event_processor/event_dispatcher/default_dispatcher.browser'; + +export { createPollingProjectConfigManager } from './project_config/config_manager_factory.react_native'; +export { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor/event_processor_factory.react_native'; + +export { createOdpManager } from './odp/odp_manager_factory.react_native'; +export { createVuidManager } from './vuid/vuid_manager_factory.react_native'; + +export * from './common_exports'; + +export * from './export_types'; + +export const clientEngine: string = REACT_NATIVE_JS_CLIENT_ENGINE; diff --git a/lib/index.universal.ts b/lib/index.universal.ts new file mode 100644 index 000000000..11c39c1d1 --- /dev/null +++ b/lib/index.universal.ts @@ -0,0 +1,137 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Client, Config } from './shared_types'; +import { getOptimizelyInstance } from './client_factory'; +import { JAVASCRIPT_CLIENT_ENGINE } from './utils/enums'; + +import { RequestHandler } from './utils/http_request_handler/http'; + +export type UniversalConfig = Config & { + requestHandler: RequestHandler; +} + +/** + * Creates an instance of the Optimizely class + * @param {Config} config + * @return {Client|null} the Optimizely client object + * null on error + */ +export const createInstance = function(config: UniversalConfig): Client { + return getOptimizelyInstance(config); +}; + +export { createEventDispatcher } from './event_processor/event_dispatcher/event_dispatcher_factory'; + +export { createPollingProjectConfigManager } from './project_config/config_manager_factory.universal'; + +export { createForwardingEventProcessor, createBatchEventProcessor } from './event_processor/event_processor_factory.universal'; + +export { createOdpManager } from './odp/odp_manager_factory.universal'; + +// TODO: decide on vuid manager API for universal +// export { createVuidManager } from './vuid/vuid_manager_factory.node'; + +export * from './common_exports'; + +export const clientEngine: string = JAVASCRIPT_CLIENT_ENGINE; + +// type exports +export type { RequestHandler } from './utils/http_request_handler/http'; + +// config manager related types +export type { + StaticConfigManagerConfig, + OpaqueConfigManager, +} from './project_config/config_manager_factory'; + +export type { UniversalPollingConfigManagerConfig } from './project_config/config_manager_factory.universal'; + +// event processor related types +export type { + LogEvent, + EventDispatcherResponse, + EventDispatcher, +} from './event_processor/event_dispatcher/event_dispatcher'; + +export type { UniversalBatchEventProcessorOptions } from './event_processor/event_processor_factory.universal'; + +// odp manager related types +export type { + UniversalOdpManagerOptions, +} from './odp/odp_manager_factory.universal'; + +export type { + UserAgentParser, +} from './odp/ua_parser/user_agent_parser'; + +export type { + OpaqueEventProcessor, +} from './event_processor/event_processor_factory'; + +// Logger related types +export type { + LogHandler, +} from './logging/logger'; + +export type { + OpaqueLevelPreset, + LoggerConfig, + OpaqueLogger, +} from './logging/logger_factory'; + +// Error related types +export type { ErrorHandler } from './error/error_handler'; +export type { OpaqueErrorNotifier } from './error/error_notifier_factory'; + +export type { SyncCache, AsyncCache, Cache, SyncCacheWithRemove, AsyncCacheWithRemove, CacheWithRemove } from './utils/cache/cache'; +export type { SyncStore, AsyncStore, Store } from './utils/cache/store' + +export type { + NotificationType, + NotificationPayload, + ActivateListenerPayload as ActivateNotificationPayload, + DecisionListenerPayload as DecisionNotificationPayload, + TrackListenerPayload as TrackNotificationPayload, + LogEventListenerPayload as LogEventNotificationPayload, + OptimizelyConfigUpdateListenerPayload as OptimizelyConfigUpdateNotificationPayload, +} from './notification_center/type'; + +export type { + UserAttributeValue, + UserAttributes, + OptimizelyConfig, + FeatureVariableValue, + OptimizelyVariable, + OptimizelyVariation, + OptimizelyExperiment, + OptimizelyFeature, + OptimizelyDecisionContext, + OptimizelyForcedDecision, + EventTags, + Event, + DatafileOptions, + UserProfileService, + UserProfile, + ListenerPayload, + OptimizelyDecision, + OptimizelyUserContext, + Config, + Client, + ActivateListenerPayload, + TrackListenerPayload, + NotificationCenter, + OptimizelySegmentOption, +} from './shared_types'; diff --git a/lib/logging/logger.spec.ts b/lib/logging/logger.spec.ts new file mode 100644 index 000000000..59edd3f96 --- /dev/null +++ b/lib/logging/logger.spec.ts @@ -0,0 +1,386 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, beforeEach, afterEach, it, expect, vi, afterAll } from 'vitest'; + +import { ConsoleLogHandler, LogLevel, OptimizelyLogger } from './logger'; +import { OptimizelyError } from '../error/optimizly_error'; + +describe('ConsoleLogHandler', () => { + const logSpy = vi.spyOn(console, 'log'); + const debugSpy = vi.spyOn(console, 'debug'); + const infoSpy = vi.spyOn(console, 'info'); + const warnSpy = vi.spyOn(console, 'warn'); + const errorSpy = vi.spyOn(console, 'error'); + + beforeEach(() => { + logSpy.mockClear(); + debugSpy.mockClear(); + infoSpy.mockClear(); + warnSpy.mockClear(); + vi.useFakeTimers().setSystemTime(0); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + afterAll(() => { + logSpy.mockRestore(); + debugSpy.mockRestore(); + infoSpy.mockRestore(); + warnSpy.mockRestore(); + errorSpy.mockRestore(); + + vi.useRealTimers(); + }); + + it('should call console.info for LogLevel.Info', () => { + const logger = new ConsoleLogHandler(); + logger.log(LogLevel.Info, 'test'); + + expect(infoSpy).toHaveBeenCalledTimes(1); + }); + + it('should call console.debug for LogLevel.Debug', () => { + const logger = new ConsoleLogHandler(); + logger.log(LogLevel.Debug, 'test'); + + expect(debugSpy).toHaveBeenCalledTimes(1); + }); + + + it('should call console.warn for LogLevel.Warn', () => { + const logger = new ConsoleLogHandler(); + logger.log(LogLevel.Warn, 'test'); + + expect(warnSpy).toHaveBeenCalledTimes(1); + }); + + it('should call console.error for LogLevel.Error', () => { + const logger = new ConsoleLogHandler(); + logger.log(LogLevel.Error, 'test'); + + expect(errorSpy).toHaveBeenCalledTimes(1); + }); + + it('should format the log message', () => { + const logger = new ConsoleLogHandler(); + logger.log(LogLevel.Info, 'info message'); + logger.log(LogLevel.Debug, 'debug message'); + logger.log(LogLevel.Warn, 'warn message'); + logger.log(LogLevel.Error, 'error message'); + + expect(infoSpy).toHaveBeenCalledWith('[OPTIMIZELY] - INFO 1970-01-01T00:00:00.000Z info message'); + expect(debugSpy).toHaveBeenCalledWith('[OPTIMIZELY] - DEBUG 1970-01-01T00:00:00.000Z debug message'); + expect(warnSpy).toHaveBeenCalledWith('[OPTIMIZELY] - WARN 1970-01-01T00:00:00.000Z warn message'); + expect(errorSpy).toHaveBeenCalledWith('[OPTIMIZELY] - ERROR 1970-01-01T00:00:00.000Z error message'); + }); + + it('should use the prefix if provided', () => { + const logger = new ConsoleLogHandler('PREFIX'); + logger.log(LogLevel.Info, 'info message'); + logger.log(LogLevel.Debug, 'debug message'); + logger.log(LogLevel.Warn, 'warn message'); + logger.log(LogLevel.Error, 'error message'); + + expect(infoSpy).toHaveBeenCalledWith('PREFIX - INFO 1970-01-01T00:00:00.000Z info message'); + expect(debugSpy).toHaveBeenCalledWith('PREFIX - DEBUG 1970-01-01T00:00:00.000Z debug message'); + expect(warnSpy).toHaveBeenCalledWith('PREFIX - WARN 1970-01-01T00:00:00.000Z warn message'); + expect(errorSpy).toHaveBeenCalledWith('PREFIX - ERROR 1970-01-01T00:00:00.000Z error message'); + }); +}); + + +const mockMessageResolver = (prefix = '') => { + return { + resolve: vi.fn().mockImplementation((message) => `${prefix} ${message}`), + }; +} + +const mockLogHandler = () => { + return { + log: vi.fn(), + }; +} + +describe('OptimizelyLogger', () => { + it('should only log error when level is set to error', () => { + const logHandler = mockLogHandler(); + const messageResolver = mockMessageResolver(); + + const logger = new OptimizelyLogger({ + logHandler, + errorMsgResolver: messageResolver, + level: LogLevel.Error, + }); + + logger.error('test'); + expect(logHandler.log).toHaveBeenCalledTimes(1); + expect(logHandler.log.mock.calls[0][0]).toBe(LogLevel.Error); + + logger.warn('test'); + expect(logHandler.log).toHaveBeenCalledTimes(1); + + logger.info('test'); + expect(logHandler.log).toHaveBeenCalledTimes(1); + + logger.debug('test'); + expect(logHandler.log).toHaveBeenCalledTimes(1); + }); + + it('should only log warn and error when level is set to warn', () => { + const logHandler = mockLogHandler(); + const messageResolver = mockMessageResolver(); + + const logger = new OptimizelyLogger({ + logHandler, + errorMsgResolver: messageResolver, + level: LogLevel.Warn, + }); + + logger.error('test'); + expect(logHandler.log).toHaveBeenCalledTimes(1); + expect(logHandler.log.mock.calls[0][0]).toBe(LogLevel.Error); + + logger.warn('test'); + expect(logHandler.log).toHaveBeenCalledTimes(2); + expect(logHandler.log.mock.calls[1][0]).toBe(LogLevel.Warn); + + logger.info('test'); + expect(logHandler.log).toHaveBeenCalledTimes(2); + + logger.debug('test'); + expect(logHandler.log).toHaveBeenCalledTimes(2); + }); + + it('should only log info, warn and error when level is set to info', () => { + const logHandler = mockLogHandler(); + const messageResolver = mockMessageResolver(); + + const logger = new OptimizelyLogger({ + logHandler, + infoMsgResolver: messageResolver, + errorMsgResolver: messageResolver, + level: LogLevel.Info, + }); + + logger.error('test'); + expect(logHandler.log).toHaveBeenCalledTimes(1); + expect(logHandler.log.mock.calls[0][0]).toBe(LogLevel.Error); + + logger.warn('test'); + expect(logHandler.log).toHaveBeenCalledTimes(2); + expect(logHandler.log.mock.calls[1][0]).toBe(LogLevel.Warn); + + logger.info('test'); + expect(logHandler.log).toHaveBeenCalledTimes(3); + expect(logHandler.log.mock.calls[2][0]).toBe(LogLevel.Info); + + logger.debug('test'); + expect(logHandler.log).toHaveBeenCalledTimes(3); + }); + + it('should log all levels when level is set to debug', () => { + const logHandler = mockLogHandler(); + const messageResolver = mockMessageResolver(); + + const logger = new OptimizelyLogger({ + logHandler, + infoMsgResolver: messageResolver, + errorMsgResolver: messageResolver, + level: LogLevel.Debug, + }); + + logger.error('test'); + expect(logHandler.log).toHaveBeenCalledTimes(1); + expect(logHandler.log.mock.calls[0][0]).toBe(LogLevel.Error); + + logger.warn('test'); + expect(logHandler.log).toHaveBeenCalledTimes(2); + expect(logHandler.log.mock.calls[1][0]).toBe(LogLevel.Warn); + + logger.info('test'); + expect(logHandler.log).toHaveBeenCalledTimes(3); + expect(logHandler.log.mock.calls[2][0]).toBe(LogLevel.Info); + + logger.debug('test'); + expect(logHandler.log).toHaveBeenCalledTimes(4); + expect(logHandler.log.mock.calls[3][0]).toBe(LogLevel.Debug); + }); + + it('should skip logging debug/info levels if not infoMessageResolver is available', () => { + const logHandler = mockLogHandler(); + const messageResolver = mockMessageResolver(); + + const logger = new OptimizelyLogger({ + logHandler, + errorMsgResolver: messageResolver, + level: LogLevel.Debug, + }); + + logger.info('test'); + logger.debug('test'); + expect(logHandler.log).not.toHaveBeenCalled(); + }); + + it('should resolve debug/info messages using the infoMessageResolver', () => { + const logHandler = mockLogHandler(); + + const logger = new OptimizelyLogger({ + logHandler, + infoMsgResolver: mockMessageResolver('info'), + errorMsgResolver: mockMessageResolver('err'), + level: LogLevel.Debug, + }); + + logger.debug('msg one'); + logger.info('msg two'); + expect(logHandler.log).toHaveBeenCalledTimes(2); + expect(logHandler.log).toHaveBeenNthCalledWith(1, LogLevel.Debug, 'info msg one'); + expect(logHandler.log).toHaveBeenNthCalledWith(2, LogLevel.Info, 'info msg two'); + }); + + it('should resolve warn/error messages using the infoMessageResolver', () => { + const logHandler = mockLogHandler(); + + const logger = new OptimizelyLogger({ + logHandler, + infoMsgResolver: mockMessageResolver('info'), + errorMsgResolver: mockMessageResolver('err'), + level: LogLevel.Debug, + }); + + logger.warn('msg one'); + logger.error('msg two'); + expect(logHandler.log).toHaveBeenCalledTimes(2); + expect(logHandler.log).toHaveBeenNthCalledWith(1, LogLevel.Warn, 'err msg one'); + expect(logHandler.log).toHaveBeenNthCalledWith(2, LogLevel.Error, 'err msg two'); + }); + + it('should use the provided name as message prefix', () => { + const logHandler = mockLogHandler(); + + const logger = new OptimizelyLogger({ + name: 'EventManager', + logHandler, + infoMsgResolver: mockMessageResolver('info'), + errorMsgResolver: mockMessageResolver('err'), + level: LogLevel.Debug, + }); + + logger.warn('msg one'); + logger.error('msg two'); + logger.debug('msg three'); + logger.info('msg four'); + expect(logHandler.log).toHaveBeenCalledTimes(4); + expect(logHandler.log).toHaveBeenNthCalledWith(1, LogLevel.Warn, 'EventManager: err msg one'); + expect(logHandler.log).toHaveBeenNthCalledWith(2, LogLevel.Error, 'EventManager: err msg two'); + expect(logHandler.log).toHaveBeenNthCalledWith(3, LogLevel.Debug, 'EventManager: info msg three'); + expect(logHandler.log).toHaveBeenNthCalledWith(4, LogLevel.Info, 'EventManager: info msg four'); + }); + + it('should format the message with the give parameters', () => { + const logHandler = mockLogHandler(); + + const logger = new OptimizelyLogger({ + name: 'EventManager', + logHandler, + infoMsgResolver: mockMessageResolver('info'), + errorMsgResolver: mockMessageResolver('err'), + level: LogLevel.Debug, + }); + + logger.warn('msg %s, %s', 'one', 1); + logger.error('msg %s', 'two'); + logger.debug('msg three', 9999); + logger.info('msg four%s%s', '!', '!'); + expect(logHandler.log).toHaveBeenCalledTimes(4); + expect(logHandler.log).toHaveBeenNthCalledWith(1, LogLevel.Warn, 'EventManager: err msg one, 1'); + expect(logHandler.log).toHaveBeenNthCalledWith(2, LogLevel.Error, 'EventManager: err msg two'); + expect(logHandler.log).toHaveBeenNthCalledWith(3, LogLevel.Debug, 'EventManager: info msg three'); + expect(logHandler.log).toHaveBeenNthCalledWith(4, LogLevel.Info, 'EventManager: info msg four!!'); + }); + + it('should log the message of the error object and ignore other arguments if first argument is an error object \ + other that OptimizelyError', () => { + const logHandler = mockLogHandler(); + + const logger = new OptimizelyLogger({ + name: 'EventManager', + logHandler, + infoMsgResolver: mockMessageResolver('info'), + errorMsgResolver: mockMessageResolver('err'), + level: LogLevel.Debug, + }); + logger.debug(new Error('msg debug %s'), 'a'); + logger.info(new Error('msg info %s'), 'b'); + logger.warn(new Error('msg warn %s'), 'c'); + logger.error(new Error('msg error %s'), 'd'); + + expect(logHandler.log).toHaveBeenCalledTimes(4); + expect(logHandler.log).toHaveBeenNthCalledWith(1, LogLevel.Debug, 'EventManager: msg debug %s'); + expect(logHandler.log).toHaveBeenNthCalledWith(2, LogLevel.Info, 'EventManager: msg info %s'); + expect(logHandler.log).toHaveBeenNthCalledWith(3, LogLevel.Warn, 'EventManager: msg warn %s'); + expect(logHandler.log).toHaveBeenNthCalledWith(4, LogLevel.Error, 'EventManager: msg error %s'); + }); + + it('should resolve and log the message of an OptimizelyError using error resolver and ignore other arguments', () => { + const logHandler = mockLogHandler(); + + const logger = new OptimizelyLogger({ + name: 'EventManager', + logHandler, + infoMsgResolver: mockMessageResolver('info'), + errorMsgResolver: mockMessageResolver('err'), + level: LogLevel.Debug, + }); + + const err = new OptimizelyError('msg %s %s', 1, 2); + logger.debug(err, 'a'); + logger.info(err, 'a'); + logger.warn(err, 'a'); + logger.error(err, 'a'); + + expect(logHandler.log).toHaveBeenCalledTimes(4); + expect(logHandler.log).toHaveBeenNthCalledWith(1, LogLevel.Debug, 'EventManager: err msg 1 2'); + expect(logHandler.log).toHaveBeenNthCalledWith(2, LogLevel.Info, 'EventManager: err msg 1 2'); + expect(logHandler.log).toHaveBeenNthCalledWith(3, LogLevel.Warn, 'EventManager: err msg 1 2'); + expect(logHandler.log).toHaveBeenNthCalledWith(4, LogLevel.Error, 'EventManager: err msg 1 2'); + }); + + it('should return a new logger with the new name but same level, handler and resolvers when child() is called', () => { + const logHandler = mockLogHandler(); + + const logger = new OptimizelyLogger({ + name: 'EventManager', + logHandler, + infoMsgResolver: mockMessageResolver('info'), + errorMsgResolver: mockMessageResolver('err'), + level: LogLevel.Info, + }); + + const childLogger = logger.child('ChildLogger'); + childLogger.debug('msg one %s', 1); + childLogger.info('msg two %s', 2); + childLogger.warn('msg three %s', 3); + childLogger.error('msg four %s', 4); + + expect(logHandler.log).toHaveBeenCalledTimes(3); + expect(logHandler.log).toHaveBeenNthCalledWith(1, LogLevel.Info, 'ChildLogger: info msg two 2'); + expect(logHandler.log).toHaveBeenNthCalledWith(2, LogLevel.Warn, 'ChildLogger: err msg three 3'); + expect(logHandler.log).toHaveBeenNthCalledWith(3, LogLevel.Error, 'ChildLogger: err msg four 4'); + }); +}); diff --git a/lib/logging/logger.ts b/lib/logging/logger.ts new file mode 100644 index 000000000..8414d544a --- /dev/null +++ b/lib/logging/logger.ts @@ -0,0 +1,167 @@ +/** + * Copyright 2019, 2024, 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { OptimizelyError } from '../error/optimizly_error'; +import { MessageResolver } from '../message/message_resolver'; +import { sprintf } from '../utils/fns' + +export enum LogLevel { + Debug, + Info, + Warn, + Error, +} + +export const LogLevelToUpper: Record<LogLevel, string> = { + [LogLevel.Debug]: 'DEBUG', + [LogLevel.Info]: 'INFO', + [LogLevel.Warn]: 'WARN', + [LogLevel.Error]: 'ERROR', +}; + +export const LogLevelToLower: Record<LogLevel, string> = { + [LogLevel.Debug]: 'debug', + [LogLevel.Info]: 'info', + [LogLevel.Warn]: 'warn', + [LogLevel.Error]: 'error', +}; + +export interface LoggerFacade { + info(message: string | Error, ...args: any[]): void; + debug(message: string | Error, ...args: any[]): void; + warn(message: string | Error, ...args: any[]): void; + error(message: string | Error, ...args: any[]): void; + child(name?: string): LoggerFacade; + setName(name: string): void; +} + +export interface LogHandler { + log(level: LogLevel, message: string, ...args: any[]): void +} + +export class ConsoleLogHandler implements LogHandler { + private prefix: string + + constructor(prefix?: string) { + this.prefix = prefix || '[OPTIMIZELY]' + } + + log(level: LogLevel, message: string) : void { + const log = `${this.prefix} - ${LogLevelToUpper[level]} ${this.getTime()} ${message}` + this.consoleLog(level, log) + } + + private getTime(): string { + return new Date().toISOString() + } + + private consoleLog(logLevel: LogLevel, log: string) : void { + const methodName: string = LogLevelToLower[logLevel]; + + const method: any = console[methodName as keyof Console] || console.log; + method.call(console, log); + } +} + +type OptimizelyLoggerConfig = { + logHandler: LogHandler, + infoMsgResolver?: MessageResolver, + errorMsgResolver: MessageResolver, + level: LogLevel, + name?: string, +}; + +export class OptimizelyLogger implements LoggerFacade { + private name?: string; + private prefix = ''; + private logHandler: LogHandler; + private infoResolver?: MessageResolver; + private errorResolver: MessageResolver; + private level: LogLevel; + + constructor(config: OptimizelyLoggerConfig) { + this.logHandler = config.logHandler; + this.infoResolver = config.infoMsgResolver; + this.errorResolver = config.errorMsgResolver; + this.level = config.level; + if (config.name) { + this.setName(config.name); + } + } + + child(name?: string): OptimizelyLogger { + return new OptimizelyLogger({ + logHandler: this.logHandler, + infoMsgResolver: this.infoResolver, + errorMsgResolver: this.errorResolver, + level: this.level, + name, + }); + } + + setName(name: string): void { + this.name = name; + this.prefix = `${name}: `; + } + + info(message: string | Error, ...args: any[]): void { + this.log(LogLevel.Info, message, args) + } + + debug(message: string | Error, ...args: any[]): void { + this.log(LogLevel.Debug, message, args) + } + + warn(message: string | Error, ...args: any[]): void { + this.log(LogLevel.Warn, message, args) + } + + error(message: string | Error, ...args: any[]): void { + this.log(LogLevel.Error, message, args) + } + + private handleLog(level: LogLevel, message: string, args: any[]) { + const log = args.length > 0 ? `${this.prefix}${sprintf(message, ...args)}` + : `${this.prefix}${message}`; + + this.logHandler.log(level, log); + } + + private log(level: LogLevel, message: string | Error, args: any[]): void { + if (level < this.level) { + return; + } + + if (message instanceof Error) { + if (message instanceof OptimizelyError) { + message.setMessage(this.errorResolver); + } + this.handleLog(level, message.message, []); + return; + } + + let resolver = this.errorResolver; + + if (level < LogLevel.Warn) { + if (!this.infoResolver) { + return; + } + resolver = this.infoResolver; + } + + const resolvedMessage = resolver.resolve(message); + this.handleLog(level, resolvedMessage, args); + } +} diff --git a/lib/logging/logger_factory.spec.ts b/lib/logging/logger_factory.spec.ts new file mode 100644 index 000000000..bc7671008 --- /dev/null +++ b/lib/logging/logger_factory.spec.ts @@ -0,0 +1,105 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, beforeEach, vi } from 'vitest'; + +vi.mock('./logger', async (importOriginal) => { + const actual = await importOriginal() + + const MockLogger = vi.fn(); + const MockConsoleLogHandler = vi.fn(); + + return { ...actual as any, OptimizelyLogger: MockLogger, ConsoleLogHandler: MockConsoleLogHandler }; +}); + +import { OptimizelyLogger, ConsoleLogHandler, LogLevel } from './logger'; +import { createLogger, extractLogger, INFO } from './logger_factory'; +import { errorResolver, infoResolver } from '../message/message_resolver'; + +describe('createLogger', () => { + const MockedOptimizelyLogger = vi.mocked(OptimizelyLogger); + const MockedConsoleLogHandler = vi.mocked(ConsoleLogHandler); + + beforeEach(() => { + MockedConsoleLogHandler.mockClear(); + MockedOptimizelyLogger.mockClear(); + }); + + it('should throw an error if the provided logHandler is not a valid LogHandler', () => { + expect(() => createLogger({ + level: INFO, + logHandler: {} as any, + })).toThrow('Invalid log handler'); + + expect(() => createLogger({ + level: INFO, + logHandler: { log: 'abc' } as any, + })).toThrow('Invalid log handler'); + + expect(() => createLogger({ + level: INFO, + logHandler: 'abc' as any, + })).toThrow('Invalid log handler'); + }); + + it('should throw an error if the level is not a valid level preset', () => { + expect(() => createLogger({ + level: null as any, + })).toThrow('Invalid level preset'); + + expect(() => createLogger({ + level: undefined as any, + })).toThrow('Invalid level preset'); + + expect(() => createLogger({ + level: 'abc' as any, + })).toThrow('Invalid level preset'); + + expect(() => createLogger({ + level: 123 as any, + })).toThrow('Invalid level preset'); + + expect(() => createLogger({ + level: {} as any, + })).toThrow('Invalid level preset'); + }); + + it('should use the passed in options and a default name Optimizely', () => { + const mockLogHandler = { log: vi.fn() }; + + const logger = extractLogger(createLogger({ + level: INFO, + logHandler: mockLogHandler, + })); + + expect(logger).toBe(MockedOptimizelyLogger.mock.instances[0]); + const { name, level, infoMsgResolver, errorMsgResolver, logHandler } = MockedOptimizelyLogger.mock.calls[0][0]; + expect(name).toBe('Optimizely'); + expect(level).toBe(LogLevel.Info); + expect(infoMsgResolver).toBe(infoResolver); + expect(errorMsgResolver).toBe(errorResolver); + expect(logHandler).toBe(mockLogHandler); + }); + + it('should use a ConsoleLogHandler if no logHandler is provided', () => { + const logger = extractLogger(createLogger({ + level: INFO, + })); + + expect(logger).toBe(MockedOptimizelyLogger.mock.instances[0]); + const { logHandler } = MockedOptimizelyLogger.mock.calls[0][0]; + expect(logHandler).toBe(MockedConsoleLogHandler.mock.instances[0]); + }); +}); \ No newline at end of file diff --git a/lib/logging/logger_factory.ts b/lib/logging/logger_factory.ts new file mode 100644 index 000000000..2aee1b535 --- /dev/null +++ b/lib/logging/logger_factory.ts @@ -0,0 +1,129 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { ConsoleLogHandler, LogHandler, LogLevel, OptimizelyLogger } from './logger'; +import { errorResolver, infoResolver, MessageResolver } from '../message/message_resolver'; +import { Maybe } from '../utils/type'; + +export const INVALID_LOG_HANDLER = 'Invalid log handler'; +export const INVALID_LEVEL_PRESET = 'Invalid level preset'; + +type LevelPreset = { + level: LogLevel, + infoResolver?: MessageResolver, + errorResolver: MessageResolver, +} + +const debugPreset: LevelPreset = { + level: LogLevel.Debug, + infoResolver, + errorResolver, +}; + +const infoPreset: LevelPreset = { + level: LogLevel.Info, + infoResolver, + errorResolver, +} + +const warnPreset: LevelPreset = { + level: LogLevel.Warn, + errorResolver, +} + +const errorPreset: LevelPreset = { + level: LogLevel.Error, + errorResolver, +} + +const levelPresetSymbol = Symbol(); + +export type OpaqueLevelPreset = { + [levelPresetSymbol]: unknown; +}; + +export const DEBUG: OpaqueLevelPreset = { + [levelPresetSymbol]: debugPreset, +}; + +export const INFO: OpaqueLevelPreset = { + [levelPresetSymbol]: infoPreset, +}; + +export const WARN: OpaqueLevelPreset = { + [levelPresetSymbol]: warnPreset, +}; + +export const ERROR: OpaqueLevelPreset = { + [levelPresetSymbol]: errorPreset, +}; + +export const extractLevelPreset = (preset: OpaqueLevelPreset): LevelPreset => { + if (!preset || typeof preset !== 'object' || !preset[levelPresetSymbol]) { + throw new Error(INVALID_LEVEL_PRESET); + } + return preset[levelPresetSymbol] as LevelPreset; +} + +const loggerSymbol = Symbol(); + +export type OpaqueLogger = { + [loggerSymbol]: unknown; +}; + +export type LoggerConfig = { + level: OpaqueLevelPreset, + logHandler?: LogHandler, +}; + +const validateLogHandler = (logHandler: any) => { + if (typeof logHandler !== 'object' || typeof logHandler.log !== 'function') { + throw new Error(INVALID_LOG_HANDLER); + } +} + +export const createLogger = (config: LoggerConfig): OpaqueLogger => { + const { level, infoResolver, errorResolver } = extractLevelPreset(config.level); + + if (config.logHandler) { + validateLogHandler(config.logHandler); + } + + const loggerName = 'Optimizely'; + + return { + [loggerSymbol]: new OptimizelyLogger({ + name: loggerName, + level, + infoMsgResolver: infoResolver, + errorMsgResolver: errorResolver, + logHandler: config.logHandler || new ConsoleLogHandler(), + }), + }; +}; + +export const wrapLogger = (logger: OptimizelyLogger): OpaqueLogger => { + return { + [loggerSymbol]: logger, + }; +}; + +export const extractLogger = (logger: Maybe<OpaqueLogger>): Maybe<OptimizelyLogger> => { + if (!logger || typeof logger !== 'object') { + return undefined; + } + + return logger[loggerSymbol] as Maybe<OptimizelyLogger>; +}; diff --git a/lib/message/error_message.ts b/lib/message/error_message.ts new file mode 100644 index 000000000..720baa377 --- /dev/null +++ b/lib/message/error_message.ts @@ -0,0 +1,100 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const NOTIFICATION_LISTENER_EXCEPTION = 'Notification listener for (%s) threw exception: %s'; +export const CONDITION_EVALUATOR_ERROR = 'Error evaluating audience condition of type %s: %s'; +export const EXPERIMENT_KEY_NOT_IN_DATAFILE = 'Experiment key %s is not in datafile.'; +export const FEATURE_NOT_IN_DATAFILE = 'Feature key %s is not in datafile.'; +export const INVALID_ATTRIBUTES = 'Provided attributes are in an invalid format.'; +export const INVALID_BUCKETING_ID = 'Unable to generate hash for bucketing ID %s: %s'; +export const INVALID_DATAFILE = 'Datafile is invalid - property %s: %s'; +export const INVALID_DATAFILE_MALFORMED = 'Datafile is invalid because it is malformed.'; +export const INVALID_CONFIG = 'Provided Optimizely config is in an invalid format.'; +export const INVALID_JSON = 'JSON object is not valid.'; +export const INVALID_EVENT_TAGS = 'Provided event tags are in an invalid format.'; +export const INVALID_EXPERIMENT_KEY = + 'Experiment key %s is not in datafile. It is either invalid, paused, or archived.'; +export const INVALID_EXPERIMENT_ID = 'Experiment ID %s is not in datafile.'; +export const INVALID_GROUP_ID = 'Group ID %s is not in datafile.'; +export const INVALID_USER_ID = 'Provided user ID is in an invalid format.'; +export const INVALID_USER_PROFILE_SERVICE = 'Provided user profile service instance is in an invalid format: %s.'; +export const MISSING_INTEGRATION_KEY = + 'Integration key missing from datafile. All integrations should include a key.'; +export const NO_DATAFILE_SPECIFIED = 'No datafile specified. Cannot start optimizely.'; +export const NO_JSON_PROVIDED = 'No JSON object to validate against schema.'; +export const NO_EVENT_PROCESSOR = 'No event processor is provided'; +export const NO_VARIATION_FOR_EXPERIMENT_KEY = 'No variation key %s defined in datafile for experiment %s.'; +export const ODP_CONFIG_NOT_AVAILABLE = 'ODP config is not available.'; +export const ODP_EVENT_FAILED = 'ODP event send failed.'; +export const ODP_EVENTS_SHOULD_HAVE_ATLEAST_ONE_KEY_VALUE = 'ODP events should have at least one key-value pair in identifiers.'; +export const ODP_EVENT_FAILED_ODP_MANAGER_MISSING = 'ODP Event failed to send. (ODP Manager not available).'; +export const ODP_NOT_INTEGRATED = 'ODP is not integrated'; +export const UNDEFINED_ATTRIBUTE = 'Provided attribute: %s has an undefined value.'; +export const UNRECOGNIZED_ATTRIBUTE = + 'Unrecognized attribute %s provided. Pruning before sending event to Optimizely.'; +export const UNABLE_TO_CAST_VALUE = 'Unable to cast value %s to type %s, returning null.'; +export const USER_NOT_IN_FORCED_VARIATION = + 'User %s is not in the forced variation map. Cannot remove their forced variation.'; +export const USER_PROFILE_LOOKUP_ERROR = 'Error while looking up user profile for user ID "%s": %s.'; +export const USER_PROFILE_SAVE_ERROR = 'Error while saving user profile for user ID "%s": %s.'; +export const VARIABLE_KEY_NOT_IN_DATAFILE = + 'Variable with key "%s" associated with feature with key "%s" is not in datafile.'; +export const VARIATION_ID_NOT_IN_DATAFILE = 'Variation ID %s is not in the datafile.'; +export const INVALID_INPUT_FORMAT = 'Provided %s is in an invalid format.'; +export const INVALID_DATAFILE_VERSION = + 'This version of the JavaScript SDK does not support the given datafile version: %s'; +export const INVALID_VARIATION_KEY = 'Provided variation key is in an invalid format.'; +export const ERROR_FETCHING_DATAFILE = 'Error fetching datafile: %s'; +export const DATAFILE_FETCH_REQUEST_FAILED = 'Datafile fetch request failed with status: %s'; +export const EVENT_DATA_INVALID = 'Event data invalid.'; +export const EVENT_ACTION_INVALID = 'Event action invalid.'; +export const FAILED_TO_SEND_ODP_EVENTS = 'failed to send odp events'; +export const UNABLE_TO_GET_VUID_VUID_MANAGER_NOT_AVAILABLE = 'Unable to get VUID - VuidManager is not available' +export const UNKNOWN_CONDITION_TYPE = + 'Audience condition %s has an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK.'; +export const UNKNOWN_MATCH_TYPE = + 'Audience condition %s uses an unknown match type. You may need to upgrade to a newer release of the Optimizely SDK.'; +export const UNRECOGNIZED_DECIDE_OPTION = 'Unrecognized decide option %s provided.'; +export const NO_PROJECT_CONFIG_FAILURE = 'No project config available. Failing %s.'; +export const EVENT_KEY_NOT_FOUND = 'Event key %s is not in datafile.'; +export const NOT_TRACKING_USER = 'Not tracking user %s.'; +export const VARIABLE_REQUESTED_WITH_WRONG_TYPE = + 'Requested variable type "%s", but variable is of type "%s". Use correct API to retrieve value. Returning None.'; +export const UNEXPECTED_RESERVED_ATTRIBUTE_PREFIX = + 'Attribute %s unexpectedly has reserved prefix %s; using attribute ID instead of reserved attribute name.'; +export const BUCKETING_ID_NOT_STRING = 'BucketingID attribute is not a string. Defaulted to userId'; +export const UNEXPECTED_CONDITION_VALUE = + 'Audience condition %s evaluated to UNKNOWN because the condition value is not supported.'; +export const UNEXPECTED_TYPE = + 'Audience condition %s evaluated to UNKNOWN because a value of type "%s" was passed for user attribute "%s".'; +export const OUT_OF_BOUNDS = + 'Audience condition %s evaluated to UNKNOWN because the number value for user attribute "%s" is not in the range [-2^53, +2^53].'; +export const REQUEST_TIMEOUT = 'Request timeout'; +export const REQUEST_ERROR = 'Request error'; +export const NO_STATUS_CODE_IN_RESPONSE = 'No status code in response'; +export const UNSUPPORTED_PROTOCOL = 'Unsupported protocol: %s'; +export const RETRY_CANCELLED = 'Retry cancelled'; +export const ONLY_POST_REQUESTS_ARE_SUPPORTED = 'Only POST requests are supported'; +export const SEND_BEACON_FAILED = 'sendBeacon failed'; +export const FAILED_TO_DISPATCH_EVENTS = 'Failed to dispatch events, status: %s'; +export const ODP_EVENT_MANAGER_STOPPED = "ODP event manager stopped before it could start"; +export const UNABLE_TO_ATTACH_UNLOAD = 'unable to bind optimizely.close() to page unload event: "%s"'; +export const UNABLE_TO_PARSE_AND_SKIPPED_HEADER = 'Unable to parse & skipped header item'; +export const CMAB_FETCH_FAILED = 'CMAB decision fetch failed with status: %s'; +export const INVALID_CMAB_FETCH_RESPONSE = 'Invalid CMAB fetch response'; +export const PROMISE_NOT_ALLOWED = "Promise value is not allowed in sync operation"; +export const SERVICE_NOT_RUNNING = "%s not running"; + +export const messages: string[] = []; diff --git a/lib/message/log_message.ts b/lib/message/log_message.ts new file mode 100644 index 000000000..aaf5a9e36 --- /dev/null +++ b/lib/message/log_message.ts @@ -0,0 +1,71 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const FEATURE_ENABLED_FOR_USER = 'Feature %s is enabled for user %s.'; +export const FEATURE_NOT_ENABLED_FOR_USER = 'Feature %s is not enabled for user %s.'; +export const FAILED_TO_PARSE_VALUE = 'Failed to parse event value "%s" from event tags.'; +export const FAILED_TO_PARSE_REVENUE = 'Failed to parse revenue value "%s" from event tags.'; +export const INVALID_CLIENT_ENGINE = 'Invalid client engine passed: %s. Defaulting to node-sdk.'; +export const INVALID_DEFAULT_DECIDE_OPTIONS = 'Provided default decide options is not an array.'; +export const INVALID_DECIDE_OPTIONS = 'Provided decide options is not an array. Using default decide options.'; +export const NOT_ACTIVATING_USER = 'Not activating user %s for experiment %s.'; +export const PARSED_REVENUE_VALUE = 'Parsed revenue value "%s" from event tags.'; +export const PARSED_NUMERIC_VALUE = 'Parsed event value "%s" from event tags.'; +export const SAVED_USER_VARIATION = 'Saved user profile for user "%s".'; +export const SAVED_VARIATION_NOT_FOUND = + 'User %s was previously bucketed into variation with ID %s for experiment %s, but no matching variation was found.'; +export const SHOULD_NOT_DISPATCH_ACTIVATE = 'Experiment %s is not in "Running" state. Not activating user.'; +export const SKIPPING_JSON_VALIDATION = 'Skipping JSON schema validation.'; +export const TRACK_EVENT = 'Tracking event %s for user %s.'; +export const USER_MAPPED_TO_FORCED_VARIATION = + 'Set variation %s for experiment %s and user %s in the forced variation map.'; +export const USER_HAS_NO_FORCED_VARIATION = 'User %s is not in the forced variation map.'; +export const USER_RECEIVED_DEFAULT_VARIABLE_VALUE = + 'User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".'; +export const FEATURE_NOT_ENABLED_RETURN_DEFAULT_VARIABLE_VALUE = + 'Feature "%s" is not enabled for user %s. Returning the default variable value "%s".'; +export const VARIABLE_NOT_USED_RETURN_DEFAULT_VARIABLE_VALUE = + 'Variable "%s" is not used in variation "%s". Returning default value.'; +export const USER_RECEIVED_VARIABLE_VALUE = 'Got variable value "%s" for variable "%s" of feature flag "%s"'; +export const VALID_DATAFILE = 'Datafile is valid.'; +export const VALID_USER_PROFILE_SERVICE = 'Valid user profile service provided.'; +export const VARIATION_REMOVED_FOR_USER = 'Variation mapped to experiment %s has been removed for user %s.'; + +export const VALID_BUCKETING_ID = 'BucketingId is valid: "%s"'; +export const EVALUATING_AUDIENCE = 'Starting to evaluate audience "%s" with conditions: %s.'; +export const AUDIENCE_EVALUATION_RESULT = 'Audience "%s" evaluated to %s.'; +export const MISSING_ATTRIBUTE_VALUE = + 'Audience condition %s evaluated to UNKNOWN because no value was passed for user attribute "%s".'; +export const UNEXPECTED_TYPE_NULL = + 'Audience condition %s evaluated to UNKNOWN because a null value was passed for user attribute "%s".'; +export const UPDATED_OPTIMIZELY_CONFIG = 'Updated Optimizely config to revision %s (project id %s)'; +export const ADDING_AUTHORIZATION_HEADER_WITH_BEARER_TOKEN = 'Adding Authorization header with Bearer Token'; +export const MAKING_DATAFILE_REQ_TO_URL_WITH_HEADERS = 'Making datafile request to url %s with headers: %s'; +export const RESPONSE_STATUS_CODE = 'Response status code: %s'; +export const SAVED_LAST_MODIFIED_HEADER_VALUE_FROM_RESPONSE = 'Saved last modified header value from response: %s'; +export const USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT = + 'No experiment %s mapped to user %s in the forced variation map.'; +export const INVALID_EXPERIMENT_KEY_INFO = + 'Experiment key %s is not in datafile. It is either invalid, paused, or archived.'; +export const EVENT_STORE_FULL = 'Event store is full. Not saving event with id %d.'; +export const IGNORE_CMAB_CACHE = 'Ignoring CMAB cache for user %s and rule %s.'; +export const RESET_CMAB_CACHE = 'Resetting CMAB cache for user %s and rule %s.'; +export const INVALIDATE_CMAB_CACHE = 'Invalidating CMAB cache for user %s and rule %s.'; +export const CMAB_CACHE_HIT = 'Cache hit for user %s and rule %s.'; +export const CMAB_CACHE_ATTRIBUTES_MISMATCH = 'CMAB cache attributes mismatch for user %s and rule %s, fetching new decision.'; +export const CMAB_CACHE_MISS = 'Cache miss for user %s and rule %s.'; + +export const messages: string[] = []; diff --git a/lib/message/message_resolver.ts b/lib/message/message_resolver.ts new file mode 100644 index 000000000..07a0cefdf --- /dev/null +++ b/lib/message/message_resolver.ts @@ -0,0 +1,20 @@ +import { messages as infoMessages } from 'log_message'; +import { messages as errorMessages } from 'error_message'; + +export interface MessageResolver { + resolve(baseMessage: string): string; +} + +export const infoResolver: MessageResolver = { + resolve(baseMessage: string): string { + const messageNum = parseInt(baseMessage); + return infoMessages[messageNum] || baseMessage; + } +}; + +export const errorResolver: MessageResolver = { + resolve(baseMessage: string): string { + const messageNum = parseInt(baseMessage); + return errorMessages[messageNum] || baseMessage; + } +}; diff --git a/lib/notification_center/index.spec.ts b/lib/notification_center/index.spec.ts new file mode 100644 index 000000000..4ba54a0c3 --- /dev/null +++ b/lib/notification_center/index.spec.ts @@ -0,0 +1,606 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, beforeEach, it, vi, expect } from 'vitest'; +import { createNotificationCenter, DefaultNotificationCenter } from './'; +import { + ActivateListenerPayload, + DecisionListenerPayload, + LogEventListenerPayload, + NOTIFICATION_TYPES, + TrackListenerPayload, + OptimizelyConfigUpdateListenerPayload, +} from './type'; +import { getMockLogger } from '../tests/mock/mock_logger'; +import { LoggerFacade } from '../logging/logger'; + +describe('addNotificationListener', () => { + let logger: LoggerFacade; + let notificationCenterInstance: DefaultNotificationCenter; + + beforeEach(() => { + logger = getMockLogger(); + notificationCenterInstance = createNotificationCenter({ logger }); + }); + + it('should return -1 if notification type is not a valid type', () => { + const INVALID_LISTENER_TYPE = 'INVALID_LISTENER_TYPE' as const; + const mockFn = vi.fn(); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const listenerId = notificationCenterInstance.addNotificationListener(INVALID_LISTENER_TYPE, mockFn); + + expect(listenerId).toBe(-1); + }); + + it('should return an id (listernId) > 0 of the notification listener if callback is not already added', () => { + const activateCallback = vi.fn(); + const decisionCallback = vi.fn(); + const logEventCallback = vi.fn(); + const configUpdateCallback = vi.fn(); + const trackCallback = vi.fn(); + // store a listenerId for each type + const activateListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.ACTIVATE, + activateCallback + ); + const decisionListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.DECISION, + decisionCallback + ); + const logEventListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.LOG_EVENT, + logEventCallback + ); + const configUpdateListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallback + ); + const trackListenerId = notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallback); + + expect(activateListenerId).toBeGreaterThan(0); + expect(decisionListenerId).toBeGreaterThan(0); + expect(logEventListenerId).toBeGreaterThan(0); + expect(configUpdateListenerId).toBeGreaterThan(0); + expect(trackListenerId).toBeGreaterThan(0); + }); +}); + +describe('removeNotificationListener', () => { + let logger: LoggerFacade; + let notificationCenterInstance: DefaultNotificationCenter; + + beforeEach(() => { + logger = getMockLogger(); + notificationCenterInstance = createNotificationCenter({ logger }); + }); + + it('should return false if listernId does not exist', () => { + const notListenerId = notificationCenterInstance.removeNotificationListener(5); + + expect(notListenerId).toBe(false); + }); + + it('should return true when eixsting listener is removed', () => { + const activateCallback = vi.fn(); + const decisionCallback = vi.fn(); + const logEventCallback = vi.fn(); + const configUpdateCallback = vi.fn(); + const trackCallback = vi.fn(); + // add listeners for each type + const activateListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.ACTIVATE, + activateCallback + ); + const decisionListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.DECISION, + decisionCallback + ); + const logEventListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.LOG_EVENT, + logEventCallback + ); + const configListenerId = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallback + ); + const trackListenerId = notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallback); + // remove listeners for each type + const activateListenerRemoved = notificationCenterInstance.removeNotificationListener(activateListenerId); + const decisionListenerRemoved = notificationCenterInstance.removeNotificationListener(decisionListenerId); + const logEventListenerRemoved = notificationCenterInstance.removeNotificationListener(logEventListenerId); + const trackListenerRemoved = notificationCenterInstance.removeNotificationListener(trackListenerId); + const configListenerRemoved = notificationCenterInstance.removeNotificationListener(configListenerId); + + expect(activateListenerRemoved).toBe(true); + expect(decisionListenerRemoved).toBe(true); + expect(logEventListenerRemoved).toBe(true); + expect(trackListenerRemoved).toBe(true); + expect(configListenerRemoved).toBe(true); + }); + it('should only remove the specified listener', () => { + const activateCallbackSpy1 = vi.fn(); + const activateCallbackSpy2 = vi.fn(); + const decisionCallbackSpy1 = vi.fn(); + const decisionCallbackSpy2 = vi.fn(); + const logEventCallbackSpy1 = vi.fn(); + const logEventCallbackSpy2 = vi.fn(); + const configUpdateCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy2 = vi.fn(); + const trackCallbackSpy1 = vi.fn(); + const trackCallbackSpy2 = vi.fn(); + // register listeners for each type + const activateListenerId1 = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.ACTIVATE, + activateCallbackSpy1 + ); + const decisionListenerId1 = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.DECISION, + decisionCallbackSpy1 + ); + const logeventlistenerId1 = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.LOG_EVENT, + logEventCallbackSpy1 + ); + const configUpdateListenerId1 = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + const trackListenerId1 = notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.TRACK, + trackCallbackSpy1 + ); + // register second listeners for each type + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy2); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy2 + ); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy2); + // remove first listener + const activateListenerRemoved1 = notificationCenterInstance.removeNotificationListener(activateListenerId1); + const decisionListenerRemoved1 = notificationCenterInstance.removeNotificationListener(decisionListenerId1); + const logEventListenerRemoved1 = notificationCenterInstance.removeNotificationListener(logeventlistenerId1); + const configUpdateListenerRemoved1 = notificationCenterInstance.removeNotificationListener(configUpdateListenerId1); + const trackListenerRemoved1 = notificationCenterInstance.removeNotificationListener(trackListenerId1); + // send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(activateListenerRemoved1).toBe(true); + expect(activateCallbackSpy1).not.toHaveBeenCalled(); + expect(activateCallbackSpy2).toHaveBeenCalledTimes(1); + expect(decisionListenerRemoved1).toBe(true); + expect(decisionCallbackSpy1).not.toHaveBeenCalled(); + expect(decisionCallbackSpy2).toHaveBeenCalledTimes(1); + expect(logEventListenerRemoved1).toBe(true); + expect(logEventCallbackSpy1).not.toHaveBeenCalled(); + expect(logEventCallbackSpy2).toHaveBeenCalledTimes(1); + expect(configUpdateListenerRemoved1).toBe(true); + expect(configUpdateCallbackSpy1).not.toHaveBeenCalled(); + expect(configUpdateCallbackSpy2).toHaveBeenCalledTimes(1); + expect(trackListenerRemoved1).toBe(true); + expect(trackCallbackSpy1).not.toHaveBeenCalled(); + expect(trackCallbackSpy2).toHaveBeenCalledTimes(1); + }); +}); + +describe('clearAllNotificationListeners', () => { + let logger: LoggerFacade; + let notificationCenterInstance: DefaultNotificationCenter; + + beforeEach(() => { + logger = getMockLogger(); + notificationCenterInstance = createNotificationCenter({ logger }); + }); + + it('should remove all notification listeners for all types', () => { + const activateCallbackSpy1 = vi.fn(); + const decisionCallbackSpy1 = vi.fn(); + const logEventCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy1 = vi.fn(); + const trackCallbackSpy1 = vi.fn(); + // add a listener for each notification type + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + // remove all listeners + notificationCenterInstance.clearAllNotificationListeners(); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(activateCallbackSpy1).not.toHaveBeenCalled(); + expect(decisionCallbackSpy1).not.toHaveBeenCalled(); + expect(logEventCallbackSpy1).not.toHaveBeenCalled(); + expect(configUpdateCallbackSpy1).not.toHaveBeenCalled(); + expect(trackCallbackSpy1).not.toHaveBeenCalled(); + }); +}); + +describe('clearNotificationListeners', () => { + let logger: LoggerFacade; + let notificationCenterInstance: DefaultNotificationCenter; + + beforeEach(() => { + logger = getMockLogger(); + notificationCenterInstance = createNotificationCenter({ logger }); + }); + + it('should remove all notification listeners for the ACTIVATE type', () => { + const activateCallbackSpy1 = vi.fn(); + const activateCallbackSpy2 = vi.fn(); + //add 2 different listeners for ACTIVATE + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy2); + // remove ACTIVATE listeners + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.ACTIVATE); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + + expect(activateCallbackSpy1).not.toHaveBeenCalled(); + expect(activateCallbackSpy2).not.toHaveBeenCalled(); + }); + + it('should remove all notification listeners for the DECISION type', () => { + const decisionCallbackSpy1 = vi.fn(); + const decisionCallbackSpy2 = vi.fn(); + //add 2 different listeners for DECISION + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy2); + // remove DECISION listeners + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.DECISION); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + + expect(decisionCallbackSpy1).not.toHaveBeenCalled(); + expect(decisionCallbackSpy2).not.toHaveBeenCalled(); + }); + + it('should remove all notification listeners for the LOG_EVENT type', () => { + const logEventCallbackSpy1 = vi.fn(); + const logEventCallbackSpy2 = vi.fn(); + //add 2 different listeners for LOG_EVENT + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy2); + // remove LOG_EVENT listeners + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.LOG_EVENT); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + + expect(logEventCallbackSpy1).not.toHaveBeenCalled(); + expect(logEventCallbackSpy2).not.toHaveBeenCalled(); + }); + + it('should remove all notification listeners for the OPTIMIZELY_CONFIG_UPDATE type', () => { + const configUpdateCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy2 = vi.fn(); + //add 2 different listeners for OPTIMIZELY_CONFIG_UPDATE + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy2 + ); + // remove OPTIMIZELY_CONFIG_UPDATE listeners + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); + // trigger send notifications + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + + expect(configUpdateCallbackSpy1).not.toHaveBeenCalled(); + expect(configUpdateCallbackSpy2).not.toHaveBeenCalled(); + }); + + it('should remove all notification listeners for the TRACK type', () => { + const trackCallbackSpy1 = vi.fn(); + const trackCallbackSpy2 = vi.fn(); + //add 2 different listeners for TRACK + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy2); + // remove TRACK listeners + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.TRACK); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(trackCallbackSpy1).not.toHaveBeenCalled(); + expect(trackCallbackSpy2).not.toHaveBeenCalled(); + }); + + it('should only remove ACTIVATE type listeners and not any other types', () => { + const activateCallbackSpy1 = vi.fn(); + const activateCallbackSpy2 = vi.fn(); + const decisionCallbackSpy1 = vi.fn(); + const logEventCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy1 = vi.fn(); + const trackCallbackSpy1 = vi.fn(); + //add 2 different listeners for ACTIVATE + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy2); + // add a listener for each notification type + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + // remove only ACTIVATE type + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.ACTIVATE); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(activateCallbackSpy1).not.toHaveBeenCalled(); + expect(activateCallbackSpy2).not.toHaveBeenCalled(); + expect(decisionCallbackSpy1).toHaveBeenCalledTimes(1); + expect(logEventCallbackSpy1).toHaveBeenCalledTimes(1); + expect(configUpdateCallbackSpy1).toHaveBeenCalledTimes(1); + expect(trackCallbackSpy1).toHaveBeenCalledTimes(1); + }); + + it('should only remove DECISION type listeners and not any other types', () => { + const decisionCallbackSpy1 = vi.fn(); + const decisionCallbackSpy2 = vi.fn(); + const activateCallbackSpy1 = vi.fn(); + const logEventCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy1 = vi.fn(); + const trackCallbackSpy1 = vi.fn(); + // add 2 different listeners for DECISION + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy2); + // add a listener for each notification type + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + // remove only DECISION type + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.DECISION); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(decisionCallbackSpy1).not.toHaveBeenCalled(); + expect(decisionCallbackSpy2).not.toHaveBeenCalled(); + expect(activateCallbackSpy1).toHaveBeenCalledTimes(1); + expect(logEventCallbackSpy1).toHaveBeenCalledTimes(1); + expect(configUpdateCallbackSpy1).toHaveBeenCalledTimes(1); + expect(trackCallbackSpy1).toHaveBeenCalledTimes(1); + }); + + it('should only remove LOG_EVENT type listeners and not any other types', () => { + const logEventCallbackSpy1 = vi.fn(); + const logEventCallbackSpy2 = vi.fn(); + const activateCallbackSpy1 = vi.fn(); + const decisionCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy1 = vi.fn(); + const trackCallbackSpy1 = vi.fn(); + // add 2 different listeners for LOG_EVENT + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy2); + // add a listener for each notification type + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + // remove only LOG_EVENT type + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.LOG_EVENT); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(logEventCallbackSpy1).not.toHaveBeenCalled(); + expect(logEventCallbackSpy2).not.toHaveBeenCalled(); + expect(activateCallbackSpy1).toHaveBeenCalledTimes(1); + expect(decisionCallbackSpy1).toHaveBeenCalledTimes(1); + expect(configUpdateCallbackSpy1).toHaveBeenCalledTimes(1); + expect(trackCallbackSpy1).toHaveBeenCalledTimes(1); + }); + + it('should only remove OPTIMIZELY_CONFIG_UPDATE type listeners and not any other types', () => { + const configUpdateCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy2 = vi.fn(); + const activateCallbackSpy1 = vi.fn(); + const decisionCallbackSpy1 = vi.fn(); + const logEventCallbackSpy1 = vi.fn(); + const trackCallbackSpy1 = vi.fn(); + // add 2 different listeners for OPTIMIZELY_CONFIG_UPDATE + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy2 + ); + // add a listener for each notification type + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + // remove only OPTIMIZELY_CONFIG_UPDATE type + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(configUpdateCallbackSpy1).not.toHaveBeenCalled(); + expect(configUpdateCallbackSpy2).not.toHaveBeenCalled(); + expect(activateCallbackSpy1).toHaveBeenCalledTimes(1); + expect(decisionCallbackSpy1).toHaveBeenCalledTimes(1); + expect(logEventCallbackSpy1).toHaveBeenCalledTimes(1); + expect(trackCallbackSpy1).toHaveBeenCalledTimes(1); + }); + + it('should only remove TRACK type listeners and not any other types', () => { + const trackCallbackSpy1 = vi.fn(); + const trackCallbackSpy2 = vi.fn(); + const activateCallbackSpy1 = vi.fn(); + const decisionCallbackSpy1 = vi.fn(); + const logEventCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy1 = vi.fn(); + // add 2 different listeners for TRACK + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy2); + // add a listener for each notification type + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + // remove only TRACK type + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.TRACK); + // trigger send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {} as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {} as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {} as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + ({} as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {} as TrackListenerPayload); + + expect(trackCallbackSpy1).not.toHaveBeenCalled(); + expect(trackCallbackSpy2).not.toHaveBeenCalled(); + expect(activateCallbackSpy1).toHaveBeenCalledTimes(1); + expect(decisionCallbackSpy1).toHaveBeenCalledTimes(1); + expect(logEventCallbackSpy1).toHaveBeenCalledTimes(1); + expect(configUpdateCallbackSpy1).toHaveBeenCalledTimes(1); + }); +}); + +describe('sendNotifications', () => { + let logger: LoggerFacade; + let notificationCenterInstance: DefaultNotificationCenter; + + beforeEach(() => { + logger = getMockLogger(); + notificationCenterInstance = createNotificationCenter({ logger }); + }); + it('should call the listener callback with exact arguments', () => { + const activateCallbackSpy1 = vi.fn(); + const decisionCallbackSpy1 = vi.fn(); + const logEventCallbackSpy1 = vi.fn(); + const configUpdateCallbackSpy1 = vi.fn(); + const trackCallbackSpy1 = vi.fn(); + // listener object data for each type + const activateData = { + experiment: {}, + userId: '', + attributes: {}, + variation: {}, + logEvent: {}, + }; + const decisionData = { + type: '', + userId: 'use1', + attributes: {}, + decisionInfo: {}, + }; + const logEventData = { + url: '', + httpVerb: '', + params: {}, + }; + const configUpdateData = {}; + const trackData = { + eventKey: '', + userId: '', + attributes: {}, + eventTags: {}, + }; + // add listeners + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + configUpdateCallbackSpy1 + ); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + // send notifications + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, activateData as ActivateListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, decisionData as DecisionListenerPayload); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, logEventData as LogEventListenerPayload); + notificationCenterInstance.sendNotifications( + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + (configUpdateData as unknown) as OptimizelyConfigUpdateListenerPayload + ); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, trackData as TrackListenerPayload); + + expect(activateCallbackSpy1).toHaveBeenCalledWith(activateData); + expect(decisionCallbackSpy1).toHaveBeenCalledWith(decisionData); + expect(logEventCallbackSpy1).toHaveBeenCalledWith(logEventData); + expect(configUpdateCallbackSpy1).toHaveBeenCalledWith(configUpdateData); + expect(trackCallbackSpy1).toHaveBeenCalledWith(trackData); + }); +}); diff --git a/packages/optimizely-sdk/lib/core/notification_center/index.tests.js b/lib/notification_center/index.tests.js similarity index 59% rename from packages/optimizely-sdk/lib/core/notification_center/index.tests.js rename to lib/notification_center/index.tests.js index 79dc2fd5f..11e6da2bb 100644 --- a/packages/optimizely-sdk/lib/core/notification_center/index.tests.js +++ b/lib/notification_center/index.tests.js @@ -1,45 +1,49 @@ -/**************************************************************************** - * Copyright 2020, Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ +/** + * Copyright 2020, 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import sinon from 'sinon'; import { assert } from 'chai'; import { createNotificationCenter } from './'; -import * as enums from '../../utils/enums'; -import { createLogger } from '../../plugins/logger'; -import errorHandler from '../../plugins/error_handler'; +import * as enums from '../utils/enums'; +import { NOTIFICATION_TYPES } from './type'; var LOG_LEVEL = enums.LOG_LEVEL; +var createLogger = () => ({ + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + child: () => createLogger(), +}) + describe('lib/core/notification_center', function() { describe('APIs', function() { var mockLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); - var mockErrorHandler = errorHandler.handleError; var mockLoggerStub; - var mockErrorHandlerStub; + var notificationCenterInstance; var sandbox; beforeEach(function() { sandbox = sinon.sandbox.create(); mockLoggerStub = sandbox.stub(mockLogger, 'log'); - mockErrorHandlerStub = sandbox.stub(mockErrorHandler, 'handleError'); notificationCenterInstance = createNotificationCenter({ logger: mockLoggerStub, - errorHandler: mockErrorHandlerStub, }); }); @@ -62,47 +66,6 @@ describe('lib/core/notification_center', function() { }); context('the listener type is a valid type', function() { - it('should return -1 if that same callback is already added', function() { - var activateCallback; - var decisionCallback; - var logEventCallback; - var configUpdateCallback; - var trackCallback; - // add a listener for each type - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallback); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallback); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallback); - notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, - configUpdateCallback - ); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallback); - // assertions - assert.strictEqual( - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallback), - -1 - ); - assert.strictEqual( - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallback), - -1 - ); - assert.strictEqual( - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallback), - -1 - ); - assert.strictEqual( - notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, - configUpdateCallback - ), - -1 - ); - assert.strictEqual( - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallback), - -1 - ); - }); - it('should return an id (listenerId) > 0 of the notification listener if callback is not already added', function() { var activateCallback; var decisionCallback; @@ -111,23 +74,23 @@ describe('lib/core/notification_center', function() { var trackCallback; // store a listenerId for each type var activateListenerId = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.ACTIVATE, + NOTIFICATION_TYPES.ACTIVATE, activateCallback ); var decisionListenerId = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.DECISION, + NOTIFICATION_TYPES.DECISION, decisionCallback ); var logEventListenerId = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.LOG_EVENT, + NOTIFICATION_TYPES.LOG_EVENT, logEventCallback ); var configUpdateListenerId = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallback ); var trackListenerId = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.TRACK, + NOTIFICATION_TYPES.TRACK, trackCallback ); // assertions @@ -157,23 +120,23 @@ describe('lib/core/notification_center', function() { var trackCallback; // add listeners for each type var activateListenerId = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.ACTIVATE, + NOTIFICATION_TYPES.ACTIVATE, activateCallback ); var decisionListenerId = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.DECISION, + NOTIFICATION_TYPES.DECISION, decisionCallback ); var logEventListenerId = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.LOG_EVENT, + NOTIFICATION_TYPES.LOG_EVENT, logEventCallback ); var configListenerId = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallback ); var trackListenerId = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.TRACK, + NOTIFICATION_TYPES.TRACK, trackCallback ); // remove listeners for each type @@ -204,34 +167,34 @@ describe('lib/core/notification_center', function() { var trackCallbackSpy2 = sinon.spy(); // register listeners for each type var activateListenerId1 = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.ACTIVATE, + NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1 ); var decisionListenerId1 = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.DECISION, + NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1 ); var logeventlistenerId1 = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.LOG_EVENT, + NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1 ); var configUpdateListenerId1 = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy1 ); var trackListenerId1 = notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.TRACK, + NOTIFICATION_TYPES.TRACK, trackCallbackSpy1 ); // register second listeners for each type - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy2); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallbackSpy2); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy2); notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy2 ); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy2); // remove first listener var activateListenerRemoved1 = notificationCenterInstance.removeNotificationListener(activateListenerId1); var decisionListenerRemoved1 = notificationCenterInstance.removeNotificationListener(decisionListenerId1); @@ -241,11 +204,11 @@ describe('lib/core/notification_center', function() { ); var trackListenerRemoved1 = notificationCenterInstance.removeNotificationListener(trackListenerId1); // send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.ACTIVATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.DECISION, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.LOG_EVENT, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.TRACK, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {}); // Assertions assert.strictEqual(activateListenerRemoved1, true); sinon.assert.notCalled(activateCallbackSpy1); @@ -274,22 +237,22 @@ describe('lib/core/notification_center', function() { var configUpdateCallbackSpy1 = sinon.spy(); var trackCallbackSpy1 = sinon.spy(); // add a listener for each notification type - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy1 ); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); // remove all listeners notificationCenterInstance.clearAllNotificationListeners(); // trigger send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.ACTIVATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.DECISION, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.LOG_EVENT, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.TRACK, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {}); // check that none of the now removed listeners were called sinon.assert.notCalled(activateCallbackSpy1); sinon.assert.notCalled(decisionCallbackSpy1); @@ -305,12 +268,12 @@ describe('lib/core/notification_center', function() { var activateCallbackSpy1 = sinon.spy(); var activateCallbackSpy2 = sinon.spy(); //add 2 different listeners for ACTIVATE - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy2); // remove ACTIVATE listeners - notificationCenterInstance.clearNotificationListeners(enums.NOTIFICATION_TYPES.ACTIVATE); + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.ACTIVATE); // trigger send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.ACTIVATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {}); // check that none of the ACTIVATE listeners were called sinon.assert.notCalled(activateCallbackSpy1); sinon.assert.notCalled(activateCallbackSpy2); @@ -320,12 +283,12 @@ describe('lib/core/notification_center', function() { var decisionCallbackSpy1 = sinon.spy(); var decisionCallbackSpy2 = sinon.spy(); //add 2 different listeners for DECISION - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy2); // remove DECISION listeners - notificationCenterInstance.clearAllNotificationListeners(enums.NOTIFICATION_TYPES.DECISION); + notificationCenterInstance.clearAllNotificationListeners(NOTIFICATION_TYPES.DECISION); // trigger send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.DECISION, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {}); // check that none of the DECISION listeners were called sinon.assert.notCalled(decisionCallbackSpy1); sinon.assert.notCalled(decisionCallbackSpy2); @@ -335,12 +298,12 @@ describe('lib/core/notification_center', function() { var logEventCallbackSpy1 = sinon.spy(); var logEventCallbackSpy2 = sinon.spy(); //add 2 different listeners for LOG_EVENT - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy2); // remove LOG_EVENT listeners - notificationCenterInstance.clearAllNotificationListeners(enums.NOTIFICATION_TYPES.LOG_EVENT); + notificationCenterInstance.clearAllNotificationListeners(NOTIFICATION_TYPES.LOG_EVENT); // trigger send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.LOG_EVENT, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {}); // check that none of the LOG_EVENT listeners were called sinon.assert.notCalled(logEventCallbackSpy1); sinon.assert.notCalled(logEventCallbackSpy2); @@ -351,17 +314,17 @@ describe('lib/core/notification_center', function() { var configUpdateCallbackSpy2 = sinon.spy(); //add 2 different listeners for OPTIMIZELY_CONFIG_UPDATE notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy1 ); notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy2 ); // remove OPTIMIZELY_CONFIG_UPDATE listeners - notificationCenterInstance.clearAllNotificationListeners(enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); + notificationCenterInstance.clearAllNotificationListeners(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); // trigger send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); // check that none of the OPTIMIZELY_CONFIG_UPDATE listeners were called sinon.assert.notCalled(configUpdateCallbackSpy1); sinon.assert.notCalled(configUpdateCallbackSpy2); @@ -371,12 +334,12 @@ describe('lib/core/notification_center', function() { var trackCallbackSpy1 = sinon.spy(); var trackCallbackSpy2 = sinon.spy(); //add 2 different listeners for TRACK - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy2); // remove TRACK listeners - notificationCenterInstance.clearAllNotificationListeners(enums.NOTIFICATION_TYPES.TRACK); + notificationCenterInstance.clearAllNotificationListeners(NOTIFICATION_TYPES.TRACK); // trigger send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.TRACK, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {}); // check that none of the TRACK listeners were called sinon.assert.notCalled(trackCallbackSpy1); sinon.assert.notCalled(trackCallbackSpy2); @@ -392,24 +355,24 @@ describe('lib/core/notification_center', function() { var configUpdateCallbackSpy1 = sinon.spy(); var trackCallbackSpy1 = sinon.spy(); //add 2 different listeners for ACTIVATE - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy2); // add a listener for each notification type - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy1 ); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); // remove only ACTIVATE type - notificationCenterInstance.clearNotificationListeners(enums.NOTIFICATION_TYPES.ACTIVATE); + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.ACTIVATE); // trigger send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.ACTIVATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.DECISION, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.LOG_EVENT, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.TRACK, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {}); // check that ACTIVATE listeners were note called sinon.assert.notCalled(activateCallbackSpy1); sinon.assert.notCalled(activateCallbackSpy2); @@ -428,24 +391,24 @@ describe('lib/core/notification_center', function() { var configUpdateCallbackSpy1 = sinon.spy(); var trackCallbackSpy1 = sinon.spy(); // add 2 different listeners for DECISION - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy2); // add a listener for each notification type - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy1 ); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); // remove only DECISION type - notificationCenterInstance.clearNotificationListeners(enums.NOTIFICATION_TYPES.DECISION); + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.DECISION); // trigger send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.ACTIVATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.DECISION, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.LOG_EVENT, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.TRACK, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {}); // check that DECISION listeners were not called sinon.assert.notCalled(decisionCallbackSpy1); sinon.assert.notCalled(decisionCallbackSpy2); @@ -464,24 +427,24 @@ describe('lib/core/notification_center', function() { var configUpdateCallbackSpy1 = sinon.spy(); var trackCallbackSpy1 = sinon.spy(); // add 2 different listeners for LOG_EVENT - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy2); // add a listener for each notification type - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy1 ); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); // remove only LOG_EVENT type - notificationCenterInstance.clearNotificationListeners(enums.NOTIFICATION_TYPES.LOG_EVENT); + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.LOG_EVENT); // trigger send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.ACTIVATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.DECISION, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.LOG_EVENT, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.TRACK, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {}); // check that LOG_EVENT listeners were not called sinon.assert.notCalled(logEventCallbackSpy1); sinon.assert.notCalled(logEventCallbackSpy2); @@ -501,26 +464,26 @@ describe('lib/core/notification_center', function() { var trackCallbackSpy1 = sinon.spy(); // add 2 different listeners for OPTIMIZELY_CONFIG_UPDATE notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy1 ); notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy2 ); // add a listener for each notification type - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); // remove only OPTIMIZELY_CONFIG_UPDATE type - notificationCenterInstance.clearNotificationListeners(enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); // trigger send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.ACTIVATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.DECISION, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.LOG_EVENT, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.TRACK, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {}); // check that OPTIMIZELY_CONFIG_UPDATE listeners were not called sinon.assert.notCalled(configUpdateCallbackSpy1); sinon.assert.notCalled(configUpdateCallbackSpy2); @@ -539,24 +502,24 @@ describe('lib/core/notification_center', function() { var logEventCallbackSpy1 = sinon.spy(); var configUpdateCallbackSpy1 = sinon.spy(); // add 2 different listeners for TRACK - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallbackSpy2); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy2); // add a listener for each notification type - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy1 ); // remove only TRACK type - notificationCenterInstance.clearNotificationListeners(enums.NOTIFICATION_TYPES.TRACK); + notificationCenterInstance.clearNotificationListeners(NOTIFICATION_TYPES.TRACK); // trigger send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.ACTIVATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.DECISION, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.LOG_EVENT, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.TRACK, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, {}); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, {}); // check that TRACK listeners were not called sinon.assert.notCalled(trackCallbackSpy1); sinon.assert.notCalled(trackCallbackSpy2); @@ -604,23 +567,23 @@ describe('lib/core/notification_center', function() { eventTags: {}, }; // add listeners - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.LOG_EVENT, logEventCallbackSpy1); notificationCenterInstance.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateCallbackSpy1 ); - notificationCenterInstance.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); + notificationCenterInstance.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackCallbackSpy1); // send notifications - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.ACTIVATE, activateData); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.DECISION, decisionData); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.LOG_EVENT, logEventData); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, activateData); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.DECISION, decisionData); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, logEventData); notificationCenterInstance.sendNotifications( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, configUpdateData ); - notificationCenterInstance.sendNotifications(enums.NOTIFICATION_TYPES.TRACK, trackData); + notificationCenterInstance.sendNotifications(NOTIFICATION_TYPES.TRACK, trackData); // assertions sinon.assert.calledWithExactly(activateCallbackSpy1, activateData); sinon.assert.calledWithExactly(decisionCallbackSpy1, decisionData); diff --git a/lib/notification_center/index.ts b/lib/notification_center/index.ts new file mode 100644 index 000000000..7b17ba658 --- /dev/null +++ b/lib/notification_center/index.ts @@ -0,0 +1,164 @@ +/** + * Copyright 2020, 2022, 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LoggerFacade } from '../logging/logger'; +import { objectValues } from '../utils/fns'; + +import { NOTIFICATION_TYPES } from './type'; +import { NotificationType, NotificationPayload } from './type'; +import { Consumer, Fn } from '../utils/type'; +import { EventEmitter } from '../utils/event_emitter/event_emitter'; +import { NOTIFICATION_LISTENER_EXCEPTION } from 'error_message'; +import { ErrorReporter } from '../error/error_reporter'; +import { ErrorNotifier } from '../error/error_notifier'; + +interface NotificationCenterOptions { + logger?: LoggerFacade; + errorNotifier?: ErrorNotifier; +} + +export interface NotificationCenter { + addNotificationListener<N extends NotificationType>( + notificationType: N, + callback: Consumer<NotificationPayload[N]> + ): number + removeNotificationListener(listenerId: number): boolean; + clearAllNotificationListeners(): void; + clearNotificationListeners(notificationType: NotificationType): void; +} + +export interface NotificationSender { + sendNotifications<N extends NotificationType>( + notificationType: N, + notificationData: NotificationPayload[N] + ): void; +} + +/** + * NotificationCenter allows registration and triggering of callback functions using + * notification event types defined in NOTIFICATION_TYPES of utils/enums/index.js: + * - ACTIVATE: An impression event will be sent to Optimizely. + * - TRACK a conversion event will be sent to Optimizely + */ +export class DefaultNotificationCenter implements NotificationCenter, NotificationSender { + private errorReporter: ErrorReporter; + + private removerId = 1; + private eventEmitter: EventEmitter<NotificationPayload> = new EventEmitter(); + private removers: Map<number, Fn> = new Map(); + + /** + * @constructor + * @param {NotificationCenterOptions} options + * @param {LogHandler} options.logger An instance of a logger to log messages with + * @param {ErrorHandler} options.errorHandler An instance of errorHandler to handle any unexpected error + */ + constructor(options: NotificationCenterOptions) { + this.errorReporter = new ErrorReporter(options.logger, options.errorNotifier); + } + + /** + * Add a notification callback to the notification center + * @param {string} notificationType One of the values from NOTIFICATION_TYPES in utils/enums/index.js + * @param {NotificationListener<T>} callback Function that will be called when the event is triggered + * @returns {number} If the callback was successfully added, returns a listener ID which can be used + * to remove the callback by calling removeNotificationListener. The ID is a number greater than 0. + * If there was an error and the listener was not added, addNotificationListener returns -1. This + * can happen if the first argument is not a valid notification type, or if the same callback + * function was already added as a listener by a prior call to this function. + */ + addNotificationListener<N extends NotificationType>( + notificationType: N, + callback: Consumer<NotificationPayload[N]> + ): number { + const notificationTypeValues: string[] = objectValues(NOTIFICATION_TYPES); + const isNotificationTypeValid = notificationTypeValues.indexOf(notificationType) > -1; + if (!isNotificationTypeValid) { + return -1; + } + + const returnId = this.removerId++; + const remover = this.eventEmitter.on( + notificationType, this.wrapWithErrorReporting(notificationType, callback)); + this.removers.set(returnId, remover); + return returnId; + } + + private wrapWithErrorReporting<N extends NotificationType>( + notificationType: N, + callback: Consumer<NotificationPayload[N]> + ): Consumer<NotificationPayload[N]> { + return (notificationData: NotificationPayload[N]) => { + try { + callback(notificationData); + } catch (ex: any) { + const message = ex instanceof Error ? ex.message : String(ex); + this.errorReporter.report(NOTIFICATION_LISTENER_EXCEPTION, notificationType, message); + } + }; + } + + /** + * Remove a previously added notification callback + * @param {number} listenerId ID of listener to be removed + * @returns {boolean} Returns true if the listener was found and removed, and false + * otherwise. + */ + removeNotificationListener(listenerId: number): boolean { + const remover = this.removers.get(listenerId); + if (remover) { + remover(); + return true; + } + return false + } + + /** + * Removes all previously added notification listeners, for all notification types + */ + clearAllNotificationListeners(): void { + this.eventEmitter.removeAllListeners(); + } + + /** + * Remove all previously added notification listeners for the argument type + * @param {NotificationType} notificationType One of NotificationType + */ + clearNotificationListeners(notificationType: NotificationType): void { + this.eventEmitter.removeListeners(notificationType); + } + + /** + * Fires notifications for the argument type. All registered callbacks for this type will be + * called. The notificationData object will be passed on to callbacks called. + * @param {NotificationType} notificationType One of NotificationType + * @param {Object} notificationData Will be passed to callbacks called + */ + sendNotifications<N extends NotificationType>( + notificationType: N, + notificationData: NotificationPayload[N] + ): void { + this.eventEmitter.emit(notificationType, notificationData); + } +} + +/** + * Create an instance of NotificationCenter + * @param {NotificationCenterOptions} options + * @returns {NotificationCenter} An instance of NotificationCenter + */ +export function createNotificationCenter(options: NotificationCenterOptions): DefaultNotificationCenter { + return new DefaultNotificationCenter(options); +} diff --git a/lib/notification_center/type.ts b/lib/notification_center/type.ts new file mode 100644 index 000000000..01adc56e5 --- /dev/null +++ b/lib/notification_center/type.ts @@ -0,0 +1,153 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LogEvent } from '../event_processor/event_dispatcher/event_dispatcher'; +import { + EventTags, + Experiment, + FeatureVariableValue, + Holdout, + UserAttributes, + VariableType, + Variation, +} from '../shared_types'; +import { DecisionSource } from '../utils/enums'; +import { Nullable } from '../utils/type'; +import { holdout, IfActive } from '../feature_toggle'; + +export type UserEventListenerPayload = { + userId: string; + attributes?: UserAttributes; +} + +export type ActivateListenerPayload = UserEventListenerPayload & { + experiment: Experiment | null; + holdout: IfActive<typeof holdout, Holdout | null>; + variation: Variation | null; + logEvent: LogEvent; +} + +export type TrackListenerPayload = UserEventListenerPayload & { + eventKey: string; + eventTags?: EventTags; + logEvent: LogEvent; +} + +export const DECISION_NOTIFICATION_TYPES = { + AB_TEST: 'ab-test', + FEATURE: 'feature', + FEATURE_TEST: 'feature-test', + FEATURE_VARIABLE: 'feature-variable', + ALL_FEATURE_VARIABLES: 'all-feature-variables', + FLAG: 'flag', +} as const; + + +export type DecisionNotificationType = typeof DECISION_NOTIFICATION_TYPES[keyof typeof DECISION_NOTIFICATION_TYPES]; + +export type ExperimentAndVariationInfo = { + experimentKey: string; + variationKey: string; +} + +export type DecisionSourceInfo = Partial<ExperimentAndVariationInfo>; + +export type AbTestDecisonInfo = Nullable<ExperimentAndVariationInfo, 'variationKey'>; + +type FeatureDecisionInfo = { + featureKey: string, + featureEnabled: boolean, + source: DecisionSource, + sourceInfo: DecisionSourceInfo, +} + +export type FeatureTestDecisionInfo = Nullable<ExperimentAndVariationInfo, 'variationKey'>; + +export type FeatureVariableDecisionInfo = { + featureKey: string, + featureEnabled: boolean, + source: DecisionSource, + variableKey: string, + variableValue: FeatureVariableValue, + variableType: VariableType, + sourceInfo: DecisionSourceInfo, +}; + +export type VariablesMap = { [variableKey: string]: unknown } + +export type AllFeatureVariablesDecisionInfo = { + featureKey: string, + featureEnabled: boolean, + source: DecisionSource, + variableValues: VariablesMap, + sourceInfo: DecisionSourceInfo, +}; + +export type FlagDecisionInfo = { + flagKey: string, + enabled: boolean, + variationKey: string | null, + ruleKey: string | null, + variables: VariablesMap, + reasons: string[], + decisionEventDispatched: boolean, + experimentId: string | null, + variationId: string | null, +}; + +export type DecisionInfo = { + [DECISION_NOTIFICATION_TYPES.AB_TEST]: AbTestDecisonInfo; + [DECISION_NOTIFICATION_TYPES.FEATURE]: FeatureDecisionInfo; + [DECISION_NOTIFICATION_TYPES.FEATURE_TEST]: FeatureTestDecisionInfo; + [DECISION_NOTIFICATION_TYPES.FEATURE_VARIABLE]: FeatureVariableDecisionInfo; + [DECISION_NOTIFICATION_TYPES.ALL_FEATURE_VARIABLES]: AllFeatureVariablesDecisionInfo; + [DECISION_NOTIFICATION_TYPES.FLAG]: FlagDecisionInfo; +} + +export type DecisionListenerPayloadForType<T extends DecisionNotificationType> = UserEventListenerPayload & { + type: T; + decisionInfo: DecisionInfo[T]; +} + +export type DecisionListenerPayload = { + [T in DecisionNotificationType]: DecisionListenerPayloadForType<T>; +}[DecisionNotificationType]; + +export type LogEventListenerPayload = LogEvent; + +export type OptimizelyConfigUpdateListenerPayload = undefined; + +export type NotificationPayload = { + ACTIVATE: ActivateListenerPayload; + DECISION: DecisionListenerPayload; + TRACK: TrackListenerPayload; + LOG_EVENT: LogEventListenerPayload; + OPTIMIZELY_CONFIG_UPDATE: OptimizelyConfigUpdateListenerPayload; +}; + +export type NotificationType = keyof NotificationPayload; + +export type NotificationTypeValues = { + [key in NotificationType]: key; +} + +export const NOTIFICATION_TYPES: NotificationTypeValues = { + ACTIVATE: 'ACTIVATE', + DECISION: 'DECISION', + LOG_EVENT: 'LOG_EVENT', + OPTIMIZELY_CONFIG_UPDATE: 'OPTIMIZELY_CONFIG_UPDATE', + TRACK: 'TRACK', +}; diff --git a/lib/odp/constant.ts b/lib/odp/constant.ts new file mode 100644 index 000000000..c33f3f0c9 --- /dev/null +++ b/lib/odp/constant.ts @@ -0,0 +1,28 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export enum ODP_USER_KEY { + VUID = 'vuid', + FS_USER_ID = 'fs_user_id', + FS_USER_ID_ALIAS = 'fs-user-id', +} + +export enum ODP_EVENT_ACTION { + IDENTIFIED = 'identified', + INITIALIZED = 'client_initialized', +} + +export const ODP_DEFAULT_EVENT_TYPE = 'fullstack'; diff --git a/packages/optimizely-sdk/lib/plugins/odp/odp_event.ts b/lib/odp/event_manager/odp_event.ts similarity index 89% rename from packages/optimizely-sdk/lib/plugins/odp/odp_event.ts rename to lib/odp/event_manager/odp_event.ts index 4260cd30d..062798d1b 100644 --- a/packages/optimizely-sdk/lib/plugins/odp/odp_event.ts +++ b/lib/odp/event_manager/odp_event.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,22 +18,22 @@ export class OdpEvent { /** * Type of event (typically "fullstack") */ - public type: string; + type: string; /** * Subcategory of the event type */ - public action: string; + action: string; /** * Key-value map of user identifiers */ - public identifiers: Map<string, string>; + identifiers: Map<string, string>; /** * Event data in a key-value map */ - public data: Map<string, unknown>; + data: Map<string, unknown>; /** * Event to be sent and stored in the Optimizely Data Platform diff --git a/lib/odp/event_manager/odp_event_api_manager.spec.ts b/lib/odp/event_manager/odp_event_api_manager.spec.ts new file mode 100644 index 000000000..04d74ea18 --- /dev/null +++ b/lib/odp/event_manager/odp_event_api_manager.spec.ts @@ -0,0 +1,225 @@ +/** + * Copyright 2022-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, vi } from 'vitest'; + +import { DefaultOdpEventApiManager, eventApiRequestGenerator, LOGGER_NAME, pixelApiRequestGenerator } from './odp_event_api_manager'; +import { OdpEvent } from './odp_event'; +import { OdpConfig } from '../odp_config'; + +const data1 = new Map<string, unknown>(); +data1.set('key11', 'value-1'); +data1.set('key12', true); +data1.set('key13', 3.5); +data1.set('key14', null); + +const data2 = new Map<string, unknown>(); + +data2.set('key2', 'value-2'); + +const ODP_EVENTS = [ + new OdpEvent('t1', 'a1', new Map([['id-key-1', 'id-value-1']]), data1), + new OdpEvent('t2', 'a2', new Map([['id-key-2', 'id-value-2']]), data2), +]; + +const API_KEY = 'test-api-key'; +const API_HOST = 'https://odp.example.com/'; +const PIXEL_URL = 'https://odp.pixel.com/'; + +const odpConfig = new OdpConfig(API_KEY, API_HOST, PIXEL_URL, []); + +import { getMockRequestHandler } from '../../tests/mock/mock_request_handler'; +import { getMockLogger } from '../../tests/mock/mock_logger'; + +describe('DefaultOdpEventApiManager', () => { + it('should set name on the logger passed into the constructor', () => { + const logger = getMockLogger(); + const requestHandler = getMockRequestHandler(); + + const manager = new DefaultOdpEventApiManager(requestHandler, vi.fn(), logger); + + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME); + }); + + it('should set name on the logger set by setLogger', () => { + const logger = getMockLogger(); + const requestHandler = getMockRequestHandler(); + + const manager = new DefaultOdpEventApiManager(requestHandler, vi.fn()); + manager.setLogger(logger); + + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME); + }); + + it('should generate the event request using the correct odp config and event', async () => { + const mockRequestHandler = getMockRequestHandler(); + mockRequestHandler.makeRequest.mockReturnValue({ + responsePromise: Promise.resolve({ + statusCode: 200, + body: '', + headers: {}, + }), + }); + const requestGenerator = vi.fn().mockReturnValue({ + method: 'PATCH', + endpoint: 'https://odp.example.com/v3/events', + headers: { + 'x-api-key': 'test-api', + }, + data: 'event-data', + }); + + const manager = new DefaultOdpEventApiManager(mockRequestHandler, requestGenerator); + manager.sendEvents(odpConfig, ODP_EVENTS); + + expect(requestGenerator.mock.calls[0][0]).toEqual(odpConfig); + expect(requestGenerator.mock.calls[0][1]).toEqual(ODP_EVENTS); + }); + + it('should send the correct request using the request handler', async () => { + const mockRequestHandler = getMockRequestHandler(); + mockRequestHandler.makeRequest.mockReturnValue({ + responsePromise: Promise.resolve({ + statusCode: 200, + body: '', + headers: {}, + }), + }); + const requestGenerator = vi.fn().mockReturnValue({ + method: 'PATCH', + endpoint: 'https://odp.example.com/v3/events', + headers: { + 'x-api-key': 'test-api', + }, + data: 'event-data', + }); + + const manager = new DefaultOdpEventApiManager(mockRequestHandler, requestGenerator); + manager.sendEvents(odpConfig, ODP_EVENTS); + + expect(mockRequestHandler.makeRequest.mock.calls[0][0]).toEqual('https://odp.example.com/v3/events'); + expect(mockRequestHandler.makeRequest.mock.calls[0][1]).toEqual({ + 'x-api-key': 'test-api', + }); + expect(mockRequestHandler.makeRequest.mock.calls[0][2]).toEqual('PATCH'); + expect(mockRequestHandler.makeRequest.mock.calls[0][3]).toEqual('event-data'); + }); + + it('should return a promise that fails if the requestHandler response promise fails', async () => { + const mockRequestHandler = getMockRequestHandler(); + mockRequestHandler.makeRequest.mockReturnValue({ + responsePromise: Promise.reject(new Error('REQUEST_FAILED')), + }); + const requestGenerator = vi.fn().mockReturnValue({ + method: 'PATCH', + endpoint: 'https://odp.example.com/v3/events', + headers: { + 'x-api-key': 'test-api', + }, + data: 'event-data', + }); + + const manager = new DefaultOdpEventApiManager(mockRequestHandler, requestGenerator); + const response = manager.sendEvents(odpConfig, ODP_EVENTS); + + await expect(response).rejects.toThrow(); + }); + + it('should return a promise that resolves with correct response code from the requestHandler', async () => { + const mockRequestHandler = getMockRequestHandler(); + mockRequestHandler.makeRequest.mockReturnValue({ + responsePromise: Promise.resolve({ + statusCode: 226, + body: '', + headers: {}, + }), + }); + const requestGenerator = vi.fn().mockReturnValue({ + method: 'PATCH', + endpoint: 'https://odp.example.com/v3/events', + headers: { + 'x-api-key': 'test-api', + }, + data: 'event-data', + }); + + const manager = new DefaultOdpEventApiManager(mockRequestHandler, requestGenerator); + const response = manager.sendEvents(odpConfig, ODP_EVENTS); + + await expect(response).resolves.not.toThrow(); + const statusCode = await response.then((r) => r.statusCode); + expect(statusCode).toBe(226); + }); +}); + +describe('pixelApiRequestGenerator', () => { + it('should generate the correct request for the pixel API using only the first event', () => { + const request = pixelApiRequestGenerator(odpConfig, ODP_EVENTS); + expect(request.method).toBe('GET'); + const endpoint = new URL(request.endpoint); + expect(endpoint.origin).toBe(PIXEL_URL); + expect(endpoint.pathname).toBe('/v2/zaius.gif'); + expect(endpoint.searchParams.get('id-key-1')).toBe('id-value-1'); + expect(endpoint.searchParams.get('key11')).toBe('value-1'); + expect(endpoint.searchParams.get('key12')).toBe('true'); + expect(endpoint.searchParams.get('key13')).toBe('3.5'); + expect(endpoint.searchParams.get('key14')).toBe('null'); + expect(endpoint.searchParams.get('tracker_id')).toBe(API_KEY); + expect(endpoint.searchParams.get('event_type')).toBe('t1'); + expect(endpoint.searchParams.get('vdl_action')).toBe('a1'); + + expect(request.headers).toEqual({}); + expect(request.data).toBe(''); + }); +}); + +describe('eventApiRequestGenerator', () => { + it('should generate the correct request for the event API using all events', () => { + const request = eventApiRequestGenerator(odpConfig, ODP_EVENTS); + expect(request.method).toBe('POST'); + expect(request.endpoint).toBe('https://odp.example.com/v3/events'); + expect(request.headers).toEqual({ + 'Content-Type': 'application/json', + 'x-api-key': API_KEY, + }); + + const data = JSON.parse(request.data); + expect(data).toEqual([ + { + type: 't1', + action: 'a1', + identifiers: { + 'id-key-1': 'id-value-1', + }, + data: { + key11: 'value-1', + key12: true, + key13: 3.5, + key14: null, + }, + }, + { + type: 't2', + action: 'a2', + identifiers: { + 'id-key-2': 'id-value-2', + }, + data: { + key2: 'value-2', + }, + }, + ]); + }); +}); diff --git a/lib/odp/event_manager/odp_event_api_manager.ts b/lib/odp/event_manager/odp_event_api_manager.ts new file mode 100644 index 000000000..79154b06e --- /dev/null +++ b/lib/odp/event_manager/odp_event_api_manager.ts @@ -0,0 +1,116 @@ +/** + * Copyright 2022-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LoggerFacade } from '../../logging/logger'; +import { OdpEvent } from './odp_event'; +import { HttpMethod, RequestHandler } from '../../utils/http_request_handler/http'; +import { OdpConfig } from '../odp_config'; + +export type EventDispatchResponse = { + statusCode?: number; +}; +export interface OdpEventApiManager { + sendEvents(odpConfig: OdpConfig, events: OdpEvent[]): Promise<EventDispatchResponse>; + setLogger(logger: LoggerFacade): void; +} + +export type EventRequest = { + method: HttpMethod; + endpoint: string; + headers: Record<string, string>; + data: string; +} + +export type EventRequestGenerator = (odpConfig: OdpConfig, events: OdpEvent[]) => EventRequest; + +export const LOGGER_NAME = 'OdpEventApiManager'; + +export class DefaultOdpEventApiManager implements OdpEventApiManager { + private logger?: LoggerFacade; + private requestHandler: RequestHandler; + private requestGenerator: EventRequestGenerator; + + constructor( + requestHandler: RequestHandler, + requestDataGenerator: EventRequestGenerator, + logger?: LoggerFacade + ) { + this.requestHandler = requestHandler; + this.requestGenerator = requestDataGenerator; + if (logger) { + this.setLogger(logger) + } + } + + setLogger(logger: LoggerFacade): void { + this.logger = logger; + this.logger.setName(LOGGER_NAME); + } + + async sendEvents(odpConfig: OdpConfig, events: OdpEvent[]): Promise<EventDispatchResponse> { + if (events.length === 0) { + return {}; + } + + const { method, endpoint, headers, data } = this.requestGenerator(odpConfig, events); + + const request = this.requestHandler.makeRequest(endpoint, headers, method, data); + return request.responsePromise; + } +} + +export const pixelApiRequestGenerator: EventRequestGenerator = (odpConfig: OdpConfig, events: OdpEvent[]): EventRequest => { + const pixelApiPath = 'v2/zaius.gif'; + const pixelApiEndpoint = new URL(pixelApiPath, odpConfig.pixelUrl); + + const apiKey = odpConfig.apiKey; + const method = 'GET'; + const event = events[0]; + + event.identifiers.forEach((v, k) => { + pixelApiEndpoint.searchParams.append(k, v); + }); + event.data.forEach((v, k) => { + pixelApiEndpoint.searchParams.append(k, v as string); + }); + pixelApiEndpoint.searchParams.append('tracker_id', apiKey); + pixelApiEndpoint.searchParams.append('event_type', event.type); + pixelApiEndpoint.searchParams.append('vdl_action', event.action); + const endpoint = pixelApiEndpoint.toString(); + + return { + method, + endpoint, + headers: {}, + data: '', + }; +} + +export const eventApiRequestGenerator: EventRequestGenerator = (odpConfig: OdpConfig, events: OdpEvent[]): EventRequest => { + const { apiHost, apiKey } = odpConfig; + + return { + method: 'POST', + endpoint: `${apiHost}/v3/events`, + headers: { + 'Content-Type': 'application/json', + 'x-api-key': apiKey, + }, + data: JSON.stringify(events, (_: unknown, value: unknown) => { + return value instanceof Map ? Object.fromEntries(value) : value; + }), + }; +} diff --git a/lib/odp/event_manager/odp_event_manager.spec.ts b/lib/odp/event_manager/odp_event_manager.spec.ts new file mode 100644 index 000000000..68484d788 --- /dev/null +++ b/lib/odp/event_manager/odp_event_manager.spec.ts @@ -0,0 +1,1054 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest'; +import { DefaultOdpEventManager, LOGGER_NAME } from './odp_event_manager'; +import { getMockRepeater } from '../../tests/mock/mock_repeater'; +import { getMockLogger } from '../../tests/mock/mock_logger'; +import { ServiceState } from '../../service'; +import { exhaustMicrotasks } from '../../tests/testUtils'; +import { OdpEvent } from './odp_event'; +import { OdpConfig } from '../odp_config'; +import { EventDispatchResponse } from './odp_event_api_manager'; +import { advanceTimersByTime } from '../../tests/testUtils'; + +const API_KEY = 'test-api-key'; +const API_HOST = 'https://odp.example.com/'; +const PIXEL_URL = 'https://odp.pixel.com/'; +const SEGMENTS_TO_CHECK = ['segment1', 'segment2']; + +const config = new OdpConfig(API_KEY, API_HOST, PIXEL_URL, SEGMENTS_TO_CHECK); + +const makeEvent = (id: number) => { + const identifiers = new Map<string, string>(); + identifiers.set('identifier1', 'value1-' + id); + identifiers.set('identifier2', 'value2-' + id); + + const data = new Map<string, unknown>(); + data.set('data1', 'data-value1-' + id); + data.set('data2', id); + + return new OdpEvent('test-type-' + id, 'test-action-' + id, identifiers, data); +}; + +const getMockApiManager = () => { + return { + sendEvents: vi.fn(), + setLogger: vi.fn(), + }; +}; + +describe('DefaultOdpEventManager', () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('should be in new state after construction', () => { + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: getMockApiManager(), + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + expect(odpEventManager.getState()).toBe(ServiceState.New); + }); + + it('should set name on the logger set using setLogger', () => { + const logger = getMockLogger(); + + const manager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: getMockApiManager(), + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + manager.setLogger(logger); + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME); + }); + + it('should pass a child logger to the event api manager when a logger is set using setLogger', () => { + const logger = getMockLogger(); + const childLogger = getMockLogger(); + logger.child.mockReturnValue(childLogger); + + const apiManager = getMockApiManager(); + + const manager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager, + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + manager.setLogger(logger); + expect(apiManager.setLogger).toHaveBeenCalledWith(childLogger); + }); + + it('should stay in starting state if started with a odpIntegationConfig and not resolve or reject onRunning', async () => { + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: getMockApiManager(), + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + const onRunningHandler = vi.fn(); + odpEventManager.onRunning().then(onRunningHandler, onRunningHandler); + + odpEventManager.start(); + expect(odpEventManager.getState()).toBe(ServiceState.Starting); + + await exhaustMicrotasks(); + + expect(odpEventManager.getState()).toBe(ServiceState.Starting); + expect(onRunningHandler).not.toHaveBeenCalled(); + }); + + it('should move to running state and resolve onRunning() is start() is called after updateConfig()', async () => { + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: getMockApiManager(), + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: false, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + expect(odpEventManager.getState()).toBe(ServiceState.Running); + }); + + it('should move to running state and resolve onRunning() is updateConfig() is called after start()', async () => { + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: getMockApiManager(), + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.start(); + + odpEventManager.updateConfig({ + integrated: false, + }); + + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + expect(odpEventManager.getState()).toBe(ServiceState.Running); + }); + + it('should queue events until batchSize is reached', async () => { + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: apiManager, + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + const events: OdpEvent[] = []; + for (let i = 0; i < 9; i++) { + events.push(makeEvent(i)); + odpEventManager.sendEvent(events[i]); + } + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + + events.push(makeEvent(9)); + odpEventManager.sendEvent(events[9]); + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).toHaveBeenCalledTimes(1); + expect(apiManager.sendEvents).toHaveBeenNthCalledWith(1, config, events); + }); + + it('should send events immediately asynchronously if batchSize is 1', async () => { + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: apiManager, + batchSize: 1, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + for (let i = 0; i < 10; i++) { + const event = makeEvent(i); + odpEventManager.sendEvent(event); + await exhaustMicrotasks(); + expect(apiManager.sendEvents).toHaveBeenCalledTimes(i + 1); + expect(apiManager.sendEvents).toHaveBeenNthCalledWith(i + 1, config, [event]); + } + }); + + it('should flush the queue immediately if disposable, regardless of the batchSize', async () => { + const apiManager = getMockApiManager(); + const repeater = getMockRepeater() + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + + const odpEventManager = new DefaultOdpEventManager({ + repeater, + apiManager: apiManager, + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + odpEventManager.makeDisposable(); + odpEventManager.start(); + + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + const event = makeEvent(0); + odpEventManager.sendEvent(event); + await exhaustMicrotasks(); + + expect(apiManager.sendEvents).toHaveBeenCalledTimes(1); + expect(apiManager.sendEvents).toHaveBeenNthCalledWith(1, config, [event]); + expect(repeater.reset).toHaveBeenCalledTimes(1); + expect(repeater.start).not.toHaveBeenCalled(); + }) + + it('drops events and logs if the state is not running', async () => { + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + const logger = getMockLogger(); + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: apiManager, + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.setLogger(logger); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + expect(odpEventManager.getState()).toBe(ServiceState.New); + + const event = makeEvent(0); + odpEventManager.sendEvent(event); + await exhaustMicrotasks(); + + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + expect(logger.error).toHaveBeenCalledTimes(1); + }); + + it('drops events and logs if odpIntegrationConfig is not integrated', async () => { + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + const logger = getMockLogger(); + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: apiManager, + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.setLogger(logger); + + odpEventManager.updateConfig({ + integrated: false, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + expect(odpEventManager.getState()).toBe(ServiceState.Running); + + const event = makeEvent(0); + odpEventManager.sendEvent(event); + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + expect(logger.error).toHaveBeenCalledTimes(1); + }); + + it('drops event and logs if there is no identifier', async () => { + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + const logger = getMockLogger(); + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: apiManager, + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.setLogger(logger); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + expect(odpEventManager.getState()).toBe(ServiceState.Running); + + const event = new OdpEvent('test-type', 'test-action', new Map(), new Map()); + odpEventManager.sendEvent(event); + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + expect(logger.error).toHaveBeenCalledTimes(1); + }); + + it('accepts string, number, boolean, and null values for data', async () => { + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + const logger = getMockLogger(); + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: apiManager, + batchSize: 1, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.setLogger(logger); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + expect(odpEventManager.getState()).toBe(ServiceState.Running); + + const data = new Map<string, unknown>(); + data.set('string', 'string-value'); + data.set('number', 123); + data.set('boolean', true); + data.set('null', null); + + const event = new OdpEvent('test-type', 'test-action', new Map([['k', 'v']]), data); + + odpEventManager.sendEvent(event); + await exhaustMicrotasks(); + + expect(apiManager.sendEvents).toHaveBeenCalledTimes(1); + expect(apiManager.sendEvents).toHaveBeenNthCalledWith(1, config, [event]); + }); + + it('should drop event and log if data contains values other than string, number, boolean, or null', async () => { + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + const logger = getMockLogger(); + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: apiManager, + batchSize: 1, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.setLogger(logger); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + expect(odpEventManager.getState()).toBe(ServiceState.Running); + + const data = new Map<string, unknown>(); + data.set('string', 'string-value'); + data.set('number', 123); + data.set('boolean', true); + data.set('null', null); + data.set('invalid', new Date()); + + const event = new OdpEvent('test-type', 'test-action', new Map([['k', 'v']]), data); + + odpEventManager.sendEvent(event); + await exhaustMicrotasks(); + + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + expect(logger.error).toHaveBeenCalledTimes(1); + }); + + it('should drop event and log if action is empty', async () => { + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + const logger = getMockLogger(); + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: apiManager, + batchSize: 1, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.setLogger(logger); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + expect(odpEventManager.getState()).toBe(ServiceState.Running); + + const event = new OdpEvent('test-type', '', new Map([['k', 'v']]), new Map([['k', 'v']])); + + odpEventManager.sendEvent(event); + await exhaustMicrotasks(); + + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + expect(logger.error).toHaveBeenCalledTimes(1); + }); + + it('should use fullstack as type if type is empty', async () => { + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + const logger = getMockLogger(); + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: apiManager, + batchSize: 1, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.setLogger(logger); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + expect(odpEventManager.getState()).toBe(ServiceState.Running); + + const event = new OdpEvent('', 'test-action', new Map([['k', 'v']]), new Map([['k', 'v']])); + + odpEventManager.sendEvent(event); + await exhaustMicrotasks(); + + expect(apiManager.sendEvents).toHaveBeenCalledTimes(1); + expect(apiManager.sendEvents.mock.calls[0][1][0].type).toBe('fullstack'); + }); + + it('should transform identifiers with keys FS-USER-ID, fs-user-id and FS_USER_ID to fs_user_id', async () => { + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + const logger = getMockLogger(); + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: apiManager, + batchSize: 3, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.setLogger(logger); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + expect(odpEventManager.getState()).toBe(ServiceState.Running); + + const event1 = new OdpEvent('test-type', 'test-action', new Map([['FS-USER-ID', 'value1']]), new Map([['k', 'v']])); + const event2 = new OdpEvent('test-type', 'test-action', new Map([['fs-user-id', 'value2']]), new Map([['k', 'v']])); + const event3 = new OdpEvent('test-type', 'test-action', new Map([['FS_USER_ID', 'value3']]), new Map([['k', 'v']])); + + odpEventManager.sendEvent(event1); + odpEventManager.sendEvent(event2); + odpEventManager.sendEvent(event3); + await exhaustMicrotasks(); + + expect(apiManager.sendEvents).toHaveBeenCalledTimes(1); + expect(apiManager.sendEvents.mock.calls[0][1][0].identifiers.get('fs_user_id')).toBe('value1'); + expect(apiManager.sendEvents.mock.calls[0][1][1].identifiers.get('fs_user_id')).toBe('value2'); + expect(apiManager.sendEvents.mock.calls[0][1][2].identifiers.get('fs_user_id')).toBe('value3'); + }); + + it('should start the repeater when the first event is sent', async () => { + const repeater = getMockRepeater(); + + const odpEventManager = new DefaultOdpEventManager({ + repeater: repeater, + apiManager: getMockApiManager(), + batchSize: 300, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + expect(repeater.start).not.toHaveBeenCalled(); + + for(let i = 0; i < 10; i++) { + odpEventManager.sendEvent(makeEvent(i)); + await exhaustMicrotasks(); + expect(repeater.start).toHaveBeenCalledTimes(1); + } + }); + + it('should flush the queue when the repeater triggers', async () => { + const repeater = getMockRepeater(); + + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + + const odpEventManager = new DefaultOdpEventManager({ + repeater: repeater, + apiManager: apiManager, + batchSize: 30, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + const events: OdpEvent[] = []; + for(let i = 0; i < 10; i++) { + events.push(makeEvent(i)); + odpEventManager.sendEvent(events[i]); + } + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + + await repeater.execute(0); + await exhaustMicrotasks(); + expect(apiManager.sendEvents).toHaveBeenCalledTimes(1); + expect(apiManager.sendEvents).toHaveBeenNthCalledWith(1, config, events); + }); + + it('should reset the repeater after flush', async () => { + const repeater = getMockRepeater(); + + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + + const odpEventManager = new DefaultOdpEventManager({ + repeater: repeater, + apiManager: apiManager, + batchSize: 30, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + const events: OdpEvent[] = []; + for(let i = 0; i < 10; i++) { + events.push(makeEvent(i)); + odpEventManager.sendEvent(events[i]); + } + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + + expect(repeater.reset).not.toHaveBeenCalled(); + + await repeater.execute(0); + await exhaustMicrotasks(); + expect(apiManager.sendEvents).toHaveBeenCalledTimes(1); + expect(apiManager.sendEvents).toHaveBeenNthCalledWith(1, config, events); + expect(repeater.reset).toHaveBeenCalledTimes(1); + }); + + it('should retry specified number of times with backoff if apiManager.sendEvents returns a rejecting promise', async () => { + const repeater = getMockRepeater(); + + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockReturnValue(Promise.reject(new Error('FAILED_TO_DISPATCH_EVENTS'))); + + const backoffController = { + backoff: vi.fn().mockReturnValue(666), + reset: vi.fn(), + }; + + const maxRetries = 5; + const retryConfig = { + maxRetries, + backoffProvider: () => backoffController, + }; + + const odpEventManager = new DefaultOdpEventManager({ + repeater: repeater, + apiManager: apiManager, + batchSize: 30, + retryConfig: retryConfig, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + const events: OdpEvent[] = []; + for(let i = 0; i < 10; i++) { + events.push(makeEvent(i)); + odpEventManager.sendEvent(events[i]); + } + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + + repeater.execute(0); + for(let i = 1; i <= maxRetries; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(666); + expect(apiManager.sendEvents).toHaveBeenCalledTimes(i + 1); + expect(apiManager.sendEvents).toHaveBeenNthCalledWith(i, config, events); + expect(backoffController.backoff).toHaveBeenCalledTimes(i); + } + }); + + it('should retry specified number of times with backoff if apiManager returns 5xx', async () => { + const repeater = getMockRepeater(); + + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockReturnValue(Promise.resolve({ statusCode: 500 })); + + const backoffController = { + backoff: vi.fn().mockReturnValue(666), + reset: vi.fn(), + }; + + const maxRetries = 5; + const retryConfig = { + maxRetries, + backoffProvider: () => backoffController, + }; + + const odpEventManager = new DefaultOdpEventManager({ + repeater: repeater, + apiManager: apiManager, + batchSize: 30, + retryConfig: retryConfig, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + const events: OdpEvent[] = []; + for(let i = 0; i < 10; i++) { + events.push(makeEvent(i)); + odpEventManager.sendEvent(events[i]); + } + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + + repeater.execute(0); + for(let i = 1; i <= maxRetries; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(666); + expect(apiManager.sendEvents).toHaveBeenCalledTimes(i + 1); + expect(apiManager.sendEvents).toHaveBeenNthCalledWith(i, config, events); + expect(backoffController.backoff).toHaveBeenCalledTimes(i); + } + }); + + it('should log error if event sends fails even after retry', async () => { + const repeater = getMockRepeater(); + + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockReturnValue(Promise.reject(new Error('FAILED_TO_DISPATCH_EVENTS'))); + + const backoffController = { + backoff: vi.fn().mockReturnValue(666), + reset: vi.fn(), + }; + + const maxRetries = 5; + const retryConfig = { + maxRetries, + backoffProvider: () => backoffController, + }; + + const logger = getMockLogger(); + const odpEventManager = new DefaultOdpEventManager({ + repeater: repeater, + apiManager: apiManager, + batchSize: 30, + retryConfig: retryConfig, + }); + + odpEventManager.setLogger(logger); + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + const events: OdpEvent[] = []; + for(let i = 0; i < 10; i++) { + events.push(makeEvent(i)); + odpEventManager.sendEvent(events[i]); + } + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + + repeater.execute(0); + for(let i = 1; i <= maxRetries; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(666); + expect(apiManager.sendEvents).toHaveBeenCalledTimes(i + 1); + expect(apiManager.sendEvents).toHaveBeenNthCalledWith(i, config, events); + expect(backoffController.backoff).toHaveBeenCalledTimes(i); + } + + await exhaustMicrotasks(); + expect(logger.error).toHaveBeenCalledTimes(1); + }); + + it('flushes the queue with old config if updateConfig is called with a new config', async () => { + const repeater = getMockRepeater(); + + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + + const odpEventManager = new DefaultOdpEventManager({ + repeater: repeater, + apiManager: apiManager, + batchSize: 30, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + const events: OdpEvent[] = []; + for(let i = 0; i < 10; i++) { + events.push(makeEvent(i)); + odpEventManager.sendEvent(events[i]); + } + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + + const newConfig = new OdpConfig('new-api-key', 'https://new-odp.example.com/', 'https://new-odp.pixel.com/', ['new-segment']); + odpEventManager.updateConfig({ + integrated: true, + odpConfig: newConfig, + }); + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).toHaveBeenCalledOnce(); + expect(apiManager.sendEvents).toHaveBeenCalledWith(config, events); + }); + + it('uses the new config after updateConfig is called', async () => { + const repeater = getMockRepeater(); + + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + + const odpEventManager = new DefaultOdpEventManager({ + repeater: repeater, + apiManager: apiManager, + batchSize: 30, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + const events: OdpEvent[] = []; + for(let i = 0; i < 10; i++) { + events.push(makeEvent(i)); + odpEventManager.sendEvent(events[i]); + } + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + + const newConfig = new OdpConfig('new-api-key', 'https://new-odp.example.com/', 'https://new-odp.pixel.com/', ['new-segment']); + odpEventManager.updateConfig({ + integrated: true, + odpConfig: newConfig, + }); + + const newEvents: OdpEvent[] = []; + for(let i = 0; i < 10; i++) { + newEvents.push(makeEvent(i + 10)); + odpEventManager.sendEvent(newEvents[i]); + } + + repeater.execute(0); + await exhaustMicrotasks(); + expect(apiManager.sendEvents).toHaveBeenCalledTimes(2); + expect(apiManager.sendEvents).toHaveBeenNthCalledWith(1, config, events); + expect(apiManager.sendEvents).toHaveBeenNthCalledWith(2, newConfig, newEvents); + }); + + it('should reject onRunning() if stop() is called in new state', async () => { + const odpEventManager = new DefaultOdpEventManager({ + repeater: getMockRepeater(), + apiManager: getMockApiManager(), + batchSize: 10, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.stop(); + await expect(odpEventManager.onRunning()).rejects.toThrow(); + }); + + it('should flush the queue and reset the repeater if stop() is called in running state', async () => { + const repeater = getMockRepeater(); + + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + + const odpEventManager = new DefaultOdpEventManager({ + repeater: repeater, + apiManager: apiManager, + batchSize: 30, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + const events: OdpEvent[] = []; + for(let i = 0; i < 10; i++) { + events.push(makeEvent(i)); + odpEventManager.sendEvent(events[i]); + } + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + + odpEventManager.stop(); + await exhaustMicrotasks(); + expect(apiManager.sendEvents).toHaveBeenCalledTimes(1); + expect(apiManager.sendEvents).toHaveBeenCalledWith(config, events); + expect(repeater.reset).toHaveBeenCalledTimes(1); + }); + + it('resolve onTerminated() and go to Terminated state if stop() is called in running state', async () => { + const repeater = getMockRepeater(); + + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + + const odpEventManager = new DefaultOdpEventManager({ + repeater: repeater, + apiManager: apiManager, + batchSize: 30, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + odpEventManager.stop(); + await expect(odpEventManager.onTerminated()).resolves.not.toThrow(); + expect(odpEventManager.getState()).toBe(ServiceState.Terminated); + }); + + it('should flush the queue when flushImmediately() is called in running state', async () => { + const repeater = getMockRepeater(); + + const apiManager = getMockApiManager(); + apiManager.sendEvents.mockResolvedValue({ statusCode: 200 }); + + const odpEventManager = new DefaultOdpEventManager({ + repeater: repeater, + apiManager: apiManager, + batchSize: 30, + retryConfig: { + maxRetries: 3, + backoffProvider: vi.fn(), + }, + }); + + odpEventManager.updateConfig({ + integrated: true, + odpConfig: config, + }); + + odpEventManager.start(); + await expect(odpEventManager.onRunning()).resolves.not.toThrow(); + + const events: OdpEvent[] = []; + for(let i = 0; i < 10; i++) { + events.push(makeEvent(i)); + odpEventManager.sendEvent(events[i]); + } + + await exhaustMicrotasks(); + expect(apiManager.sendEvents).not.toHaveBeenCalled(); + + odpEventManager.flushImmediately(); + await exhaustMicrotasks(); + + expect(apiManager.sendEvents).toHaveBeenCalledTimes(1); + expect(apiManager.sendEvents).toHaveBeenCalledWith(config, events); + expect(odpEventManager.isRunning()).toBe(true); + }); +}); diff --git a/lib/odp/event_manager/odp_event_manager.ts b/lib/odp/event_manager/odp_event_manager.ts new file mode 100644 index 000000000..d1a30d3ff --- /dev/null +++ b/lib/odp/event_manager/odp_event_manager.ts @@ -0,0 +1,248 @@ +/** + * Copyright 2022-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { OdpEvent } from './odp_event'; +import { OdpConfig, OdpIntegrationConfig } from '../odp_config'; +import { OdpEventApiManager } from './odp_event_api_manager'; +import { BaseService, Service, ServiceState, StartupLog } from '../../service'; +import { BackoffController, Repeater } from '../../utils/repeater/repeater'; +import { Producer } from '../../utils/type'; +import { runWithRetry } from '../../utils/executor/backoff_retry_runner'; +import { isSuccessStatusCode } from '../../utils/http_request_handler/http_util'; +import { ODP_DEFAULT_EVENT_TYPE, ODP_USER_KEY } from '../constant'; +import { + EVENT_ACTION_INVALID, + EVENT_DATA_INVALID, + FAILED_TO_SEND_ODP_EVENTS, + ODP_EVENTS_SHOULD_HAVE_ATLEAST_ONE_KEY_VALUE, + ODP_NOT_INTEGRATED, + FAILED_TO_DISPATCH_EVENTS, + ODP_EVENT_MANAGER_STOPPED, + SERVICE_NOT_RUNNING +} from 'error_message'; +import { OptimizelyError } from '../../error/optimizly_error'; +import { LoggerFacade } from '../../logging/logger'; +import { SERVICE_STOPPED_BEFORE_RUNNING } from '../../service'; +import { sprintf } from '../../utils/fns'; + +export interface OdpEventManager extends Service { + updateConfig(odpIntegrationConfig: OdpIntegrationConfig): void; + sendEvent(event: OdpEvent): void; + setLogger(logger: LoggerFacade): void; + flushImmediately(): Promise<unknown>; +} + +export type RetryConfig = { + maxRetries: number; + backoffProvider: Producer<BackoffController>; +} + +export type OdpEventManagerConfig = { + repeater: Repeater, + apiManager: OdpEventApiManager, + batchSize: number, + startUpLogs?: StartupLog[], + retryConfig: RetryConfig, +}; + +export const LOGGER_NAME = 'OdpEventManager'; + +export class DefaultOdpEventManager extends BaseService implements OdpEventManager { + private queue: OdpEvent[] = []; + private repeater: Repeater; + private odpIntegrationConfig?: OdpIntegrationConfig; + private apiManager: OdpEventApiManager; + private batchSize: number; + + private retryConfig: RetryConfig; + + constructor(config: OdpEventManagerConfig) { + super(config.startUpLogs); + + this.apiManager = config.apiManager; + this.batchSize = config.batchSize; + this.retryConfig = config.retryConfig; + + this.repeater = config.repeater; + this.repeater.setTask(() => this.flush()); + } + + setLogger(logger: LoggerFacade): void { + this.logger = logger; + this.logger.setName(LOGGER_NAME); + this.apiManager.setLogger(logger.child()); + } + + private async executeDispatch(odpConfig: OdpConfig, batch: OdpEvent[]): Promise<unknown> { + const res = await this.apiManager.sendEvents(odpConfig, batch); + if (res.statusCode && !isSuccessStatusCode(res.statusCode)) { + return Promise.reject(new OptimizelyError(FAILED_TO_DISPATCH_EVENTS, res.statusCode)); + } + return await Promise.resolve(res); + } + + private async flush(): Promise<unknown> { + if (!this.odpIntegrationConfig || !this.odpIntegrationConfig.integrated) { + return; + } + + const odpConfig = this.odpIntegrationConfig.odpConfig; + + const batch = this.queue; + this.queue = []; + + // as the queue has been emptied, stop repeating flush + // until more events become available + this.repeater.reset(); + + return runWithRetry( + () => this.executeDispatch(odpConfig, batch), this.retryConfig.backoffProvider(), this.retryConfig.maxRetries + ).result.catch((err) => { + this.logger?.error(FAILED_TO_SEND_ODP_EVENTS, err); + }); + } + + start(): void { + if (!this.isNew()) { + return; + } + + super.start(); + + if (this.odpIntegrationConfig) { + this.goToRunningState(); + } else { + this.state = ServiceState.Starting; + } + } + + makeDisposable(): void { + super.makeDisposable(); + this.retryConfig.maxRetries = Math.min(this.retryConfig.maxRetries, 5); + this.batchSize = 1; + } + + updateConfig(odpIntegrationConfig: OdpIntegrationConfig): void { + if (this.isDone()) { + return; + } + + if (this.isNew()) { + this.odpIntegrationConfig = odpIntegrationConfig; + return; + } + + if (this.isStarting()) { + this.odpIntegrationConfig = odpIntegrationConfig; + this.goToRunningState(); + return; + } + + // already running, flush the queue using the previous config first before updating the config + this.flush(); + this.odpIntegrationConfig = odpIntegrationConfig; + } + + private goToRunningState() { + this.state = ServiceState.Running; + this.startPromise.resolve(); + } + + flushImmediately(): Promise<unknown> { + if (!this.isRunning()) { + return Promise.resolve(); + } + return this.flush(); + } + + stop(): void { + if (this.isDone()) { + return; + } + + if (this.isNew()) { + this.startPromise.reject(new Error( + sprintf(SERVICE_STOPPED_BEFORE_RUNNING, 'OdpEventManager') + )); + } + + this.flush(); + this.state = ServiceState.Terminated; + this.stopPromise.resolve(); + } + + sendEvent(event: OdpEvent): void { + if (!this.isRunning()) { + this.logger?.error(SERVICE_NOT_RUNNING, 'OdpEventManager'); + return; + } + + if (!this.odpIntegrationConfig?.integrated) { + this.logger?.error(ODP_NOT_INTEGRATED); + return; + } + + if (event.identifiers.size === 0) { + this.logger?.error(ODP_EVENTS_SHOULD_HAVE_ATLEAST_ONE_KEY_VALUE); + return; + } + + if (!this.isDataValid(event.data)) { + this.logger?.error(EVENT_DATA_INVALID); + return; + } + + if (!event.action ) { + this.logger?.error(EVENT_ACTION_INVALID); + return; + } + + if (event.type === '') { + event.type = ODP_DEFAULT_EVENT_TYPE; + } + + Array.from(event.identifiers.entries()).forEach(([key, value]) => { + // Catch for fs-user-id, FS-USER-ID, and FS_USER_ID and assign value to fs_user_id identifier. + if ( + ODP_USER_KEY.FS_USER_ID_ALIAS === key.toLowerCase() || + ODP_USER_KEY.FS_USER_ID === key.toLowerCase() + ) { + event.identifiers.delete(key); + event.identifiers.set(ODP_USER_KEY.FS_USER_ID, value); + } + }); + + this.processEvent(event); + } + + private isDataValid(data: Map<string, any>): boolean { + const validTypes: string[] = ['string', 'number', 'boolean']; + return Array.from(data.values()).reduce( + (valid, value) => valid && (value === null || validTypes.includes(typeof value)), + true, + ); + } + + private processEvent(event: OdpEvent): void { + this.queue.push(event); + + if (this.queue.length === this.batchSize) { + this.flush(); + } else if (!this.repeater.isRunning()) { + this.repeater.start(); + } + } +} diff --git a/lib/odp/odp_config.ts b/lib/odp/odp_config.ts new file mode 100644 index 000000000..5003e1238 --- /dev/null +++ b/lib/odp/odp_config.ts @@ -0,0 +1,83 @@ +/** + * Copyright 2022-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { checkArrayEquality } from '../utils/fns'; + +export class OdpConfig { + /** + * Host of ODP audience segments API. + */ + readonly apiHost: string; + + /** + * Public API key for the ODP account from which the audience segments will be fetched (optional). + */ + readonly apiKey: string; + + /** + * Url for sending events via pixel. + */ + readonly pixelUrl: string; + + /** + * All ODP segments used in the current datafile (associated with apiHost/apiKey). + */ + readonly segmentsToCheck: string[]; + + constructor(apiKey: string, apiHost: string, pixelUrl: string, segmentsToCheck: string[]) { + this.apiKey = apiKey; + this.apiHost = apiHost; + this.pixelUrl = pixelUrl; + this.segmentsToCheck = segmentsToCheck; + } + + /** + * Detects if there are any changes between the current and incoming ODP Configs + * @param configToCompare ODP Configuration to check self against for equality + * @returns Boolean based on if the current ODP Config is equivalent to the incoming ODP Config + */ + equals(configToCompare: OdpConfig): boolean { + return ( + this.apiHost === configToCompare.apiHost && + this.apiKey === configToCompare.apiKey && + this.pixelUrl === configToCompare.pixelUrl && + checkArrayEquality(this.segmentsToCheck, configToCompare.segmentsToCheck) + ); + } +} + +export type OdpNotIntegratedConfig = { + readonly integrated: false; +} + +export type OdpIntegratedConfig = { + readonly integrated: true; + readonly odpConfig: OdpConfig; +} + +export const odpIntegrationsAreEqual = (config1: OdpIntegrationConfig, config2: OdpIntegrationConfig): boolean => { + if (config1.integrated !== config2.integrated) { + return false; + } + + if (config1.integrated && config2.integrated) { + return config1.odpConfig.equals(config2.odpConfig); + } + + return true; +} + +export type OdpIntegrationConfig = OdpNotIntegratedConfig | OdpIntegratedConfig; diff --git a/lib/odp/odp_manager.spec.ts b/lib/odp/odp_manager.spec.ts new file mode 100644 index 000000000..9ae0daf69 --- /dev/null +++ b/lib/odp/odp_manager.spec.ts @@ -0,0 +1,809 @@ +/** + * Copyright 2023-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, vi, expect } from 'vitest'; + + +import { DefaultOdpManager, LOGGER_NAME } from './odp_manager'; +import { ServiceState } from '../service'; +import { resolvablePromise } from '../utils/promise/resolvablePromise'; +import { OdpConfig } from './odp_config'; +import { exhaustMicrotasks } from '../tests/testUtils'; +import { ODP_USER_KEY } from './constant'; +import { OptimizelySegmentOption } from './segment_manager/optimizely_segment_option'; +import { OdpEventManager } from './event_manager/odp_event_manager'; +import { CLIENT_VERSION, JAVASCRIPT_CLIENT_ENGINE } from '../utils/enums'; +import { getMockLogger } from '../tests/mock/mock_logger'; + +const keyA = 'key-a'; +const hostA = 'host-a'; +const pixelA = 'pixel-a'; +const segmentsA = ['a']; +const userA = 'fs-user-a'; + +const keyB = 'key-b'; +const hostB = 'host-b'; +const pixelB = 'pixel-b'; +const segmentsB = ['b']; +const userB = 'fs-user-b'; + +const config = new OdpConfig(keyA, hostA, pixelA, segmentsA); +const updatedConfig = new OdpConfig(keyB, hostB, pixelB, segmentsB); + +const getMockOdpEventManager = () => { + return { + start: vi.fn(), + stop: vi.fn(), + onRunning: vi.fn(), + onTerminated: vi.fn(), + getState: vi.fn(), + updateConfig: vi.fn(), + sendEvent: vi.fn(), + makeDisposable: vi.fn(), + setLogger: vi.fn(), + flushImmediately: vi.fn(), + }; +}; + +const getMockOdpSegmentManager = () => { + return { + fetchQualifiedSegments: vi.fn(), + updateConfig: vi.fn(), + setLogger: vi.fn(), + }; +}; + +describe('DefaultOdpManager', () => { + describe('a logger is passed in the constructor', () => { + it('should set name on the logger passed into the constructor', () => { + const logger = getMockLogger(); + const manager = new DefaultOdpManager({ + logger, + eventManager: getMockOdpEventManager(), + segmentManager: getMockOdpSegmentManager(), + }); + + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME); + }); + + it('should pass different child loggers to the eventManager and segmentManager', () => { + const logger = getMockLogger(); + const eventChildLogger = getMockLogger(); + const segmentChildLogger = getMockLogger(); + + logger.child.mockReturnValueOnce(eventChildLogger) + .mockReturnValueOnce(segmentChildLogger); + + const eventManager = getMockOdpEventManager(); + const segmentManager = getMockOdpSegmentManager(); + + const manager = new DefaultOdpManager({ + logger, + eventManager, + segmentManager, + }); + + expect(eventManager.setLogger).toHaveBeenCalledWith(eventChildLogger); + expect(segmentManager.setLogger).toHaveBeenCalledWith(segmentChildLogger); + }); + }); + + describe('setLogger method', () => { + it('should set name on the logger', () => { + const logger = getMockLogger(); + const manager = new DefaultOdpManager({ + eventManager: getMockOdpEventManager(), + segmentManager: getMockOdpSegmentManager(), + }); + + manager.setLogger(logger); + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME); + }); + + it('should pass a child logger to the datafileManager', () => { + const logger = getMockLogger(); + const eventChildLogger = getMockLogger(); + const segmentChildLogger = getMockLogger(); + + logger.child.mockReturnValueOnce(eventChildLogger) + .mockReturnValueOnce(segmentChildLogger); + + const eventManager = getMockOdpEventManager(); + const segmentManager = getMockOdpSegmentManager(); + + const manager = new DefaultOdpManager({ + eventManager, + segmentManager, + }); + manager.setLogger(logger); + + expect(eventManager.setLogger).toHaveBeenCalledWith(eventChildLogger); + expect(segmentManager.setLogger).toHaveBeenCalledWith(segmentChildLogger); + }); + }); + + it('should be in new state on construction', () => { + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager: getMockOdpEventManager(), + }); + + expect(odpManager.getState()).toEqual(ServiceState.New); + }); + + it('should be in starting state after start is called', () => { + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager: getMockOdpEventManager(), + }); + + odpManager.start(); + + expect(odpManager.getState()).toEqual(ServiceState.Starting); + }); + + it('should start eventManager after start is called', () => { + const eventManager = getMockOdpEventManager(); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + expect(eventManager.start).toHaveBeenCalled(); + }); + + it('should stay in starting state if updateConfig is called but eventManager is still not running', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(resolvablePromise<void>().promise); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + expect(odpManager.getState()).toEqual(ServiceState.Starting); + + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await exhaustMicrotasks(); + expect(odpManager.getState()).toEqual(ServiceState.Starting); + }); + + it('should stay in starting state if eventManager is running but config is not yet available', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + expect(odpManager.getState()).toEqual(ServiceState.Starting); + + await exhaustMicrotasks(); + expect(odpManager.getState()).toEqual(ServiceState.Starting); + }); + + it('should go to running state and resolve onRunning() if updateConfig is called and eventManager is running', async () => { + const eventManager = getMockOdpEventManager(); + const eventManagerPromise = resolvablePromise<void>(); + eventManager.onRunning.mockReturnValue(eventManagerPromise.promise); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + expect(odpManager.getState()).toEqual(ServiceState.Starting); + + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await exhaustMicrotasks(); + + expect(odpManager.getState()).toEqual(ServiceState.Starting); + eventManagerPromise.resolve(); + + await expect(odpManager.onRunning()).resolves.not.toThrow(); + expect(odpManager.getState()).toEqual(ServiceState.Running); + }); + + it('should go to failed state and reject onRunning(), onTerminated() if updateConfig is called and eventManager fails to start', async () => { + const eventManager = getMockOdpEventManager(); + const eventManagerPromise = resolvablePromise<void>(); + eventManager.onRunning.mockReturnValue(eventManagerPromise.promise); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + expect(odpManager.getState()).toEqual(ServiceState.Starting); + + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await exhaustMicrotasks(); + + expect(odpManager.getState()).toEqual(ServiceState.Starting); + eventManagerPromise.reject(new Error("Failed to start")); + + await expect(odpManager.onRunning()).rejects.toThrow(); + await expect(odpManager.onTerminated()).rejects.toThrow(); + expect(odpManager.getState()).toEqual(ServiceState.Failed); + }); + + it('should go to failed state and reject onRunning(), onTerminated() if eventManager fails to start before updateSettings()', async () => { + const eventManager = getMockOdpEventManager(); + const eventManagerPromise = resolvablePromise<void>(); + eventManager.onRunning.mockReturnValue(eventManagerPromise.promise); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + expect(odpManager.getState()).toEqual(ServiceState.Starting); + + eventManagerPromise.reject(new Error("Failed to start")); + + await expect(odpManager.onRunning()).rejects.toThrow(); + await expect(odpManager.onTerminated()).rejects.toThrow(); + expect(odpManager.getState()).toEqual(ServiceState.Failed); + }); + + it('should pass the changed config to eventManager and segmentManager', async () => { + const eventManager = getMockOdpEventManager(); + const segmentManager = getMockOdpSegmentManager(); + + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const odpManager = new DefaultOdpManager({ + segmentManager, + eventManager, + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + expect(eventManager.updateConfig).toHaveBeenNthCalledWith(1, { integrated: true, odpConfig: config }); + expect(segmentManager.updateConfig).toHaveBeenNthCalledWith(1, { integrated: true, odpConfig: config }); + + odpManager.updateConfig({ integrated: true, odpConfig: updatedConfig }); + + expect(eventManager.updateConfig).toHaveBeenNthCalledWith(2, { integrated: true, odpConfig: updatedConfig }); + expect(segmentManager.updateConfig).toHaveBeenNthCalledWith(2, { integrated: true, odpConfig: updatedConfig }); + expect(eventManager.updateConfig).toHaveBeenCalledTimes(2); + expect(segmentManager.updateConfig).toHaveBeenCalledTimes(2); + }); + + it('should not call eventManager and segmentManager updateConfig if config does not change', async () => { + const eventManager = getMockOdpEventManager(); + const segmentManager = getMockOdpSegmentManager(); + + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const odpManager = new DefaultOdpManager({ + segmentManager, + eventManager, + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + expect(eventManager.updateConfig).toHaveBeenNthCalledWith(1, { integrated: true, odpConfig: config }); + expect(segmentManager.updateConfig).toHaveBeenNthCalledWith(1, { integrated: true, odpConfig: config }); + + odpManager.updateConfig({ integrated: true, odpConfig: JSON.parse(JSON.stringify(config)) }); + + expect(eventManager.updateConfig).toHaveBeenCalledTimes(1); + expect(segmentManager.updateConfig).toHaveBeenCalledTimes(1); + }); + + it('fetches qualified segments correctly for both fs_user_id and vuid from segmentManager', async () => { + const segmentManager = getMockOdpSegmentManager(); + segmentManager.fetchQualifiedSegments.mockImplementation((key: ODP_USER_KEY) => { + if (key === ODP_USER_KEY.FS_USER_ID) { + return Promise.resolve(['fs1', 'fs2']); + } + return Promise.resolve(['vuid1', 'vuid2']); + }); + + const odpManager = new DefaultOdpManager({ + segmentManager, + eventManager: getMockOdpEventManager(), + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + const fsSegments = await odpManager.fetchQualifiedSegments(userA); + expect(fsSegments).toEqual(['fs1', 'fs2']); + expect(segmentManager.fetchQualifiedSegments).toHaveBeenNthCalledWith(1, ODP_USER_KEY.FS_USER_ID, userA, []); + + const vuidSegments = await odpManager.fetchQualifiedSegments('vuid_abcd'); + expect(vuidSegments).toEqual(['vuid1', 'vuid2']); + expect(segmentManager.fetchQualifiedSegments).toHaveBeenNthCalledWith(2, ODP_USER_KEY.VUID, 'vuid_abcd', []); + }); + + it('returns null from fetchQualifiedSegments if segmentManger returns null', async () => { + const segmentManager = getMockOdpSegmentManager(); + segmentManager.fetchQualifiedSegments.mockResolvedValue(null); + + const odpManager = new DefaultOdpManager({ + segmentManager, + eventManager: getMockOdpEventManager(), + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + const fsSegments = await odpManager.fetchQualifiedSegments(userA); + expect(fsSegments).toBeNull(); + + const vuidSegments = await odpManager.fetchQualifiedSegments('vuid_abcd'); + expect(vuidSegments).toBeNull(); + }); + + it('passes options to segmentManager correctly', async () => { + const segmentManager = getMockOdpSegmentManager(); + segmentManager.fetchQualifiedSegments.mockResolvedValue(null); + + const odpManager = new DefaultOdpManager({ + segmentManager, + eventManager: getMockOdpEventManager(), + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + const options = [OptimizelySegmentOption.IGNORE_CACHE, OptimizelySegmentOption.RESET_CACHE]; + await odpManager.fetchQualifiedSegments(userA, options); + expect(segmentManager.fetchQualifiedSegments).toHaveBeenNthCalledWith(1, ODP_USER_KEY.FS_USER_ID, userA, options); + + await odpManager.fetchQualifiedSegments('vuid_abcd', options); + expect(segmentManager.fetchQualifiedSegments).toHaveBeenNthCalledWith(2, ODP_USER_KEY.VUID, 'vuid_abcd', options); + + await odpManager.fetchQualifiedSegments(userA, [OptimizelySegmentOption.IGNORE_CACHE]); + expect(segmentManager.fetchQualifiedSegments).toHaveBeenNthCalledWith( + 3, ODP_USER_KEY.FS_USER_ID, userA, [OptimizelySegmentOption.IGNORE_CACHE]); + + await odpManager.fetchQualifiedSegments('vuid_abcd', []); + expect(segmentManager.fetchQualifiedSegments).toHaveBeenNthCalledWith(4, ODP_USER_KEY.VUID, 'vuid_abcd', []); + }); + + it('sends a client_intialized event with the vuid after becoming ready if setVuid is called and odp is integrated', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const mockSendEvents = vi.mocked(eventManager.sendEvent as OdpEventManager['sendEvent']); + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.setVuid('vuid_123'); + + await exhaustMicrotasks(); + expect(eventManager.sendEvent).not.toHaveBeenCalled(); + + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + expect(mockSendEvents).toHaveBeenCalledOnce(); + + const { type, action, identifiers } = mockSendEvents.mock.calls[0][0]; + expect(type).toEqual('fullstack'); + expect(action).toEqual('client_initialized'); + expect(identifiers).toEqual(new Map([['vuid', 'vuid_123']])); + }); + + it('does not send a client_intialized event with the vuid after becoming ready if setVuid is called and odp is not integrated', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const mockSendEvents = vi.mocked(eventManager.sendEvent as OdpEventManager['sendEvent']); + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.setVuid('vuid_123'); + + await exhaustMicrotasks(); + expect(eventManager.sendEvent).not.toHaveBeenCalled(); + + odpManager.updateConfig({ integrated: false }); + await odpManager.onRunning(); + + await exhaustMicrotasks(); + expect(mockSendEvents).not.toHaveBeenCalled(); + }); + + it('includes the available vuid in events sent via sendEvent', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const mockSendEvents = vi.mocked(eventManager.sendEvent as OdpEventManager['sendEvent']); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + odpManager.setVuid('vuid_123'); + + const event = { + type: 'type', + action: 'action', + identifiers: new Map([['email', 'a@b.com']]), + data: new Map([['key1', 'value1'], ['key2', 'value2']]), + }; + + odpManager.sendEvent(event); + const { identifiers } = mockSendEvents.mock.calls[0][0]; + expect(identifiers).toEqual(new Map([['email', 'a@b.com'], ['vuid', 'vuid_123']])); + }); + + it('does not override the vuid in events sent via sendEvent', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const mockSendEvents = vi.mocked(eventManager.sendEvent as OdpEventManager['sendEvent']); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + odpManager.setVuid('vuid_123'); + + const event = { + type: 'type', + action: 'action', + identifiers: new Map([['email', 'a@b.com'], ['vuid', 'vuid_456']]), + data: new Map([['key1', 'value1'], ['key2', 'value2']]), + }; + + odpManager.sendEvent(event); + const { identifiers } = mockSendEvents.mock.calls[0][0]; + expect(identifiers).toEqual(new Map([['email', 'a@b.com'], ['vuid', 'vuid_456']])); + }); + + it('augments the data with common data before sending the event', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const mockSendEvents = vi.mocked(eventManager.sendEvent as OdpEventManager['sendEvent']); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + const event = { + type: 'type', + action: 'action', + identifiers: new Map([['email', 'a@b.com']]), + data: new Map([['key1', 'value1'], ['key2', 'value2']]), + }; + + odpManager.sendEvent(event); + const { data } = mockSendEvents.mock.calls[0][0]; + expect(data.get('idempotence_id')).toBeDefined(); + expect(data.get('data_source_type')).toEqual('sdk'); + expect(data.get('data_source')).toEqual(JAVASCRIPT_CLIENT_ENGINE); + expect(data.get('data_source_version')).toEqual(CLIENT_VERSION); + expect(data.get('key1')).toEqual('value1'); + expect(data.get('key2')).toEqual('value2'); + }); + + it('uses the clientInfo provided by setClientInfo() when augmenting the data', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const mockSendEvents = vi.mocked(eventManager.sendEvent as OdpEventManager['sendEvent']); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + odpManager.setClientInfo('client', 'version'); + + const event = { + type: 'type', + action: 'action', + identifiers: new Map([['email', 'a@b.com']]), + data: new Map([['key1', 'value1'], ['key2', 'value2']]), + }; + + odpManager.sendEvent(event); + const { data } = mockSendEvents.mock.calls[0][0]; + expect(data.get('data_source')).toEqual('client'); + expect(data.get('data_source_version')).toEqual('version'); + }); + + it('augments the data with user agent data before sending the event if userAgentParser is provided ', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const mockSendEvents = vi.mocked(eventManager.sendEvent as OdpEventManager['sendEvent']); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + userAgentParser: { + parseUserAgentInfo: () => ({ + os: { name: 'os', version: '1.0' }, + device: { type: 'phone', model: 'model' }, + }), + }, + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + const event = { + type: 'type', + action: 'action', + identifiers: new Map([['email', 'a@b.com']]), + data: new Map([['key1', 'value1'], ['key2', 'value2']]), + }; + + odpManager.sendEvent(event); + const { data } = mockSendEvents.mock.calls[0][0]; + expect(data.get('os')).toEqual('os'); + expect(data.get('os_version')).toEqual('1.0'); + expect(data.get('device_type')).toEqual('phone'); + expect(data.get('model')).toEqual('model'); + }); + + it('sends identified event with both fs_user_id and vuid if both parameters are provided', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const mockSendEvents = vi.mocked(eventManager.sendEvent as OdpEventManager['sendEvent']); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + odpManager.identifyUser('user', 'vuid_a'); + expect(mockSendEvents).toHaveBeenCalledOnce(); + const { identifiers } = mockSendEvents.mock.calls[0][0]; + expect(identifiers).toEqual(new Map([['fs_user_id', 'user'], ['vuid', 'vuid_a']])); + }); + + it('sends identified event when called with just fs_user_id in first parameter', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const mockSendEvents = vi.mocked(eventManager.sendEvent as OdpEventManager['sendEvent']); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + odpManager.identifyUser('user'); + expect(mockSendEvents).toHaveBeenCalledOnce(); + const { identifiers } = mockSendEvents.mock.calls[0][0]; + expect(identifiers).toEqual(new Map([['fs_user_id', 'user']])); + }); + + it('sends identified event when called with just vuid in first parameter', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + + const mockSendEvents = vi.mocked(eventManager.sendEvent as OdpEventManager['sendEvent']); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.updateConfig({ integrated: true, odpConfig: config }); + await odpManager.onRunning(); + + odpManager.identifyUser('vuid_a'); + expect(mockSendEvents).toHaveBeenCalledOnce(); + const { identifiers } = mockSendEvents.mock.calls[0][0]; + expect(identifiers).toEqual(new Map([['vuid', 'vuid_a']])); + }); + + it('should reject onRunning() if stopped in new state', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + eventManager.onTerminated.mockReturnValue(Promise.resolve()); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.stop(); + + await expect(odpManager.onRunning()).rejects.toThrow(); + }); + + it('should reject onRunning() if stopped in starting state', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + eventManager.onTerminated.mockReturnValue(Promise.resolve()); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + expect(odpManager.getState()).toEqual(ServiceState.Starting); + + odpManager.stop(); + await expect(odpManager.onRunning()).rejects.toThrow(); + }); + + it('should go to stopping state and wait for eventManager to stop if stop is called', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + eventManager.onTerminated.mockReturnValue(resolvablePromise().promise); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.stop(); + + const terminatedHandler = vi.fn(); + odpManager.onTerminated().then(terminatedHandler); + + expect(odpManager.getState()).toEqual(ServiceState.Stopping); + await exhaustMicrotasks(); + expect(terminatedHandler).not.toHaveBeenCalled(); + }); + + it('should stop eventManager if stop is called', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + eventManager.onTerminated.mockReturnValue(Promise.resolve()); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + + odpManager.stop(); + expect(eventManager.stop).toHaveBeenCalled(); + }); + + it('should resolve onTerminated after eventManager stops successfully', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + const eventManagerTerminatedPromise = resolvablePromise<void>(); + eventManager.onTerminated.mockReturnValue(eventManagerTerminatedPromise.promise); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.stop(); + await exhaustMicrotasks(); + expect(odpManager.getState()).toEqual(ServiceState.Stopping); + + eventManagerTerminatedPromise.resolve(); + await expect(odpManager.onTerminated()).resolves.not.toThrow(); + }); + + it('should reject onTerminated after eventManager fails to stop correctly', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockReturnValue(Promise.resolve()); + const eventManagerTerminatedPromise = resolvablePromise<void>(); + eventManager.onTerminated.mockReturnValue(eventManagerTerminatedPromise.promise); + + const odpManager = new DefaultOdpManager({ + segmentManager: getMockOdpSegmentManager(), + eventManager, + }); + + odpManager.start(); + odpManager.stop(); + await exhaustMicrotasks(); + expect(odpManager.getState()).toEqual(ServiceState.Stopping); + + eventManagerTerminatedPromise.reject(new Error('FAILED_TO_STOP')); + await expect(odpManager.onTerminated()).rejects.toThrow(); + }); + + it('should call makeDisposable() on eventManager when makeDisposable() is called on odpManager', async () => { + const eventManager = getMockOdpEventManager(); + const segmentManager = getMockOdpSegmentManager(); + + const odpManager = new DefaultOdpManager({ + segmentManager, + eventManager, + }); + + odpManager.makeDisposable(); + + expect(eventManager.makeDisposable).toHaveBeenCalled(); + }); + + it('should call flushImmediately() on eventManager when flushImmediately() is called on odpManager', async () => { + const eventManager = getMockOdpEventManager(); + eventManager.onRunning.mockResolvedValue({}); + const segmentManager = getMockOdpSegmentManager(); + + eventManager.flushImmediately.mockResolvedValue({}); + + const odpManager = new DefaultOdpManager({ + segmentManager, + eventManager, + }); + + odpManager.updateConfig({ integrated: true, odpConfig: config }); + odpManager.start(); + + await odpManager.onRunning(); + + odpManager.flushImmediately(); + + expect(eventManager.flushImmediately).toHaveBeenCalledOnce(); + expect(odpManager.isRunning()).toBe(true); + }) +}); + diff --git a/lib/odp/odp_manager.ts b/lib/odp/odp_manager.ts new file mode 100644 index 000000000..feaca24b9 --- /dev/null +++ b/lib/odp/odp_manager.ts @@ -0,0 +1,265 @@ +/** + * Copyright 2023-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { v4 as uuidV4} from 'uuid'; +import { LoggerFacade } from '../logging/logger'; + +import { OdpIntegrationConfig, odpIntegrationsAreEqual } from './odp_config'; +import { OdpEventManager } from './event_manager/odp_event_manager'; +import { OdpSegmentManager } from './segment_manager/odp_segment_manager'; +import { OptimizelySegmentOption } from './segment_manager/optimizely_segment_option'; +import { OdpEvent } from './event_manager/odp_event'; +import { resolvablePromise, ResolvablePromise } from '../utils/promise/resolvablePromise'; +import { BaseService, Service, ServiceState } from '../service'; +import { UserAgentParser } from './ua_parser/user_agent_parser'; +import { CLIENT_VERSION, JAVASCRIPT_CLIENT_ENGINE } from '../utils/enums'; +import { ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION, ODP_USER_KEY } from './constant'; +import { isVuid } from '../vuid/vuid'; +import { Maybe } from '../utils/type'; +import { sprintf } from '../utils/fns'; +import { SERVICE_STOPPED_BEFORE_RUNNING } from '../service'; + +export interface OdpManager extends Service { + updateConfig(odpIntegrationConfig: OdpIntegrationConfig): boolean; + fetchQualifiedSegments(userId: string, options?: Array<OptimizelySegmentOption>): Promise<string[] | null>; + identifyUser(userId: string, vuid?: string): void; + sendEvent(event: OdpEvent): void; + setClientInfo(clientEngine: string, clientVersion: string): void; + setVuid(vuid: string): void; + setLogger(logger: LoggerFacade): void; + flushImmediately(): Promise<unknown>; +} + +export type OdpManagerConfig = { + segmentManager: OdpSegmentManager; + eventManager: OdpEventManager; + logger?: LoggerFacade; + userAgentParser?: UserAgentParser; +}; + +export const LOGGER_NAME = 'OdpManager'; + +export class DefaultOdpManager extends BaseService implements OdpManager { + private configPromise: ResolvablePromise<void>; + private segmentManager: OdpSegmentManager; + private eventManager: OdpEventManager; + private odpIntegrationConfig?: OdpIntegrationConfig; + private vuid?: string; + private clientEngine = JAVASCRIPT_CLIENT_ENGINE; + private clientVersion = CLIENT_VERSION; + private userAgentData?: Map<string, unknown>; + + constructor(config: OdpManagerConfig) { + super(); + this.segmentManager = config.segmentManager; + this.eventManager = config.eventManager; + + this.configPromise = resolvablePromise(); + + if (config.userAgentParser) { + const { os, device } = config.userAgentParser.parseUserAgentInfo(); + + const userAgentInfo: Record<string, unknown> = { + 'os': os.name, + 'os_version': os.version, + 'device_type': device.type, + 'model': device.model, + }; + + this.userAgentData = new Map<string, unknown>( + Object.entries(userAgentInfo).filter(([_, value]) => value != null && value != undefined) + ); + } + + if (config.logger) { + this.setLogger(config.logger); + } + } + + setLogger(logger: LoggerFacade): void { + this.logger = logger; + this.logger.setName(LOGGER_NAME); + this.eventManager.setLogger(logger.child()); + this.segmentManager.setLogger(logger.child()); + } + + setClientInfo(clientEngine: string, clientVersion: string): void { + this.clientEngine = clientEngine; + this.clientVersion = clientVersion; + } + + start(): void { + if (!this.isNew()) { + return; + } + + this.state = ServiceState.Starting; + + this.eventManager.start(); + + const startDependencies = [ + this.configPromise, + this.eventManager.onRunning(), + ]; + + Promise.all(startDependencies) + .then(() => { + this.handleStartSuccess(); + }).catch((err) => { + this.handleStartFailure(err); + }); + } + + makeDisposable(): void { + super.makeDisposable(); + this.eventManager.makeDisposable(); + } + + private handleStartSuccess() { + if (this.isDone()) { + return; + } + this.state = ServiceState.Running; + this.startPromise.resolve(); + } + + private handleStartFailure(error: Error) { + if (this.isDone()) { + return; + } + + this.state = ServiceState.Failed; + this.startPromise.reject(error); + this.stopPromise.reject(error); + } + + flushImmediately(): Promise<unknown> { + if (!this.isRunning()) { + return Promise.resolve(); + } + return this.eventManager.flushImmediately(); + } + + stop(): void { + if (this.isDone()) { + return; + } + + if (!this.isRunning()) { + this.startPromise.reject(new Error( + sprintf(SERVICE_STOPPED_BEFORE_RUNNING, 'OdpManager') + )); + } + + this.state = ServiceState.Stopping; + this.eventManager.stop(); + + this.eventManager.onTerminated().then(() => { + this.state = ServiceState.Terminated; + this.stopPromise.resolve(); + }).catch((err) => { + this.state = ServiceState.Failed; + this.stopPromise.reject(err); + }); + } + + updateConfig(odpIntegrationConfig: OdpIntegrationConfig): boolean { + // do nothing if config did not change + if (this.odpIntegrationConfig && odpIntegrationsAreEqual(this.odpIntegrationConfig, odpIntegrationConfig)) { + return false; + } + + if (this.isDone()) { + return false; + } + + this.odpIntegrationConfig = odpIntegrationConfig; + this.configPromise.resolve(); + this.segmentManager.updateConfig(odpIntegrationConfig) + this.eventManager.updateConfig(odpIntegrationConfig); + + return true; + } + + /** + * Attempts to fetch and return a list of a user's qualified segments from the local segments cache. + * If no cached data exists for the target user, this fetches and caches data from the ODP server instead. + * @param {string} userId - Unique identifier of a target user. + * @param {Array<OptimizelySegmentOption>} options - An array of OptimizelySegmentOption used to ignore and/or reset the cache. + * @returns {Promise<string[] | null>} A promise holding either a list of qualified segments or null. + */ + async fetchQualifiedSegments(userId: string, options: Array<OptimizelySegmentOption> = []): Promise<string[] | null> { + if (isVuid(userId)) { + return this.segmentManager.fetchQualifiedSegments(ODP_USER_KEY.VUID, userId, options); + } + + return this.segmentManager.fetchQualifiedSegments(ODP_USER_KEY.FS_USER_ID, userId, options); + } + + identifyUser(userId: string, vuid?: string): void { + const identifiers = new Map<string, string>(); + + let finalUserId: Maybe<string> = userId; + let finalVuid: Maybe<string> = vuid; + + if (!vuid && isVuid(userId)) { + finalVuid = userId; + finalUserId = undefined; + } + + if (finalVuid) { + identifiers.set(ODP_USER_KEY.VUID, finalVuid); + } + + if (finalUserId) { + identifiers.set(ODP_USER_KEY.FS_USER_ID, finalUserId); + } + + const event = new OdpEvent(ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION.IDENTIFIED, identifiers); + this.sendEvent(event); + } + + sendEvent(event: OdpEvent): void { + if (!event.identifiers.has(ODP_USER_KEY.VUID) && this.vuid) { + event.identifiers.set(ODP_USER_KEY.VUID, this.vuid); + } + + event.data = this.augmentCommonData(event.data); + this.eventManager.sendEvent(event); + } + + private augmentCommonData(sourceData: Map<string, unknown>): Map<string, unknown> { + const data = new Map<string, unknown>(this.userAgentData); + + data.set('idempotence_id', uuidV4()); + data.set('data_source_type', 'sdk'); + data.set('data_source', this.clientEngine); + data.set('data_source_version', this.clientVersion); + + sourceData.forEach((value, key) => data.set(key, value)); + return data; + } + + setVuid(vuid: string): void { + this.vuid = vuid; + this.onRunning().then(() => { + if (this.odpIntegrationConfig?.integrated) { + const event = new OdpEvent(ODP_DEFAULT_EVENT_TYPE, ODP_EVENT_ACTION.INITIALIZED); + this.sendEvent(event); + } + }); + } +} diff --git a/lib/odp/odp_manager_factory.browser.spec.ts b/lib/odp/odp_manager_factory.browser.spec.ts new file mode 100644 index 000000000..75edcdf3d --- /dev/null +++ b/lib/odp/odp_manager_factory.browser.spec.ts @@ -0,0 +1,127 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +vi.mock('../utils/http_request_handler/request_handler.browser', () => { + return { BrowserRequestHandler: vi.fn() }; +}); + +vi.mock('./odp_manager_factory', () => { + return { + getOdpManager: vi.fn().mockImplementation(() => ({})), + getOpaqueOdpManager: vi.fn().mockImplementation(() => ({})) + }; +}); + + +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { getOpaqueOdpManager, OdpManagerOptions } from './odp_manager_factory'; +import { BROWSER_DEFAULT_API_TIMEOUT, BROWSER_DEFAULT_BATCH_SIZE, BROWSER_DEFAULT_FLUSH_INTERVAL, createOdpManager } from './odp_manager_factory.browser'; +import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser'; +import { eventApiRequestGenerator, pixelApiRequestGenerator } from './event_manager/odp_event_api_manager'; + +describe('createOdpManager', () => { + const MockBrowserRequestHandler = vi.mocked(BrowserRequestHandler); + const mockGetOpaqueOdpManager = vi.mocked(getOpaqueOdpManager); + + beforeEach(() => { + MockBrowserRequestHandler.mockClear(); + mockGetOpaqueOdpManager.mockClear(); + }); + + it('should use BrowserRequestHandler with the provided timeout as the segment request handler', () => { + const odpManager = createOdpManager({ segmentsApiTimeout: 3456 }); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { segmentRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(segmentRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[0]); + const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[0][0]; + expect(requestHandlerOptions?.timeout).toBe(3456); + }); + + it('should use BrowserRequestHandler with the browser default timeout as the segment request handler', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { segmentRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(segmentRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[0]); + const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[0][0]; + expect(requestHandlerOptions?.timeout).toBe(BROWSER_DEFAULT_API_TIMEOUT); + }); + + it('should use BrowserRequestHandler with the provided timeout as the event request handler', () => { + const odpManager = createOdpManager({ eventApiTimeout: 2345 }); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(eventRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[1]); + const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[1][0]; + expect(requestHandlerOptions?.timeout).toBe(2345); + }); + + it('should use BrowserRequestHandler with the browser default timeout as the event request handler', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(eventRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[1]); + const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[1][0]; + expect(requestHandlerOptions?.timeout).toBe(BROWSER_DEFAULT_API_TIMEOUT); + }); + + it('should use the provided eventBatchSize', () => { + const odpManager = createOdpManager({ eventBatchSize: 99 }); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventBatchSize } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(eventBatchSize).toBe(99); + }); + + it('should use the browser default eventBatchSize if none provided', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventBatchSize } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(eventBatchSize).toBe(BROWSER_DEFAULT_BATCH_SIZE); + }); + + it('should use the provided eventFlushInterval', () => { + const odpManager = createOdpManager({ eventFlushInterval: 9999 }); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventFlushInterval } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(eventFlushInterval).toBe(9999); + }); + + it('should use the browser default eventFlushInterval if none provided', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventFlushInterval } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(eventFlushInterval).toBe(BROWSER_DEFAULT_FLUSH_INTERVAL); + }); + + it('uses the event api request generator', () => { + const odpManager = createOdpManager({ }); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventRequestGenerator } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(eventRequestGenerator).toBe(eventApiRequestGenerator); + }); + + it('uses the passed options for relevant fields', () => { + const options: OdpManagerOptions = { + segmentsCache: {} as any, + segmentsCacheSize: 11, + segmentsCacheTimeout: 2025, + eventFlushInterval: 2222, + userAgentParser: {} as any, + }; + const odpManager = createOdpManager(options); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + expect(mockGetOpaqueOdpManager).toHaveBeenNthCalledWith(1, expect.objectContaining(options)); + }); +}); diff --git a/lib/odp/odp_manager_factory.browser.ts b/lib/odp/odp_manager_factory.browser.ts new file mode 100644 index 000000000..e5d97d8e1 --- /dev/null +++ b/lib/odp/odp_manager_factory.browser.ts @@ -0,0 +1,42 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser'; +import { eventApiRequestGenerator } from './event_manager/odp_event_api_manager'; +import { getOpaqueOdpManager, OdpManagerOptions, OpaqueOdpManager } from './odp_manager_factory'; + +export const BROWSER_DEFAULT_API_TIMEOUT = 10_000; +export const BROWSER_DEFAULT_BATCH_SIZE = 10; +export const BROWSER_DEFAULT_FLUSH_INTERVAL = 1000; + +export const createOdpManager = (options: OdpManagerOptions = {}): OpaqueOdpManager => { + const segmentRequestHandler = new BrowserRequestHandler({ + timeout: options.segmentsApiTimeout || BROWSER_DEFAULT_API_TIMEOUT, + }); + + const eventRequestHandler = new BrowserRequestHandler({ + timeout: options.eventApiTimeout || BROWSER_DEFAULT_API_TIMEOUT, + }); + + return getOpaqueOdpManager({ + ...options, + eventBatchSize: options.eventBatchSize || BROWSER_DEFAULT_BATCH_SIZE, + eventFlushInterval: options.eventFlushInterval || BROWSER_DEFAULT_FLUSH_INTERVAL, + segmentRequestHandler, + eventRequestHandler, + eventRequestGenerator: eventApiRequestGenerator, + }); +}; diff --git a/lib/odp/odp_manager_factory.node.spec.ts b/lib/odp/odp_manager_factory.node.spec.ts new file mode 100644 index 000000000..ac3bcb4ce --- /dev/null +++ b/lib/odp/odp_manager_factory.node.spec.ts @@ -0,0 +1,126 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +vi.mock('../utils/http_request_handler/request_handler.node', () => { + return { NodeRequestHandler: vi.fn() }; +}); + +vi.mock('./odp_manager_factory', () => { + return { + getOdpManager: vi.fn().mockImplementation(() => ({})), + getOpaqueOdpManager: vi.fn().mockImplementation(() => ({})) + }; +}); + + +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { getOpaqueOdpManager, OdpManagerOptions } from './odp_manager_factory'; +import { NODE_DEFAULT_API_TIMEOUT, NODE_DEFAULT_BATCH_SIZE, NODE_DEFAULT_FLUSH_INTERVAL, createOdpManager } from './odp_manager_factory.node'; +import { NodeRequestHandler } from '../utils/http_request_handler/request_handler.node'; +import { eventApiRequestGenerator } from './event_manager/odp_event_api_manager'; + +describe('createOdpManager', () => { + const MockNodeRequestHandler = vi.mocked(NodeRequestHandler); + const mockGetOpaqueOdpManager = vi.mocked(getOpaqueOdpManager); + + beforeEach(() => { + MockNodeRequestHandler.mockClear(); + mockGetOpaqueOdpManager.mockClear(); + }); + + it('should use NodeRequestHandler with the provided timeout as the segment request handler', () => { + const odpManager = createOdpManager({ segmentsApiTimeout: 3456 }); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { segmentRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(segmentRequestHandler).toBe(MockNodeRequestHandler.mock.instances[0]); + const requestHandlerOptions = MockNodeRequestHandler.mock.calls[0][0]; + expect(requestHandlerOptions?.timeout).toBe(3456); + }); + + it('should use NodeRequestHandler with the node default timeout as the segment request handler', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { segmentRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(segmentRequestHandler).toBe(MockNodeRequestHandler.mock.instances[0]); + const requestHandlerOptions = MockNodeRequestHandler.mock.calls[0][0]; + expect(requestHandlerOptions?.timeout).toBe(NODE_DEFAULT_API_TIMEOUT); + }); + + it('should use NodeRequestHandler with the provided timeout as the event request handler', () => { + const odpManager = createOdpManager({ eventApiTimeout: 2345 }); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(eventRequestHandler).toBe(MockNodeRequestHandler.mock.instances[1]); + const requestHandlerOptions = MockNodeRequestHandler.mock.calls[1][0]; + expect(requestHandlerOptions?.timeout).toBe(2345); + }); + + it('should use NodeRequestHandler with the node default timeout as the event request handler', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(eventRequestHandler).toBe(MockNodeRequestHandler.mock.instances[1]); + const requestHandlerOptions = MockNodeRequestHandler.mock.calls[1][0]; + expect(requestHandlerOptions?.timeout).toBe(NODE_DEFAULT_API_TIMEOUT); + }); + + it('uses the event api request generator', () => { + const odpManager = createOdpManager({ }); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventRequestGenerator } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(eventRequestGenerator).toBe(eventApiRequestGenerator); + }); + + it('should use the provided eventBatchSize', () => { + const odpManager = createOdpManager({ eventBatchSize: 99 }); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventBatchSize } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(eventBatchSize).toBe(99); + }); + + it('should use the node default eventBatchSize if none provided', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventBatchSize } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(eventBatchSize).toBe(NODE_DEFAULT_BATCH_SIZE); + }); + + it('should use the provided eventFlushInterval', () => { + const odpManager = createOdpManager({ eventFlushInterval: 9999 }); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventFlushInterval } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(eventFlushInterval).toBe(9999); + }); + + it('should use the node default eventFlushInterval if none provided', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventFlushInterval } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(eventFlushInterval).toBe(NODE_DEFAULT_FLUSH_INTERVAL); + }); + + it('uses the passed options for relevant fields', () => { + const options: OdpManagerOptions = { + segmentsCache: {} as any, + segmentsCacheSize: 11, + segmentsCacheTimeout: 2025, + userAgentParser: {} as any, + }; + const odpManager = createOdpManager(options); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + expect(mockGetOpaqueOdpManager).toHaveBeenNthCalledWith(1, expect.objectContaining(options)); + }); +}); diff --git a/lib/odp/odp_manager_factory.node.ts b/lib/odp/odp_manager_factory.node.ts new file mode 100644 index 000000000..7b8f737a7 --- /dev/null +++ b/lib/odp/odp_manager_factory.node.ts @@ -0,0 +1,42 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NodeRequestHandler } from '../utils/http_request_handler/request_handler.node'; +import { eventApiRequestGenerator } from './event_manager/odp_event_api_manager'; +import { getOpaqueOdpManager, OdpManagerOptions, OpaqueOdpManager } from './odp_manager_factory'; + +export const NODE_DEFAULT_API_TIMEOUT = 10_000; +export const NODE_DEFAULT_BATCH_SIZE = 10; +export const NODE_DEFAULT_FLUSH_INTERVAL = 1000; + +export const createOdpManager = (options: OdpManagerOptions = {}): OpaqueOdpManager => { + const segmentRequestHandler = new NodeRequestHandler({ + timeout: options.segmentsApiTimeout || NODE_DEFAULT_API_TIMEOUT, + }); + + const eventRequestHandler = new NodeRequestHandler({ + timeout: options.eventApiTimeout || NODE_DEFAULT_API_TIMEOUT, + }); + + return getOpaqueOdpManager({ + ...options, + segmentRequestHandler, + eventRequestHandler, + eventBatchSize: options.eventBatchSize || NODE_DEFAULT_BATCH_SIZE, + eventFlushInterval: options.eventFlushInterval || NODE_DEFAULT_FLUSH_INTERVAL, + eventRequestGenerator: eventApiRequestGenerator, + }); +}; diff --git a/lib/odp/odp_manager_factory.react_native.spec.ts b/lib/odp/odp_manager_factory.react_native.spec.ts new file mode 100644 index 000000000..95d7be4fc --- /dev/null +++ b/lib/odp/odp_manager_factory.react_native.spec.ts @@ -0,0 +1,126 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +vi.mock('../utils/http_request_handler/request_handler.browser', () => { + return { BrowserRequestHandler: vi.fn() }; +}); + +vi.mock('./odp_manager_factory', () => { + return { + getOdpManager: vi.fn().mockImplementation(() => ({})), + getOpaqueOdpManager: vi.fn().mockImplementation(() => ({})), + }; +}); + + +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { getOpaqueOdpManager, OdpManagerOptions } from './odp_manager_factory'; +import { RN_DEFAULT_API_TIMEOUT, RN_DEFAULT_BATCH_SIZE, RN_DEFAULT_FLUSH_INTERVAL, createOdpManager } from './odp_manager_factory.react_native'; +import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser' +import { eventApiRequestGenerator } from './event_manager/odp_event_api_manager'; + +describe('createOdpManager', () => { + const MockBrowserRequestHandler = vi.mocked(BrowserRequestHandler); + const mockGetOpaqueOdpManager = vi.mocked(getOpaqueOdpManager); + + beforeEach(() => { + MockBrowserRequestHandler.mockClear(); + mockGetOpaqueOdpManager.mockClear(); + }); + + it('should use BrowserRequestHandler with the provided timeout as the segment request handler', () => { + const odpManager = createOdpManager({ segmentsApiTimeout: 3456 }); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { segmentRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(segmentRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[0]); + const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[0][0]; + expect(requestHandlerOptions?.timeout).toBe(3456); + }); + + it('should use BrowserRequestHandler with the node default timeout as the segment request handler', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { segmentRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(segmentRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[0]); + const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[0][0]; + expect(requestHandlerOptions?.timeout).toBe(RN_DEFAULT_API_TIMEOUT); + }); + + it('should use BrowserRequestHandler with the provided timeout as the event request handler', () => { + const odpManager = createOdpManager({ eventApiTimeout: 2345 }); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(eventRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[1]); + const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[1][0]; + expect(requestHandlerOptions?.timeout).toBe(2345); + }); + + it('should use BrowserRequestHandler with the node default timeout as the event request handler', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventRequestHandler } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(eventRequestHandler).toBe(MockBrowserRequestHandler.mock.instances[1]); + const requestHandlerOptions = MockBrowserRequestHandler.mock.calls[1][0]; + expect(requestHandlerOptions?.timeout).toBe(RN_DEFAULT_API_TIMEOUT); + }); + + it('uses the event api request generator', () => { + const odpManager = createOdpManager({ }); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventRequestGenerator } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(eventRequestGenerator).toBe(eventApiRequestGenerator); + }); + + it('should use the provided eventBatchSize', () => { + const odpManager = createOdpManager({ eventBatchSize: 99 }); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventBatchSize } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(eventBatchSize).toBe(99); + }); + + it('should use the react_native default eventBatchSize if none provided', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventBatchSize } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(eventBatchSize).toBe(RN_DEFAULT_BATCH_SIZE); + }); + + it('should use the provided eventFlushInterval', () => { + const odpManager = createOdpManager({ eventFlushInterval: 9999 }); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventFlushInterval } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(eventFlushInterval).toBe(9999); + }); + + it('should use the react_native default eventFlushInterval if none provided', () => { + const odpManager = createOdpManager({}); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + const { eventFlushInterval } = mockGetOpaqueOdpManager.mock.calls[0][0]; + expect(eventFlushInterval).toBe(RN_DEFAULT_FLUSH_INTERVAL); + }); + + it('uses the passed options for relevant fields', () => { + const options: OdpManagerOptions = { + segmentsCache: {} as any, + segmentsCacheSize: 11, + segmentsCacheTimeout: 2025, + userAgentParser: {} as any, + }; + const odpManager = createOdpManager(options); + expect(odpManager).toBe(mockGetOpaqueOdpManager.mock.results[0].value); + expect(mockGetOpaqueOdpManager).toHaveBeenNthCalledWith(1, expect.objectContaining(options)); + }); +}); diff --git a/lib/odp/odp_manager_factory.react_native.ts b/lib/odp/odp_manager_factory.react_native.ts new file mode 100644 index 000000000..c76312d6d --- /dev/null +++ b/lib/odp/odp_manager_factory.react_native.ts @@ -0,0 +1,43 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser'; +import { eventApiRequestGenerator } from './event_manager/odp_event_api_manager'; +import { OdpManager } from './odp_manager'; +import { getOpaqueOdpManager, OdpManagerOptions, OpaqueOdpManager } from './odp_manager_factory'; + +export const RN_DEFAULT_API_TIMEOUT = 10_000; +export const RN_DEFAULT_BATCH_SIZE = 10; +export const RN_DEFAULT_FLUSH_INTERVAL = 1000; + +export const createOdpManager = (options: OdpManagerOptions = {}): OpaqueOdpManager => { + const segmentRequestHandler = new BrowserRequestHandler({ + timeout: options.segmentsApiTimeout || RN_DEFAULT_API_TIMEOUT, + }); + + const eventRequestHandler = new BrowserRequestHandler({ + timeout: options.eventApiTimeout || RN_DEFAULT_API_TIMEOUT, + }); + + return getOpaqueOdpManager({ + ...options, + segmentRequestHandler, + eventRequestHandler, + eventBatchSize: options.eventBatchSize || RN_DEFAULT_BATCH_SIZE, + eventFlushInterval: options.eventFlushInterval || RN_DEFAULT_FLUSH_INTERVAL, + eventRequestGenerator: eventApiRequestGenerator, + }); +}; diff --git a/lib/odp/odp_manager_factory.spec.ts b/lib/odp/odp_manager_factory.spec.ts new file mode 100644 index 000000000..b80689b79 --- /dev/null +++ b/lib/odp/odp_manager_factory.spec.ts @@ -0,0 +1,415 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { getMockSyncCache } from '../tests/mock/mock_cache'; + +vi.mock('./odp_manager', () => { + return { + DefaultOdpManager: vi.fn(), + }; +}); + +vi.mock('./segment_manager/odp_segment_manager', () => { + return { + DefaultOdpSegmentManager: vi.fn(), + }; +}); + +vi.mock('./segment_manager/odp_segment_api_manager', () => { + return { + DefaultOdpSegmentApiManager: vi.fn(), + }; +}); + +vi.mock('../utils/cache/in_memory_lru_cache', () => { + return { + InMemoryLruCache: vi.fn(), + }; +}); + +vi.mock('./event_manager/odp_event_manager', () => { + return { + DefaultOdpEventManager: vi.fn(), + }; +}); + +vi.mock('./event_manager/odp_event_api_manager', () => { + return { + DefaultOdpEventApiManager: vi.fn(), + }; +}); + +vi.mock( '../utils/repeater/repeater', () => { + return { + IntervalRepeater: vi.fn(), + ExponentialBackoff: vi.fn(), + }; +}); + +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { DefaultOdpManager } from './odp_manager'; +import { DEFAULT_CACHE_SIZE, DEFAULT_CACHE_TIMEOUT, DEFAULT_EVENT_BATCH_SIZE, DEFAULT_EVENT_MAX_BACKOFF, DEFAULT_EVENT_MAX_RETRIES, DEFAULT_EVENT_MIN_BACKOFF, getOdpManager } from './odp_manager_factory'; +import { getMockRequestHandler } from '../tests/mock/mock_request_handler'; +import { DefaultOdpSegmentManager } from './segment_manager/odp_segment_manager'; +import { DefaultOdpSegmentApiManager } from './segment_manager/odp_segment_api_manager'; +import { InMemoryLruCache } from '../utils/cache/in_memory_lru_cache'; +import { DefaultOdpEventManager } from './event_manager/odp_event_manager'; +import { DefaultOdpEventApiManager } from './event_manager/odp_event_api_manager'; +import { IntervalRepeater } from '../utils/repeater/repeater'; +import { ExponentialBackoff } from '../utils/repeater/repeater'; + +describe('getOdpManager', () => { + const MockDefaultOdpManager = vi.mocked(DefaultOdpManager); + const MockDefaultOdpSegmentManager = vi.mocked(DefaultOdpSegmentManager); + const MockDefaultOdpSegmentApiManager = vi.mocked(DefaultOdpSegmentApiManager); + const MockInMemoryLruCache = vi.mocked(InMemoryLruCache); + const MockDefaultOdpEventManager = vi.mocked(DefaultOdpEventManager); + const MockDefaultOdpEventApiManager = vi.mocked(DefaultOdpEventApiManager); + const MockIntervalRepeater = vi.mocked(IntervalRepeater); + const MockExponentialBackoff = vi.mocked(ExponentialBackoff); + + beforeEach(() => { + MockDefaultOdpManager.mockClear(); + MockDefaultOdpSegmentManager.mockClear(); + MockDefaultOdpSegmentApiManager.mockClear(); + MockInMemoryLruCache.mockClear(); + MockDefaultOdpEventManager.mockClear(); + MockDefaultOdpEventApiManager.mockClear(); + MockIntervalRepeater.mockClear(); + MockExponentialBackoff.mockClear(); + }); + + it('should throw and error if provided segment cache is invalid', () => { + expect(() => getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + segmentsCache: 'abc' as any + })).toThrow('Invalid cache'); + + expect(() => getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + segmentsCache: {} as any, + })).toThrow('Invalid cache method save, Invalid cache method lookup, Invalid cache method reset'); + + expect(() => getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + segmentsCache: { save: 'abc', lookup: 'abc', reset: 'abc' } as any, + })).toThrow('Invalid cache method save, Invalid cache method lookup, Invalid cache method reset'); + + const noop = () => {}; + + expect(() => getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + segmentsCache: { save: noop, lookup: 'abc', reset: 'abc' } as any, + })).toThrow('Invalid cache method lookup, Invalid cache method reset'); + + expect(() => getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + segmentsCache: { save: noop, lookup: noop, reset: 'abc' } as any, + })).toThrow('Invalid cache method reset'); + }); + + describe('segment manager', () => { + it('should create a default segment manager with default api manager using the passed eventRequestHandler', () => { + const segmentRequestHandler = getMockRequestHandler(); + const odpManager = getOdpManager({ + segmentRequestHandler, + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + }); + + expect(Object.is(odpManager, MockDefaultOdpManager.mock.instances[0])).toBe(true); + const { segmentManager: usedSegmentManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(Object.is(usedSegmentManager, MockDefaultOdpSegmentManager.mock.instances[0])).toBe(true); + const apiManager = MockDefaultOdpSegmentManager.mock.calls[0][1]; + expect(Object.is(apiManager, MockDefaultOdpSegmentApiManager.mock.instances[0])).toBe(true); + const usedRequestHandler = MockDefaultOdpSegmentApiManager.mock.calls[0][0]; + expect(Object.is(usedRequestHandler, segmentRequestHandler)).toBe(true); + }); + + it('should create a default segment manager with the provided segment cache', () => { + const segmentsCache = getMockSyncCache<string[]>(); + + const odpManager = getOdpManager({ + segmentsCache, + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + }); + + expect(Object.is(odpManager, MockDefaultOdpManager.mock.instances[0])).toBe(true); + const { segmentManager: usedSegmentManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(Object.is(usedSegmentManager, MockDefaultOdpSegmentManager.mock.instances[0])).toBe(true); + const usedCache = MockDefaultOdpSegmentManager.mock.calls[0][0]; + expect(usedCache).toBe(segmentsCache); + }); + + describe('when no segment cache is provided', () => { + it('should use a InMemoryLruCache with the provided size', () => { + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + segmentsCacheSize: 3141, + }); + + expect(Object.is(odpManager, MockDefaultOdpManager.mock.instances[0])).toBe(true); + const { segmentManager: usedSegmentManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(Object.is(usedSegmentManager, MockDefaultOdpSegmentManager.mock.instances[0])).toBe(true); + const usedCache = MockDefaultOdpSegmentManager.mock.calls[0][0]; + expect(usedCache).toBe(MockInMemoryLruCache.mock.instances[0]); + expect(MockInMemoryLruCache.mock.calls[0][0]).toBe(3141); + }); + + it('should use a InMemoryLruCache with default size if no segmentCacheSize is provided', () => { + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + }); + + expect(Object.is(odpManager, MockDefaultOdpManager.mock.instances[0])).toBe(true); + const { segmentManager: usedSegmentManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(Object.is(usedSegmentManager, MockDefaultOdpSegmentManager.mock.instances[0])).toBe(true); + const usedCache = MockDefaultOdpSegmentManager.mock.calls[0][0]; + expect(usedCache).toBe(MockInMemoryLruCache.mock.instances[0]); + expect(MockInMemoryLruCache.mock.calls[0][0]).toBe(DEFAULT_CACHE_SIZE); + }); + + it('should use a InMemoryLruCache with the provided timeout', () => { + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + segmentsCacheTimeout: 123456, + }); + + expect(Object.is(odpManager, MockDefaultOdpManager.mock.instances[0])).toBe(true); + const { segmentManager: usedSegmentManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(Object.is(usedSegmentManager, MockDefaultOdpSegmentManager.mock.instances[0])).toBe(true); + const usedCache = MockDefaultOdpSegmentManager.mock.calls[0][0]; + expect(usedCache).toBe(MockInMemoryLruCache.mock.instances[0]); + expect(MockInMemoryLruCache.mock.calls[0][1]).toBe(123456); + }); + + it('should use a InMemoryLruCache with default timeout if no segmentsCacheTimeout is provided', () => { + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + }); + + expect(Object.is(odpManager, MockDefaultOdpManager.mock.instances[0])).toBe(true); + const { segmentManager: usedSegmentManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(Object.is(usedSegmentManager, MockDefaultOdpSegmentManager.mock.instances[0])).toBe(true); + const usedCache = MockDefaultOdpSegmentManager.mock.calls[0][0]; + expect(usedCache).toBe(MockInMemoryLruCache.mock.instances[0]); + expect(MockInMemoryLruCache.mock.calls[0][1]).toBe(DEFAULT_CACHE_TIMEOUT); + }); + }); + }); + + describe('event manager', () => { + it('should use a default event manager with default api manager using the passed eventRequestHandler and eventRequestGenerator', () => { + const eventRequestHandler = getMockRequestHandler(); + const eventRequestGenerator = vi.fn(); + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler, + eventRequestGenerator, + }); + + expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); + const { eventManager: usedEventManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedEventManager).toBe(MockDefaultOdpEventManager.mock.instances[0]); + const apiManager = MockDefaultOdpEventManager.mock.calls[0][0].apiManager; + expect(apiManager).toBe(MockDefaultOdpEventApiManager.mock.instances[0]); + const usedRequestHandler = MockDefaultOdpEventApiManager.mock.calls[0][0]; + expect(usedRequestHandler).toBe(eventRequestHandler); + const usedRequestGenerator = MockDefaultOdpEventApiManager.mock.calls[0][1]; + expect(usedRequestGenerator).toBe(eventRequestGenerator); + }); + + it('should use a default event manager with the provided event batch size', () => { + const eventBatchSize = 1234; + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + eventBatchSize, + }); + + expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); + const { eventManager: usedEventManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedEventManager).toBe(MockDefaultOdpEventManager.mock.instances[0]); + const usedBatchSize = MockDefaultOdpEventManager.mock.calls[0][0].batchSize; + expect(usedBatchSize).toBe(eventBatchSize); + }); + + it('should use a default event manager with the default batch size if no eventBatchSize is provided', () => { + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + }); + + expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); + const { eventManager: usedEventManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedEventManager).toBe(MockDefaultOdpEventManager.mock.instances[0]); + const usedBatchSize = MockDefaultOdpEventManager.mock.calls[0][0].batchSize; + expect(usedBatchSize).toBe(DEFAULT_EVENT_BATCH_SIZE); + }); + + it('should use a default event manager with an interval repeater with the provided flush interval', () => { + const eventFlushInterval = 1234; + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + eventFlushInterval, + }); + + expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); + const { eventManager: usedEventManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedEventManager).toBe(MockDefaultOdpEventManager.mock.instances[0]); + const usedRepeater = MockDefaultOdpEventManager.mock.calls[0][0].repeater; + expect(usedRepeater).toBe(MockIntervalRepeater.mock.instances[0]); + const usedInterval = MockIntervalRepeater.mock.calls[0][0]; + expect(usedInterval).toBe(eventFlushInterval); + }); + + it('should use a default event manager with the provided max retries', () => { + const eventMaxRetries = 7; + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + eventMaxRetries, + }); + + expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); + const { eventManager: usedEventManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedEventManager).toBe(MockDefaultOdpEventManager.mock.instances[0]); + const usedMaxRetries = MockDefaultOdpEventManager.mock.calls[0][0].retryConfig.maxRetries; + expect(usedMaxRetries).toBe(eventMaxRetries); + }); + + it('should use a default event manager with the default max retries if no eventMaxRetries is provided', () => { + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + }); + + expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); + const { eventManager: usedEventManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedEventManager).toBe(MockDefaultOdpEventManager.mock.instances[0]); + const usedMaxRetries = MockDefaultOdpEventManager.mock.calls[0][0].retryConfig.maxRetries; + expect(usedMaxRetries).toBe(DEFAULT_EVENT_MAX_RETRIES); + }); + + it('should use a default event manager with ExponentialBackoff with provided minBackoff', () => { + const eventMinBackoff = 1234; + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + eventMinBackoff, + }); + + expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); + const { eventManager: usedEventManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedEventManager).toBe(MockDefaultOdpEventManager.mock.instances[0]); + const usedBackoffProvider = MockDefaultOdpEventManager.mock.calls[0][0].retryConfig.backoffProvider; + const backoff = usedBackoffProvider(); + expect(Object.is(backoff, MockExponentialBackoff.mock.instances[0])).toBe(true); + expect(MockExponentialBackoff.mock.calls[0][0]).toBe(eventMinBackoff); + }); + + it('should use a default event manager with ExponentialBackoff with default min backoff if none provided', () => { + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + }); + + expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); + const { eventManager: usedEventManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedEventManager).toBe(MockDefaultOdpEventManager.mock.instances[0]); + const usedBackoffProvider = MockDefaultOdpEventManager.mock.calls[0][0].retryConfig.backoffProvider; + const backoff = usedBackoffProvider(); + expect(Object.is(backoff, MockExponentialBackoff.mock.instances[0])).toBe(true); + expect(MockExponentialBackoff.mock.calls[0][0]).toBe(DEFAULT_EVENT_MIN_BACKOFF); + }); + + it('should use a default event manager with ExponentialBackoff with provided maxBackoff', () => { + const eventMaxBackoff = 9999; + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + eventMaxBackoff: eventMaxBackoff, + }); + + expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); + const { eventManager: usedEventManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedEventManager).toBe(MockDefaultOdpEventManager.mock.instances[0]); + const usedBackoffProvider = MockDefaultOdpEventManager.mock.calls[0][0].retryConfig.backoffProvider; + const backoff = usedBackoffProvider(); + expect(Object.is(backoff, MockExponentialBackoff.mock.instances[0])).toBe(true); + expect(MockExponentialBackoff.mock.calls[0][1]).toBe(eventMaxBackoff); + }); + + it('should use a default event manager with ExponentialBackoff with default max backoff if none provided', () => { + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + }); + + expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); + const { eventManager: usedEventManager } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedEventManager).toBe(MockDefaultOdpEventManager.mock.instances[0]); + const usedBackoffProvider = MockDefaultOdpEventManager.mock.calls[0][0].retryConfig.backoffProvider; + const backoff = usedBackoffProvider(); + expect(Object.is(backoff, MockExponentialBackoff.mock.instances[0])).toBe(true); + expect(MockExponentialBackoff.mock.calls[0][1]).toBe(DEFAULT_EVENT_MAX_BACKOFF); + }); + }); + + it('should use the provided userAgentParser', () => { + const userAgentParser = {} as any; + + const odpManager = getOdpManager({ + segmentRequestHandler: getMockRequestHandler(), + eventRequestHandler: getMockRequestHandler(), + eventRequestGenerator: vi.fn(), + userAgentParser, + }); + + expect(odpManager).toBe(MockDefaultOdpManager.mock.instances[0]); + const { userAgentParser: usedUserAgentParser } = MockDefaultOdpManager.mock.calls[0][0]; + expect(usedUserAgentParser).toBe(userAgentParser); + }); +}); diff --git a/lib/odp/odp_manager_factory.ts b/lib/odp/odp_manager_factory.ts new file mode 100644 index 000000000..45c79e591 --- /dev/null +++ b/lib/odp/odp_manager_factory.ts @@ -0,0 +1,138 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RequestHandler } from "../shared_types"; +import { Cache } from "../utils/cache/cache"; +import { InMemoryLruCache } from "../utils/cache/in_memory_lru_cache"; +import { ExponentialBackoff, IntervalRepeater } from "../utils/repeater/repeater"; +import { Maybe } from "../utils/type"; +import { DefaultOdpEventApiManager, EventRequestGenerator } from "./event_manager/odp_event_api_manager"; +import { DefaultOdpEventManager, OdpEventManager } from "./event_manager/odp_event_manager"; +import { DefaultOdpManager, OdpManager } from "./odp_manager"; +import { DefaultOdpSegmentApiManager } from "./segment_manager/odp_segment_api_manager"; +import { DefaultOdpSegmentManager, OdpSegmentManager } from "./segment_manager/odp_segment_manager"; +import { UserAgentParser } from "./ua_parser/user_agent_parser"; + +export const DEFAULT_CACHE_SIZE = 10_000; +export const DEFAULT_CACHE_TIMEOUT = 600_000; + +export const DEFAULT_EVENT_BATCH_SIZE = 100; +export const DEFAULT_EVENT_FLUSH_INTERVAL = 10_000; +export const DEFAULT_EVENT_MAX_RETRIES = 5; +export const DEFAULT_EVENT_MIN_BACKOFF = 1000; +export const DEFAULT_EVENT_MAX_BACKOFF = 32_000; + +export const INVALID_CACHE = 'Invalid cache'; +export const INVALID_CACHE_METHOD = 'Invalid cache method %s'; + +const odpManagerSymbol: unique symbol = Symbol(); + +export type OpaqueOdpManager = { + [odpManagerSymbol]: unknown; +}; + +export type OdpManagerOptions = { + segmentsCache?: Cache<string[]>; + segmentsCacheSize?: number; + segmentsCacheTimeout?: number; + segmentsApiTimeout?: number; + eventFlushInterval?: number; + eventBatchSize?: number; + eventApiTimeout?: number; + userAgentParser?: UserAgentParser; +}; + +export type OdpManagerFactoryOptions = Omit<OdpManagerOptions, 'segmentsApiTimeout' | 'eventApiTimeout'> & { + segmentRequestHandler: RequestHandler; + eventRequestHandler: RequestHandler; + eventRequestGenerator: EventRequestGenerator; + eventMaxRetries?: number; + eventMinBackoff?: number; + eventMaxBackoff?: number; +} + +const validateCache = (cache: any) => { + const errors = []; + if (!cache || typeof cache !== 'object') { + throw new Error(INVALID_CACHE); + } + + for (const method of ['save', 'lookup', 'reset']) { + if (typeof cache[method] !== 'function') { + errors.push(INVALID_CACHE_METHOD.replace('%s', method)); + } + } + + if (errors.length > 0) { + throw new Error(errors.join(', ')); + } +} + +const getDefaultSegmentsCache = (cacheSize?: number, cacheTimeout?: number) => { + return new InMemoryLruCache<string[]>(cacheSize || DEFAULT_CACHE_SIZE, cacheTimeout || DEFAULT_CACHE_TIMEOUT); +} + +const getDefaultSegmentManager = (options: OdpManagerFactoryOptions) => { + if (options.segmentsCache) { + validateCache(options.segmentsCache); + } + + return new DefaultOdpSegmentManager( + options.segmentsCache || getDefaultSegmentsCache(options.segmentsCacheSize, options.segmentsCacheTimeout), + new DefaultOdpSegmentApiManager(options.segmentRequestHandler), + ); +}; + +const getDefaultEventManager = (options: OdpManagerFactoryOptions) => { + return new DefaultOdpEventManager({ + apiManager: new DefaultOdpEventApiManager(options.eventRequestHandler, options.eventRequestGenerator), + batchSize: options.eventBatchSize || DEFAULT_EVENT_BATCH_SIZE, + repeater: new IntervalRepeater(options.eventFlushInterval || DEFAULT_EVENT_FLUSH_INTERVAL), + retryConfig: { + maxRetries: options.eventMaxRetries || DEFAULT_EVENT_MAX_RETRIES, + backoffProvider: () => new ExponentialBackoff( + options.eventMinBackoff || DEFAULT_EVENT_MIN_BACKOFF, + options.eventMaxBackoff || DEFAULT_EVENT_MAX_BACKOFF, + 500, + ), + }, + }); +} + +export const getOdpManager = (options: OdpManagerFactoryOptions): OdpManager => { + const segmentManager = getDefaultSegmentManager(options); + const eventManager = getDefaultEventManager(options); + + return new DefaultOdpManager({ + segmentManager, + eventManager, + userAgentParser: options.userAgentParser, + }); +}; + +export const getOpaqueOdpManager = (options: OdpManagerFactoryOptions): OpaqueOdpManager => { + return { + [odpManagerSymbol]: getOdpManager(options), + }; +}; + +export const extractOdpManager = (manager: Maybe<OpaqueOdpManager>): Maybe<OdpManager> => { + if (!manager || typeof manager !== 'object') { + return undefined; + } + + return manager[odpManagerSymbol] as Maybe<OdpManager>; +} diff --git a/lib/odp/odp_manager_factory.universal.ts b/lib/odp/odp_manager_factory.universal.ts new file mode 100644 index 000000000..6bf509611 --- /dev/null +++ b/lib/odp/odp_manager_factory.universal.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RequestHandler } from '../utils/http_request_handler/http'; +import { validateRequestHandler } from '../utils/http_request_handler/request_handler_validator'; +import { eventApiRequestGenerator } from './event_manager/odp_event_api_manager'; +import { getOpaqueOdpManager, OdpManagerOptions, OpaqueOdpManager } from './odp_manager_factory'; + +export const DEFAULT_API_TIMEOUT = 10_000; +export const DEFAULT_BATCH_SIZE = 1; +export const DEFAULT_FLUSH_INTERVAL = 1000; + +export type UniversalOdpManagerOptions = OdpManagerOptions & { + requestHandler: RequestHandler; +}; + +export const createOdpManager = (options: UniversalOdpManagerOptions): OpaqueOdpManager => { + validateRequestHandler(options.requestHandler); + return getOpaqueOdpManager({ + ...options, + segmentRequestHandler: options.requestHandler, + eventRequestHandler: options.requestHandler, + eventBatchSize: options.eventBatchSize || DEFAULT_BATCH_SIZE, + eventFlushInterval: options.eventFlushInterval || DEFAULT_FLUSH_INTERVAL, + eventRequestGenerator: eventApiRequestGenerator, + }); +}; diff --git a/packages/optimizely-sdk/lib/plugins/odp/odp_types.ts b/lib/odp/odp_types.ts similarity index 96% rename from packages/optimizely-sdk/lib/plugins/odp/odp_types.ts rename to lib/odp/odp_types.ts index cb034a3c9..abe47b245 100644 --- a/packages/optimizely-sdk/lib/plugins/odp/odp_types.ts +++ b/lib/odp/odp_types.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,6 +58,7 @@ export interface Location { * Extended error information */ export interface Extension { + code: string; classification: string; } diff --git a/packages/optimizely-sdk/lib/plugins/odp/odp_response_schema.ts b/lib/odp/segment_manager/odp_response_schema.ts similarity index 99% rename from packages/optimizely-sdk/lib/plugins/odp/odp_response_schema.ts rename to lib/odp/segment_manager/odp_response_schema.ts index 9aad4ac35..4221178af 100644 --- a/packages/optimizely-sdk/lib/plugins/odp/odp_response_schema.ts +++ b/lib/odp/segment_manager/odp_response_schema.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the 'License'); * you may not use this file except in compliance with the License. diff --git a/lib/odp/segment_manager/odp_segment_api_manager.spec.ts b/lib/odp/segment_manager/odp_segment_api_manager.spec.ts new file mode 100644 index 000000000..7906f5745 --- /dev/null +++ b/lib/odp/segment_manager/odp_segment_api_manager.spec.ts @@ -0,0 +1,262 @@ +/** + * Copyright 2022-2024 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect } from 'vitest'; + +import { ODP_USER_KEY } from '../constant'; +import { getMockRequestHandler } from '../../tests/mock/mock_request_handler'; +import { getMockLogger } from '../../tests/mock/mock_logger'; +import { DefaultOdpSegmentApiManager, LOGGER_NAME } from './odp_segment_api_manager'; + +const API_KEY = 'not-real-api-key'; +const GRAPHQL_ENDPOINT = 'https://some.example.com/graphql/endpoint'; +const USER_KEY = ODP_USER_KEY.FS_USER_ID; +const USER_VALUE = 'tester-101'; +const SEGMENTS_TO_CHECK = ['has_email', 'has_email_opted_in', 'push_on_sale']; + +describe('DefaultOdpSegmentApiManager', () => { + it('should set name on the logger passed into the constructor', () => { + const logger = getMockLogger(); + + const manager = new DefaultOdpSegmentApiManager(getMockRequestHandler(), logger); + + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME); + }); + + it('should set name on the logger set by setLogger', () => { + const logger = getMockLogger(); + + const manager = new DefaultOdpSegmentApiManager(getMockRequestHandler()); + manager.setLogger(logger); + + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME); + }); + + it('should return empty list without calling api when segmentsToCheck is empty', async () => { + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValue({ + abort: () => {}, + responsePromise: Promise.resolve({ statusCode: 200, body: '' }), + }); + const logger = getMockLogger(); + const manager = new DefaultOdpSegmentApiManager(requestHandler, logger); + const segments = await manager.fetchSegments(API_KEY, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, []); + + expect(segments).toEqual([]); + expect(requestHandler.makeRequest).not.toHaveBeenCalled(); + }); + + it('should return null and log error if requestHandler promise rejects', async () => { + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValue({ + abort: () => {}, + responsePromise: Promise.reject(new Error("Request timed out")), + }); + const logger = getMockLogger(); + const manager = new DefaultOdpSegmentApiManager(requestHandler, logger); + const segments = await manager.fetchSegments(API_KEY, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); + + expect(segments).toBeNull(); + expect(logger.error).toHaveBeenCalledOnce(); + }); + + it('should log error and return null in case of non 200 HTTP status code response', async () => { + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValue({ + abort: () => {}, + responsePromise: Promise.resolve({ statusCode: 500, body: '' }), + }); + const logger = getMockLogger(); + const manager = new DefaultOdpSegmentApiManager(requestHandler, logger); + const segments = await manager.fetchSegments(API_KEY, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); + + expect(segments).toBeNull(); + expect(logger.error).toHaveBeenCalledOnce(); + }); + + it('should return null and log error if response body is invalid JSON', async () => { + const invalidJsonResponse = 'not-a-valid-json-response'; + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValue({ + abort: () => {}, + responsePromise: Promise.resolve({ statusCode: 200, body: invalidJsonResponse }), + }); + + const logger = getMockLogger(); + const manager = new DefaultOdpSegmentApiManager(requestHandler, logger); + const segments = await manager.fetchSegments(API_KEY, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); + + expect(segments).toBeNull(); + expect(logger.error).toHaveBeenCalledOnce(); + }); + + it('should return null and log error if response body is unrecognized JSON', async () => { + const invalidJsonResponse = '{"a":1}'; + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValue({ + abort: () => {}, + responsePromise: Promise.resolve({ statusCode: 200, body: invalidJsonResponse }), + }); + + const logger = getMockLogger(); + const manager = new DefaultOdpSegmentApiManager(requestHandler, logger); + const segments = await manager.fetchSegments(API_KEY, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); + + expect(segments).toBeNull(); + expect(logger.error).toHaveBeenCalledOnce(); + }); + + it('should log error and return null in case of invalid identifier error response', async () => { + const INVALID_USER_ID = 'invalid-user'; + const errorJsonResponse = + '{"errors":[{"message":' + + '"Exception while fetching data (/customer) : ' + + `Exception: could not resolve _fs_user_id = ${INVALID_USER_ID}",` + + '"locations":[{"line":1,"column":8}],"path":["customer"],' + + '"extensions":{"code": "INVALID_IDENTIFIER_EXCEPTION","classification":"DataFetchingException"}}],' + + '"data":{"customer":null}}'; + + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValue({ + abort: () => {}, + responsePromise: Promise.resolve({ statusCode: 200, body: errorJsonResponse }), + }); + + const logger = getMockLogger(); + const manager = new DefaultOdpSegmentApiManager(requestHandler, logger); + const segments = await manager.fetchSegments(API_KEY, GRAPHQL_ENDPOINT, USER_KEY, 'mock_user_id', SEGMENTS_TO_CHECK); + expect(segments).toBeNull(); + expect(logger.error).toHaveBeenCalledWith('Audience segments fetch failed (invalid identifier)'); + }); + + it('should log error and return null in case of errors other than invalid identifier error', async () => { + const INVALID_USER_ID = 'invalid-user'; + const errorJsonResponse = + '{"errors":[{"message":' + + '"Exception while fetching data (/customer) : ' + + `Exception: could not resolve _fs_user_id = ${INVALID_USER_ID}",` + + '"locations":[{"line":1,"column":8}],"path":["customer"],' + + '"extensions":{"classification":"DataFetchingException"}}],' + + '"data":{"customer":null}}'; + + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValue({ + abort: () => {}, + responsePromise: Promise.resolve({ statusCode: 200, body: errorJsonResponse }), + }); + + const logger = getMockLogger(); + const manager = new DefaultOdpSegmentApiManager(requestHandler, logger); + const segments = await manager.fetchSegments(API_KEY, GRAPHQL_ENDPOINT, USER_KEY, 'mock_user_id', SEGMENTS_TO_CHECK); + expect(segments).toBeNull(); + expect(logger.error).toHaveBeenCalledWith('Audience segments fetch failed (DataFetchingException)'); + }); + + it('should log error and return null in case of response with invalid falsy edges field', async () => { + const jsonResponse = `{ + "data": { + "customer": { + "audiences": { + } + } + } + }`; + + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValue({ + abort: () => {}, + responsePromise: Promise.resolve({ statusCode: 200, body: jsonResponse }), + }); + + const logger = getMockLogger(); + const manager = new DefaultOdpSegmentApiManager(requestHandler, logger); + const segments = await manager.fetchSegments(API_KEY, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); + + expect(segments).toBeNull(); + expect(logger.error).toHaveBeenCalledOnce(); + }); + + it('should parse a success response and return qualified segments', async () => { + const validJsonResponse = `{ + "data": { + "customer": { + "audiences": { + "edges": [ + { + "node": { + "name": "has_email", + "state": "qualified" + } + }, + { + "node": { + "name": "has_email_opted_in", + "state": "not-qualified" + } + } + ] + } + } + } + }`; + + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValue({ + abort: () => {}, + responsePromise: Promise.resolve({ statusCode: 200, body: validJsonResponse }), + }); + + const manager = new DefaultOdpSegmentApiManager(requestHandler); + const segments = await manager.fetchSegments(API_KEY, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); + + expect(segments).toEqual(['has_email']); + }); + + it('should handle empty qualified segments', async () => { + const responseJsonWithNoQualifiedSegments = '{"data":{"customer":{"audiences":' + '{"edges":[ ]}}}}'; + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValue({ + abort: () => {}, + responsePromise: Promise.resolve({ statusCode: 200, body: responseJsonWithNoQualifiedSegments }), + }); + + const manager = new DefaultOdpSegmentApiManager(requestHandler); + const segments = await manager.fetchSegments(API_KEY, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); + + expect(segments).toEqual([]); + }); + + it('should construct a valid GraphQL query request', async () => { + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValue({ + abort: () => {}, + responsePromise: Promise.resolve({ statusCode: 200, body: '' }), + }); + + const manager = new DefaultOdpSegmentApiManager(requestHandler); + await manager.fetchSegments(API_KEY, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); + + expect(requestHandler.makeRequest).toHaveBeenCalledWith( + `${GRAPHQL_ENDPOINT}/v3/graphql`, + { + 'Content-Type': 'application/json', + 'x-api-key': API_KEY, + }, + 'POST', + `{"query" : "query {customer(${USER_KEY} : \\"${USER_VALUE}\\") {audiences(subset: [\\"has_email\\",\\"has_email_opted_in\\",\\"push_on_sale\\"]) {edges {node {name state}}}}}"}` + ); + }); +}); diff --git a/packages/optimizely-sdk/lib/plugins/odp/graphql_manager.ts b/lib/odp/segment_manager/odp_segment_api_manager.ts similarity index 60% rename from packages/optimizely-sdk/lib/plugins/odp/graphql_manager.ts rename to lib/odp/segment_manager/odp_segment_api_manager.ts index 12d8fbc8f..92eeaa02e 100644 --- a/packages/optimizely-sdk/lib/plugins/odp/graphql_manager.ts +++ b/lib/odp/segment_manager/odp_segment_api_manager.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2022-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,13 +14,13 @@ * limitations under the License. */ -import { LogHandler, LogLevel } from '../../modules/logging'; +import { LoggerFacade } from '../../logging/logger'; import { validate } from '../../utils/json_schema_validator'; import { OdpResponseSchema } from './odp_response_schema'; -import { ODP_USER_KEY } from '../../utils/enums'; -import { RequestHandler, Response as HttpResponse } from '../../utils/http_request_handler/http'; -import { Response as GraphQLResponse } from './odp_types'; - +import { ODP_USER_KEY } from '../constant'; +import { RequestHandler } from '../../utils/http_request_handler/http'; +import { Response as GraphQLResponse } from '../odp_types'; +import { log } from 'console'; /** * Expected value for a qualified/valid segment */ @@ -41,25 +41,33 @@ const AUDIENCE_FETCH_FAILURE_MESSAGE = 'Audience segments fetch failed'; /** * Manager for communicating with the Optimizely Data Platform GraphQL endpoint */ -export interface IGraphQLManager { - fetchSegments(apiKey: string, apiHost: string, userKey: string, userValue: string, segmentsToCheck: string[]): Promise<string[] | null>; +export interface OdpSegmentApiManager { + fetchSegments( + apiKey: string, + apiHost: string, + userKey: string, + userValue: string, + segmentsToCheck: string[] + ): Promise<string[] | null>; + setLogger(logger: LoggerFacade): void; } -/** - * Concrete implementation for communicating with the ODP GraphQL endpoint - */ -export class GraphQLManager implements IGraphQLManager { - private readonly logger: LogHandler; - private readonly requestHandler: RequestHandler; +export const LOGGER_NAME = 'OdpSegmentApiManager'; - /** - * Communicates with Optimizely Data Platform's GraphQL endpoint - * @param requestHandler Desired request handler for testing - * @param logger Collect and record events/errors for this GraphQL implementation - */ - constructor(requestHandler: RequestHandler, logger: LogHandler) { +export class DefaultOdpSegmentApiManager implements OdpSegmentApiManager { + private logger?: LoggerFacade; + private requestHandler: RequestHandler; + + constructor(requestHandler: RequestHandler, logger?: LoggerFacade) { this.requestHandler = requestHandler; + if (logger) { + this.setLogger(logger); + } + } + + setLogger(logger: LoggerFacade): void { this.logger = logger; + this.logger.setName(LOGGER_NAME); } /** @@ -70,12 +78,13 @@ export class GraphQLManager implements IGraphQLManager { * @param userValue Associated value to query for the user key * @param segmentsToCheck Audience segments to check for experiment inclusion */ - public async fetchSegments(apiKey: string, apiHost: string, userKey: ODP_USER_KEY, userValue: string, segmentsToCheck: string[]): Promise<string[] | null> { - if (!apiKey || !apiHost) { - this.logger.log(LogLevel.ERROR, `${AUDIENCE_FETCH_FAILURE_MESSAGE} (Parameters apiKey or apiHost invalid)`); - return null; - } - + async fetchSegments( + apiKey: string, + apiHost: string, + userKey: ODP_USER_KEY, + userValue: string, + segmentsToCheck: string[] + ): Promise<string[] | null> { if (segmentsToCheck?.length === 0) { return EMPTY_SEGMENTS_COLLECTION; } @@ -83,29 +92,33 @@ export class GraphQLManager implements IGraphQLManager { const endpoint = `${apiHost}/v3/graphql`; const query = this.toGraphQLJson(userKey, userValue, segmentsToCheck); - const segmentsResponse = await this.querySegments(apiKey, endpoint, userKey, userValue, query); + const segmentsResponse = await this.querySegments(apiKey, endpoint, query); if (!segmentsResponse) { - this.logger.log(LogLevel.ERROR, `${AUDIENCE_FETCH_FAILURE_MESSAGE} (network error)`); + this.logger?.error(`${AUDIENCE_FETCH_FAILURE_MESSAGE} (network error)`); return null; } const parsedSegments = this.parseSegmentsResponseJson(segmentsResponse); if (!parsedSegments) { - this.logger.log(LogLevel.ERROR, `${AUDIENCE_FETCH_FAILURE_MESSAGE} (decode error)`); + this.logger?.error(`${AUDIENCE_FETCH_FAILURE_MESSAGE} (decode error)`); return null; } if (parsedSegments.errors?.length > 0) { - const errors = parsedSegments.errors.map((e) => e.message).join('; '); + const { code, classification } = parsedSegments.errors[0].extensions; - this.logger.log(LogLevel.ERROR, `${AUDIENCE_FETCH_FAILURE_MESSAGE} (${errors})`); + if (code == 'INVALID_IDENTIFIER_EXCEPTION') { + this.logger?.error(`${AUDIENCE_FETCH_FAILURE_MESSAGE} (invalid identifier)`); + } else { + this.logger?.error(`${AUDIENCE_FETCH_FAILURE_MESSAGE} (${classification})`); + } return null; } const edges = parsedSegments?.data?.customer?.audiences?.edges; if (!edges) { - this.logger.log(LogLevel.ERROR, `${AUDIENCE_FETCH_FAILURE_MESSAGE} (decode error)`); + this.logger?.error(`${AUDIENCE_FETCH_FAILURE_MESSAGE} (decode error)`); return null; } @@ -116,16 +129,17 @@ export class GraphQLManager implements IGraphQLManager { * Converts the query parameters to a GraphQL JSON payload * @returns GraphQL JSON string */ - private toGraphQLJson = (userKey: string, userValue: string, segmentsToCheck: string[]): string => ([ - '{"query" : "query {customer"', - `(${userKey} : "${userValue}") `, - '{audiences', - '(subset: [', - ...segmentsToCheck?.map((segment, index) => - `\\"${segment}\\"${index < segmentsToCheck.length - 1 ? ',' : ''}`, - ) || '', - '] {edges {node {name state}}}}}"}', - ].join('')); + private toGraphQLJson = (userKey: string, userValue: string, segmentsToCheck: string[]): string => + [ + '{"query" : "query {customer', + `(${userKey} : \\"${userValue}\\") `, + '{audiences', + '(subset: [', + ...(segmentsToCheck?.map( + (segment, index) => `\\"${segment}\\"${index < segmentsToCheck.length - 1 ? ',' : ''}` + ) || ''), + ']) {edges {node {name state}}}}}"}', + ].join(''); /** * Handler for querying the ODP GraphQL endpoint @@ -136,7 +150,11 @@ export class GraphQLManager implements IGraphQLManager { * @param query GraphQL formatted query string * @returns JSON response string from ODP or null */ - private async querySegments(apiKey: string, endpoint: string, userKey: string, userValue: string, query: string): Promise<string | null> { + private async querySegments( + apiKey: string, + endpoint: string, + query: string + ): Promise<string | null> { const method = 'POST'; const url = endpoint; const headers = { @@ -144,15 +162,16 @@ export class GraphQLManager implements IGraphQLManager { 'x-api-key': apiKey, }; - let response: HttpResponse; try { const request = this.requestHandler.makeRequest(url, headers, method, query); - response = await request.responsePromise; + const { statusCode, body} = await request.responsePromise; + if (!(statusCode >= 200 && statusCode < 300)) { + return null; + } + return body; } catch { return null; } - - return response.body; } /** diff --git a/lib/odp/segment_manager/odp_segment_manager.spec.ts b/lib/odp/segment_manager/odp_segment_manager.spec.ts new file mode 100644 index 000000000..550e431b5 --- /dev/null +++ b/lib/odp/segment_manager/odp_segment_manager.spec.ts @@ -0,0 +1,226 @@ +/** + * Copyright 2022-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, vi } from 'vitest'; + + +import { ODP_USER_KEY } from '../constant'; +import { DefaultOdpSegmentManager } from './odp_segment_manager'; +import { OdpConfig } from '../odp_config'; +import { OptimizelySegmentOption } from './optimizely_segment_option'; +import { getMockLogger } from '../../tests/mock/mock_logger'; +import { getMockSyncCache } from '../../tests/mock/mock_cache'; +import { LOGGER_NAME } from './odp_segment_manager'; + +const API_KEY = 'test-api-key'; +const API_HOST = 'https://odp.example.com/'; +const PIXEL_URL = 'https://odp.pixel.com/'; +const SEGMENTS_TO_CHECK = ['segment1', 'segment2']; + +const config = new OdpConfig(API_KEY, API_HOST, PIXEL_URL, SEGMENTS_TO_CHECK); + +const getMockApiManager = () => { + return { + fetchSegments: vi.fn(), + setLogger: vi.fn(), + }; +}; + +const userKey: ODP_USER_KEY = ODP_USER_KEY.FS_USER_ID; +const userValue = 'test-user'; + +describe('DefaultOdpSegmentManager', () => { + describe('a logger is passed in the constructor', () => { + it('should set name on the logger passed into the constructor', () => { + const logger = getMockLogger(); + const cache = getMockSyncCache<string[]>(); + const manager = new DefaultOdpSegmentManager(cache, getMockApiManager(), logger); + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME); + }); + + it('should pass a child logger to the segmentApiManager', () => { + const logger = getMockLogger(); + const childLogger = getMockLogger(); + logger.child.mockReturnValue(childLogger); + + const cache = getMockSyncCache<string[]>(); + const apiManager = getMockApiManager(); + const manager = new DefaultOdpSegmentManager(cache, apiManager, logger); + + expect(apiManager.setLogger).toHaveBeenCalledWith(childLogger); + }); + }); + + describe('setLogger method', () => { + it('should set name on the logger', () => { + const logger = getMockLogger(); + const cache = getMockSyncCache<string[]>(); + const manager = new DefaultOdpSegmentManager(cache, getMockApiManager()); + manager.setLogger(logger); + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME); + }); + + it('should pass a child logger to the datafileManager', () => { + const logger = getMockLogger(); + const childLogger = getMockLogger(); + logger.child.mockReturnValue(childLogger); + + const cache = getMockSyncCache<string[]>(); + const apiManager = getMockApiManager(); + const manager = new DefaultOdpSegmentManager(cache, apiManager); + manager.setLogger(logger); + + expect(apiManager.setLogger).toHaveBeenCalledWith(childLogger); + }); + }); + + it('should return null and log error if the ODP config is not available.', async () => { + const logger = getMockLogger(); + const cache = getMockSyncCache<string[]>(); + const manager = new DefaultOdpSegmentManager(cache, getMockApiManager(), logger); + expect(await manager.fetchQualifiedSegments(userKey, userValue)).toBeNull(); + expect(logger.warn).toHaveBeenCalledOnce(); + }); + + it('should return null and log error if ODP is not integrated.', async () => { + const logger = getMockLogger(); + const cache = getMockSyncCache<string[]>(); + const manager = new DefaultOdpSegmentManager(cache, getMockApiManager(), logger); + manager.updateConfig({ integrated: false }); + expect(await manager.fetchQualifiedSegments(userKey, userValue)).toBeNull(); + expect(logger.warn).toHaveBeenCalledOnce(); + }); + + it('should fetch segments from apiManager using correct config on cache miss and save to cache.', async () => { + const cache = getMockSyncCache<string[]>(); + const apiManager = getMockApiManager(); + apiManager.fetchSegments.mockResolvedValue(['k', 'l']); + const manager = new DefaultOdpSegmentManager(cache, apiManager); + manager.updateConfig({ integrated: true, odpConfig: config }); + + const segments = await manager.fetchQualifiedSegments(userKey, userValue); + expect(segments).toEqual(['k', 'l']); + expect(apiManager.fetchSegments).toHaveBeenCalledWith(API_KEY, API_HOST, userKey, userValue, SEGMENTS_TO_CHECK); + expect(cache.get(manager.makeCacheKey(userKey, userValue))).toEqual(['k', 'l']); + }); + + it('should return segment from cache and not call apiManager on cache hit.', async () => { + const cache = getMockSyncCache<string[]>(); + const apiManager = getMockApiManager(); + + const manager = new DefaultOdpSegmentManager(cache, apiManager); + manager.updateConfig({ integrated: true, odpConfig: config }); + + cache.set(manager.makeCacheKey(userKey, userValue), ['x']); + + const segments = await manager.fetchQualifiedSegments(userKey, userValue); + expect(segments).toEqual(['x']); + + expect(apiManager.fetchSegments).not.toHaveBeenCalled(); + }); + + it('should return null when apiManager returns null.', async () => { + const cache = getMockSyncCache<string[]>(); + const apiManager = getMockApiManager(); + apiManager.fetchSegments.mockResolvedValue(null); + const manager = new DefaultOdpSegmentManager(cache, apiManager); + manager.updateConfig({ integrated: true, odpConfig: config }); + + const segments = await manager.fetchQualifiedSegments(userKey, userValue); + expect(segments).toBeNull(); + }); + + it('should ignore the cache if the option enum is included in the options array.', async () => { + const cache = getMockSyncCache<string[]>(); + const apiManager = getMockApiManager(); + apiManager.fetchSegments.mockResolvedValue(['k', 'l']); + const manager = new DefaultOdpSegmentManager(cache, apiManager); + manager.updateConfig({ integrated: true, odpConfig: config }); + cache.set(manager.makeCacheKey(userKey, userValue), ['x']); + + const segments = await manager.fetchQualifiedSegments(userKey, userValue, [OptimizelySegmentOption.IGNORE_CACHE]); + expect(segments).toEqual(['k', 'l']); + expect(cache.get(manager.makeCacheKey(userKey, userValue))).toEqual(['x']); + expect(apiManager.fetchSegments).toHaveBeenCalledWith(API_KEY, API_HOST, userKey, userValue, SEGMENTS_TO_CHECK); + }); + + it('should ignore the cache if the option string is included in the options array.', async () => { + const cache = getMockSyncCache<string[]>(); + const apiManager = getMockApiManager(); + apiManager.fetchSegments.mockResolvedValue(['k', 'l']); + const manager = new DefaultOdpSegmentManager(cache, apiManager); + manager.updateConfig({ integrated: true, odpConfig: config }); + cache.set(manager.makeCacheKey(userKey, userValue), ['x']); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const segments = await manager.fetchQualifiedSegments(userKey, userValue, ['IGNORE_CACHE']); + expect(segments).toEqual(['k', 'l']); + expect(cache.get(manager.makeCacheKey(userKey, userValue))).toEqual(['x']); + expect(apiManager.fetchSegments).toHaveBeenCalledWith(API_KEY, API_HOST, userKey, userValue, SEGMENTS_TO_CHECK); + }); + + it('should reset the cache if the option enum is included in the options array.', async () => { + const cache = getMockSyncCache<string[]>(); + const apiManager = getMockApiManager(); + apiManager.fetchSegments.mockResolvedValue(['k', 'l']); + + const manager = new DefaultOdpSegmentManager(cache, apiManager); + manager.updateConfig({ integrated: true, odpConfig: config }); + cache.set(manager.makeCacheKey(userKey, userValue), ['x']); + cache.set(manager.makeCacheKey(userKey, '123'), ['a']); + cache.set(manager.makeCacheKey(userKey, '456'), ['b']); + + const segments = await manager.fetchQualifiedSegments(userKey, userValue, [OptimizelySegmentOption.RESET_CACHE]); + expect(segments).toEqual(['k', 'l']); + expect(cache.get(manager.makeCacheKey(userKey, userValue))).toEqual(['k', 'l']); + expect(cache.size()).toBe(1); + }); + + it('should reset the cache if the option string is included in the options array.', async () => { + const cache = getMockSyncCache<string[]>(); + const apiManager = getMockApiManager(); + apiManager.fetchSegments.mockResolvedValue(['k', 'l']); + + const manager = new DefaultOdpSegmentManager(cache, apiManager); + manager.updateConfig({ integrated: true, odpConfig: config }); + cache.set(manager.makeCacheKey(userKey, userValue), ['x']); + cache.set(manager.makeCacheKey(userKey, '123'), ['a']); + cache.set(manager.makeCacheKey(userKey, '456'), ['b']); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const segments = await manager.fetchQualifiedSegments(userKey, userValue, ['RESET_CACHE']); + expect(segments).toEqual(['k', 'l']); + expect(cache.get(manager.makeCacheKey(userKey, userValue))).toEqual(['k', 'l']); + expect(cache.size()).toBe(1); + }); + + it('should reset the cache on config update.', async () => { + const cache = getMockSyncCache<string[]>(); + const apiManager = getMockApiManager(); + apiManager.fetchSegments.mockResolvedValue(['k', 'l']); + + const manager = new DefaultOdpSegmentManager(cache, apiManager); + cache.set(manager.makeCacheKey(userKey, userValue), ['x']); + cache.set(manager.makeCacheKey(userKey, '123'), ['a']); + cache.set(manager.makeCacheKey(userKey, '456'), ['b']); + + expect(cache.size()).toBe(3); + manager.updateConfig({ integrated: true, odpConfig: config }); + expect(cache.size()).toBe(0); + }); +}); diff --git a/lib/odp/segment_manager/odp_segment_manager.ts b/lib/odp/segment_manager/odp_segment_manager.ts new file mode 100644 index 000000000..4ff125672 --- /dev/null +++ b/lib/odp/segment_manager/odp_segment_manager.ts @@ -0,0 +1,130 @@ +/** + * Copyright 2022-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Cache } from '../../utils/cache/cache'; +import { OdpSegmentApiManager } from './odp_segment_api_manager'; +import { OdpIntegrationConfig } from '../odp_config'; +import { OptimizelySegmentOption } from './optimizely_segment_option'; +import { ODP_USER_KEY } from '../constant'; +import { LoggerFacade } from '../../logging/logger'; +import { ODP_CONFIG_NOT_AVAILABLE, ODP_NOT_INTEGRATED } from 'error_message'; + +export interface OdpSegmentManager { + fetchQualifiedSegments( + userKey: ODP_USER_KEY, + userValue: string, + options?: Array<OptimizelySegmentOption> + ): Promise<string[] | null>; + updateConfig(config: OdpIntegrationConfig): void; + setLogger(logger: LoggerFacade): void; +} + +export const LOGGER_NAME = 'OdpSegmentManager'; + +export class DefaultOdpSegmentManager implements OdpSegmentManager { + private odpIntegrationConfig?: OdpIntegrationConfig; + private segmentsCache: Cache<string[]>; + private odpSegmentApiManager: OdpSegmentApiManager + private logger?: LoggerFacade; + + constructor( + segmentsCache: Cache<string[]>, + odpSegmentApiManager: OdpSegmentApiManager, + logger?: LoggerFacade, + ) { + this.segmentsCache = segmentsCache; + this.odpSegmentApiManager = odpSegmentApiManager; + if (logger) { + this.setLogger(logger); + } + } + + setLogger(logger: LoggerFacade): void { + this.logger = logger; + this.logger.setName(LOGGER_NAME); + this.odpSegmentApiManager.setLogger(logger.child()); + } + + /** + * Attempts to fetch and return a list of a user's qualified segments from the local segments cache. + * If no cached data exists for the target user, this fetches and caches data from the ODP server instead. + * @param userKey Key used for identifying the id type. + * @param userValue The id value itself. + * @param options An array of OptimizelySegmentOption used to ignore and/or reset the cache. + * @returns Qualified segments for the user from the cache or the ODP server if the cache is empty. + */ + async fetchQualifiedSegments( + userKey: ODP_USER_KEY, + userValue: string, + options?: Array<OptimizelySegmentOption> + ): Promise<string[] | null> { + if (!this.odpIntegrationConfig) { + this.logger?.warn(ODP_CONFIG_NOT_AVAILABLE); + return null; + } + + if (!this.odpIntegrationConfig.integrated) { + this.logger?.warn(ODP_NOT_INTEGRATED); + return null; + } + + const odpConfig = this.odpIntegrationConfig.odpConfig; + + const segmentsToCheck = odpConfig.segmentsToCheck; + if (!segmentsToCheck || segmentsToCheck.length <= 0) { + return []; + } + + const cacheKey = this.makeCacheKey(userKey, userValue); + + const ignoreCache = options?.includes(OptimizelySegmentOption.IGNORE_CACHE); + const resetCache = options?.includes(OptimizelySegmentOption.RESET_CACHE); + + if (resetCache) { + this.segmentsCache.reset(); + } + + if (!ignoreCache) { + const cachedSegments = await this.segmentsCache.lookup(cacheKey); + if (cachedSegments) { + return cachedSegments; + } + } + + const segments = await this.odpSegmentApiManager.fetchSegments( + odpConfig.apiKey, + odpConfig.apiHost, + userKey, + userValue, + segmentsToCheck + ); + + if (segments && !ignoreCache) { + this.segmentsCache.save(cacheKey, segments); + } + + return segments; + } + + makeCacheKey(userKey: string, userValue: string): string { + return `${userKey}-$-${userValue}`; + } + + updateConfig(config: OdpIntegrationConfig): void { + this.odpIntegrationConfig = config; + this.segmentsCache.reset(); + } +} diff --git a/packages/event-processor/src/managed.ts b/lib/odp/segment_manager/optimizely_segment_option.ts similarity index 64% rename from packages/event-processor/src/managed.ts rename to lib/odp/segment_manager/optimizely_segment_option.ts index 686d2aa0f..cf7c801ef 100644 --- a/packages/event-processor/src/managed.ts +++ b/lib/odp/segment_manager/optimizely_segment_option.ts @@ -1,11 +1,11 @@ /** - * Copyright 2019, Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export interface Managed { - start(): void - stop(): Promise<any> +// Options for defining behavior of OdpSegmentManager's caching mechanism when calling fetchSegments() +export enum OptimizelySegmentOption { + IGNORE_CACHE = 'IGNORE_CACHE', + RESET_CACHE = 'RESET_CACHE', } diff --git a/lib/odp/ua_parser/ua_parser.ts b/lib/odp/ua_parser/ua_parser.ts new file mode 100644 index 000000000..8622b0ade --- /dev/null +++ b/lib/odp/ua_parser/ua_parser.ts @@ -0,0 +1,31 @@ +/** + * Copyright 2023, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { UAParser } from 'ua-parser-js'; +import { UserAgentInfo } from './user_agent_info'; +import { UserAgentParser } from './user_agent_parser'; + +const userAgentParser: UserAgentParser = { + parseUserAgentInfo(): UserAgentInfo { + const parser = new UAParser(); + const agentInfo = parser.getResult(); + const { os, device } = agentInfo; + return { os, device }; + } +} + +export function getUserAgentParser(): UserAgentParser { + return userAgentParser; +} diff --git a/packages/datafile-manager/src/index.react_native.ts b/lib/odp/ua_parser/user_agent_info.ts similarity index 70% rename from packages/datafile-manager/src/index.react_native.ts rename to lib/odp/ua_parser/user_agent_info.ts index e6706908d..e83b3b032 100644 --- a/packages/datafile-manager/src/index.react_native.ts +++ b/lib/odp/ua_parser/user_agent_info.ts @@ -1,11 +1,11 @@ /** - * Copyright 2020, Optimizely + * Copyright 2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,5 +14,13 @@ * limitations under the License. */ -export * from './datafileManager'; -export { default as HttpPollingDatafileManager } from './reactNativeDatafileManager'; +export type UserAgentInfo = { + os: { + name?: string, + version?: string, + }, + device: { + type?: string, + model?: string, + } +}; diff --git a/packages/optimizely-sdk/lib/modules/event_processor/managed.ts b/lib/odp/ua_parser/user_agent_parser.ts similarity index 71% rename from packages/optimizely-sdk/lib/modules/event_processor/managed.ts rename to lib/odp/ua_parser/user_agent_parser.ts index 6d89843de..9ca30c141 100644 --- a/packages/optimizely-sdk/lib/modules/event_processor/managed.ts +++ b/lib/odp/ua_parser/user_agent_parser.ts @@ -1,11 +1,11 @@ /** - * Copyright 2022, Optimizely + * Copyright 2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export interface Managed { - start(): void - stop(): Promise<any> +import { UserAgentInfo } from "./user_agent_info"; + +export interface UserAgentParser { + parseUserAgentInfo(): UserAgentInfo, } diff --git a/lib/optimizely/index.spec.ts b/lib/optimizely/index.spec.ts new file mode 100644 index 000000000..4548ffbb7 --- /dev/null +++ b/lib/optimizely/index.spec.ts @@ -0,0 +1,908 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import Optimizely from '.'; +import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; +import * as jsonSchemaValidator from '../utils/json_schema_validator'; +import testData from '../tests/test_data'; +import { getForwardingEventProcessor } from '../event_processor/event_processor_factory'; +import { createProjectConfig } from '../project_config/project_config'; +import { getMockLogger } from '../tests/mock/mock_logger'; +import { createOdpManager } from '../odp/odp_manager_factory.node'; +import { extractOdpManager } from '../odp/odp_manager_factory'; +import { Value } from '../utils/promise/operation_value'; +import { getDecisionTestDatafile } from '../tests/decision_test_datafile'; +import { DECISION_SOURCES } from '../utils/enums'; +import OptimizelyUserContext from '../optimizely_user_context'; +import { newErrorDecision } from '../optimizely_decision'; +import { ImpressionEvent } from '../event_processor/event_builder/user_event'; +import { OptimizelyDecideOption } from '../shared_types'; +import { NOTIFICATION_TYPES, DECISION_NOTIFICATION_TYPES } from '../notification_center/type'; + + +const holdoutData = [ + { + id: 'holdout_test_id', + key: 'holdout_test_key', + status: 'Running', + includedFlags: [], + excludedFlags: [], + audienceIds: [], + audienceConditions: [], + variations: [ + { + id: 'holdout_variation_id', + key: 'holdout_variation_key', + variables: [], + featureEnabled: false, + }, + ], + trafficAllocation: [ + { + entityId: 'holdout_variation_id', + endOfRange: 10000, + }, + ], + }, +]; + +describe('Optimizely', () => { + const eventDispatcher = { + dispatchEvent: () => Promise.resolve({ statusCode: 200 }), + }; + + const eventProcessor = getForwardingEventProcessor(eventDispatcher); + const odpManager = extractOdpManager(createOdpManager({})); + const logger = getMockLogger(); + + beforeEach(() => { + vi.clearAllMocks(); + }); + + it('should pass disposable options to the respective services', () => { + const projectConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + + vi.spyOn(projectConfigManager, 'makeDisposable'); + vi.spyOn(eventProcessor, 'makeDisposable'); + vi.spyOn(odpManager!, 'makeDisposable'); + + new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager, + jsonSchemaValidator, + logger, + eventProcessor, + odpManager, + disposable: true, + cmabService: {} as any + }); + + expect(projectConfigManager.makeDisposable).toHaveBeenCalled(); + expect(eventProcessor.makeDisposable).toHaveBeenCalled(); + expect(odpManager!.makeDisposable).toHaveBeenCalled(); + }); + + it('should set child logger to respective services', () => { + const projectConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + + const eventProcessor = getForwardingEventProcessor(eventDispatcher); + const odpManager = extractOdpManager(createOdpManager({})); + + vi.spyOn(projectConfigManager, 'setLogger'); + vi.spyOn(eventProcessor, 'setLogger'); + vi.spyOn(odpManager!, 'setLogger'); + + const logger = getMockLogger(); + const configChildLogger = getMockLogger(); + const eventProcessorChildLogger = getMockLogger(); + const odpManagerChildLogger = getMockLogger(); + vi.spyOn(logger, 'child').mockReturnValueOnce(configChildLogger) + .mockReturnValueOnce(eventProcessorChildLogger) + .mockReturnValueOnce(odpManagerChildLogger); + + new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager, + jsonSchemaValidator, + logger, + eventProcessor, + odpManager, + disposable: true, + cmabService: {} as any + }); + + expect(projectConfigManager.setLogger).toHaveBeenCalledWith(configChildLogger); + expect(eventProcessor.setLogger).toHaveBeenCalledWith(eventProcessorChildLogger); + expect(odpManager!.setLogger).toHaveBeenCalledWith(odpManagerChildLogger); + }); + + describe('decideAsync', () => { + it('should return an error decision with correct reasons if decisionService returns error', async () => { + const projectConfig = createProjectConfig(getDecisionTestDatafile()); + + const projectConfigManager = getMockProjectConfigManager({ + initConfig: projectConfig, + }); + + const optimizely = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager, + jsonSchemaValidator, + logger, + eventProcessor, + odpManager, + disposable: true, + cmabService: {} as any + }); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const decisionService = optimizely.decisionService; + vi.spyOn(decisionService, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: true, + result: { + variation: null, + experiment: projectConfig.experimentKeyMap['exp_3'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }, + reasons:[ + ['test reason %s', '1'], + ['test reason %s', '2'], + ] + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + country: 'BD', + age: 80, // should satisfy audience condition for exp_3 which is cmab and not others + }, + }); + + const decision = await optimizely.decideAsync(user, 'flag_1', []); + + expect(decision).toEqual(newErrorDecision('flag_1', user, ['test reason 1', 'test reason 2'])); + }); + + it('should include cmab uuid in dispatched event if decisionService returns a cmab uuid', async () => { + const projectConfig = createProjectConfig(getDecisionTestDatafile()); + + const projectConfigManager = getMockProjectConfigManager({ + initConfig: projectConfig, + }); + + const eventProcessor = getForwardingEventProcessor(eventDispatcher); + const processSpy = vi.spyOn(eventProcessor, 'process'); + + const optimizely = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager, + eventProcessor, + jsonSchemaValidator, + logger, + odpManager, + disposable: true, + cmabService: {} as any + }); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const decisionService = optimizely.decisionService; + vi.spyOn(decisionService, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: false, + result: { + cmabUuid: 'uuid-cmab', + variation: projectConfig.variationIdMap['5003'], + experiment: projectConfig.experimentKeyMap['exp_3'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }, + reasons: [], + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely: {} as any, + userId: 'tester', + attributes: { + country: 'BD', + age: 80, // should satisfy audience condition for exp_3 which is cmab and not others + }, + }); + + const decision = await optimizely.decideAsync(user, 'flag_1', []); + + expect(decision.ruleKey).toBe('exp_3'); + expect(decision.flagKey).toBe('flag_1'); + expect(decision.variationKey).toBe('variation_3'); + expect(decision.enabled).toBe(true); + + expect(eventProcessor.process).toHaveBeenCalledOnce(); + const event = processSpy.mock.calls[0][0] as ImpressionEvent; + expect(event.cmabUuid).toBe('uuid-cmab'); + }); + + + }); + + describe('holdout tests', () => { + let projectConfig: any; + let optimizely: any; + let decisionService: any; + let flagNotificationSpy: any; + let activateNotificationSpy: any; + let eventProcessor: any; + + beforeEach(() => { + const datafile = getDecisionTestDatafile(); + datafile.holdouts = JSON.parse(JSON.stringify(holdoutData)); // Deep copy to avoid mutations + projectConfig = createProjectConfig(datafile); + + const projectConfigManager = getMockProjectConfigManager({ + initConfig: projectConfig, + }); + + const mockEventDispatcher = { + dispatchEvent: vi.fn(() => Promise.resolve({ statusCode: 200 })), + }; + eventProcessor = getForwardingEventProcessor(mockEventDispatcher); + + optimizely = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager, + eventProcessor, + jsonSchemaValidator, + logger, + odpManager, + disposable: true, + cmabService: {} as any + }); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + decisionService = optimizely.decisionService; + + // Setup notification spy + flagNotificationSpy = vi.fn(); + optimizely.notificationCenter.addNotificationListener( + NOTIFICATION_TYPES.DECISION, + flagNotificationSpy + ); + + activateNotificationSpy = vi.fn(); + optimizely.notificationCenter.addNotificationListener( + NOTIFICATION_TYPES.ACTIVATE, + activateNotificationSpy + ); + }); + + it('should dispatch impression event for holdout decision', async () => { + const processSpy = vi.spyOn(eventProcessor, 'process'); + + vi.spyOn(decisionService, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: false, + result: { + variation: projectConfig.holdouts[0].variations[0], + experiment: projectConfig.holdouts[0], + decisionSource: DECISION_SOURCES.HOLDOUT, + }, + reasons: [], + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely, + userId: 'test_user', + attributes: {}, + }); + + const decision = await optimizely.decideAsync(user, 'flag_1', []); + + expect(decision.ruleKey).toBe('holdout_test_key'); + expect(decision.flagKey).toBe('flag_1'); + expect(decision.variationKey).toBe('holdout_variation_key'); + expect(decision.enabled).toBe(false); + + expect(eventProcessor.process).toHaveBeenCalledOnce(); + + const event = processSpy.mock.calls[0][0] as ImpressionEvent; + + expect(event.type).toBe('impression'); + expect(event.ruleKey).toBe('holdout_test_key'); + expect(event.ruleType).toBe('holdout'); + expect(event.enabled).toBe(false); + }); + + it('should not dispatch impression event for holdout when DISABLE_DECISION_EVENT is used', async () => { + const processSpy = vi.spyOn(eventProcessor, 'process'); + + vi.spyOn(decisionService, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: false, + result: { + variation: projectConfig.holdouts[0].variations[0], + experiment: projectConfig.holdouts[0], + decisionSource: DECISION_SOURCES.HOLDOUT, + }, + reasons: [], + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely, + userId: 'test_user', + attributes: {}, + }); + + const decision = await optimizely.decideAsync(user, 'flag_1', [OptimizelyDecideOption.DISABLE_DECISION_EVENT]); + + expect(decision.ruleKey).toBe('holdout_test_key'); + expect(decision.enabled).toBe(false); + expect(processSpy).not.toHaveBeenCalled(); + }); + + it('should dispatch impression event for holdout decision for isFeatureEnabled', async () => { + const processSpy = vi.spyOn(eventProcessor, 'process'); + + vi.spyOn(decisionService, 'getVariationForFeature').mockReturnValue({ + error: false, + result: { + variation: projectConfig.holdouts[0].variations[0], + experiment: projectConfig.holdouts[0], + decisionSource: DECISION_SOURCES.HOLDOUT, + }, + reasons: [], + }); + + const result = optimizely.isFeatureEnabled('flag_1', 'test_user', {}); + + expect(result).toBe(false); + + expect(eventProcessor.process).toHaveBeenCalledOnce(); + const event = processSpy.mock.calls[0][0] as ImpressionEvent; + + expect(event.type).toBe('impression'); + expect(event.ruleKey).toBe('holdout_test_key'); + expect(event.ruleType).toBe('holdout'); + expect(event.enabled).toBe(false); + }); + + it('should send correct decision notification for holdout decision', async () => { + vi.spyOn(decisionService, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: false, + result: { + variation: projectConfig.holdouts[0].variations[0], + experiment: projectConfig.holdouts[0], + decisionSource: DECISION_SOURCES.HOLDOUT, + }, + reasons: [], + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely, + userId: 'test_user', + attributes: { country: 'US' }, + }); + + const decision = await optimizely.decideAsync(user, 'flag_1', []); + + expect(decision.flagKey).toBe('flag_1'); + expect(decision.enabled).toBe(false); + expect(decision.variationKey).toBe('holdout_variation_key'); + expect(decision.ruleKey).toBe('holdout_test_key'); + + // Verify decision notification was sent + expect(flagNotificationSpy).toHaveBeenCalledWith({ + type: DECISION_NOTIFICATION_TYPES.FLAG, + userId: 'test_user', + attributes: { country: 'US' }, + decisionInfo: expect.objectContaining({ + flagKey: 'flag_1', + enabled: false, + variationKey: 'holdout_variation_key', + ruleKey: 'holdout_test_key', + variables: expect.any(Object), + reasons: expect.any(Array), + decisionEventDispatched: true, + }), + }); + + expect(activateNotificationSpy).toHaveBeenCalledWith(expect.objectContaining({ + experiment: null, + holdout: projectConfig.holdouts[0], + userId: 'test_user', + attributes: { country: 'US' }, + variation: projectConfig.holdouts[0].variations[0] + })); + }); + + it('should handle holdout with included flags', async () => { + // Modify holdout to include specific flag + const modifiedHoldout = { ...projectConfig.holdouts[0] }; + modifiedHoldout.includedFlags = ['1001']; // flag_1 ID from test datafile + projectConfig.holdouts = [modifiedHoldout]; + + vi.spyOn(decisionService, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: false, + result: { + variation: modifiedHoldout.variations[0], + experiment: modifiedHoldout, + decisionSource: DECISION_SOURCES.HOLDOUT, + }, + reasons: [], + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely, + userId: 'test_user', + attributes: { country: 'US' }, + }); + + const decision = await optimizely.decideAsync(user, 'flag_1', []); + + expect(decision.enabled).toBe(false); + expect(decision.ruleKey).toBe('holdout_test_key'); + expect(decision.variationKey).toBe('holdout_variation_key'); + + // Verify notification shows holdout details + expect(flagNotificationSpy).toHaveBeenCalledWith({ + type: DECISION_NOTIFICATION_TYPES.FLAG, + userId: 'test_user', + attributes: { country: 'US' }, + decisionInfo: expect.objectContaining({ + flagKey: 'flag_1', + enabled: false, + ruleKey: 'holdout_test_key', + }), + }); + + expect(activateNotificationSpy).toHaveBeenCalledWith(expect.objectContaining({ + experiment: null, + holdout: modifiedHoldout, + userId: 'test_user', + attributes: { country: 'US' }, + variation: modifiedHoldout.variations[0] + })); + }); + + it('should handle holdout with excluded flags', async () => { + // Modify holdout to exclude specific flag + const modifiedHoldout = { ...projectConfig.holdouts[0] }; + modifiedHoldout.excludedFlags = ['1001']; // flag_1 ID from test datafile + projectConfig.holdouts = [modifiedHoldout]; + + // Mock normal feature test behavior for excluded flag + vi.spyOn(decisionService, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: false, + result: { + variation: projectConfig.variationIdMap['5003'], + experiment: projectConfig.experimentKeyMap['exp_3'], + decisionSource: DECISION_SOURCES.FEATURE_TEST, + }, + reasons: [], + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely, + userId: 'test_user', + attributes: { country: 'BD', age: 80 }, + }); + + const decision = await optimizely.decideAsync(user, 'flag_1', []); + + expect(decision.enabled).toBe(true); + expect(decision.ruleKey).toBe('exp_3'); + expect(decision.variationKey).toBe('variation_3'); + + // Verify notification shows normal experiment details (not holdout) + expect(flagNotificationSpy).toHaveBeenCalledWith({ + type: DECISION_NOTIFICATION_TYPES.FLAG, + userId: 'test_user', + attributes: { country: 'BD', age: 80 }, + decisionInfo: expect.objectContaining({ + flagKey: 'flag_1', + enabled: true, + ruleKey: 'exp_3', + }), + }); + + expect(activateNotificationSpy).toHaveBeenCalledWith(expect.objectContaining({ + experiment: projectConfig.experimentKeyMap['exp_3'], + holdout: null, + userId: 'test_user', + attributes: { country: 'BD', age: 80 }, + variation: projectConfig.variationIdMap['5003'] + })); + }); + + it('should handle multiple holdouts with correct priority', async () => { + // Setup multiple holdouts + const holdout1 = { ...projectConfig.holdouts[0] }; + holdout1.excludedFlags = ['1001']; // exclude flag_1 + + const holdout2 = { + id: 'holdout_test_id_2', + key: 'holdout_test_key_2', + status: 'Running', + includedFlags: ['1001'], // include flag_1 + excludedFlags: [], + audienceIds: [], + audienceConditions: [], + variations: [ + { + id: 'holdout_variation_id_2', + key: 'holdout_variation_key_2', + variables: [], + featureEnabled: false, + }, + ], + trafficAllocation: [ + { + entityId: 'holdout_variation_id_2', + endOfRange: 10000, + }, + ], + }; + + projectConfig.holdouts = [holdout1, holdout2]; + + // Mock that holdout2 takes priority due to includedFlags + vi.spyOn(decisionService, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: false, + result: { + variation: holdout2.variations[0], + experiment: holdout2, + decisionSource: DECISION_SOURCES.HOLDOUT, + }, + reasons: [], + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely, + userId: 'test_user', + attributes: { country: 'US' }, + }); + + const decision = await optimizely.decideAsync(user, 'flag_1', []); + + expect(decision.enabled).toBe(false); + expect(decision.ruleKey).toBe('holdout_test_key_2'); + expect(decision.variationKey).toBe('holdout_variation_key_2'); + + // Verify notification shows details of selected holdout + expect(flagNotificationSpy).toHaveBeenCalledWith({ + type: DECISION_NOTIFICATION_TYPES.FLAG, + userId: 'test_user', + attributes: { country: 'US' }, + decisionInfo: expect.objectContaining({ + flagKey: 'flag_1', + enabled: false, + ruleKey: 'holdout_test_key_2', + }), + }); + + expect(activateNotificationSpy).toHaveBeenCalledWith(expect.objectContaining({ + experiment: null, + holdout: holdout2, + userId: 'test_user', + attributes: { country: 'US' }, + variation: holdout2.variations[0] + })); + }); + + it('should respect sendFlagDecisions setting for holdout events - false', async () => { + // Set sendFlagDecisions to false + projectConfig.sendFlagDecisions = false; + + const mockEventDispatcher = { + dispatchEvent: vi.fn(() => Promise.resolve({ statusCode: 200 })), + }; + const eventProcessor = getForwardingEventProcessor(mockEventDispatcher); + const processSpy = vi.spyOn(eventProcessor, 'process'); + + const projectConfigManager = getMockProjectConfigManager({ + initConfig: projectConfig, + }); + + const optimizelyWithConfig = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager, + eventProcessor, + jsonSchemaValidator, + logger, + odpManager, + disposable: true, + cmabService: {} as any + }); + + // Add notification listener + const notificationSpyLocal = vi.fn(); + optimizelyWithConfig.notificationCenter.addNotificationListener( + NOTIFICATION_TYPES.DECISION, + notificationSpyLocal + ); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const decisionServiceLocal = optimizelyWithConfig.decisionService; + vi.spyOn(decisionServiceLocal, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: false, + result: { + variation: projectConfig.holdouts[0].variations[0], + experiment: projectConfig.holdouts[0], + decisionSource: DECISION_SOURCES.HOLDOUT, + }, + reasons: [], + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely: optimizelyWithConfig, + userId: 'test_user', + attributes: { country: 'US' }, + }); + + await optimizelyWithConfig.decideAsync(user, 'flag_1', []); + + // Impression event should still be dispatched for holdouts even when sendFlagDecisions is false + expect(processSpy).toHaveBeenCalledOnce(); + + // Verify notification shows decisionEventDispatched: true + expect(notificationSpyLocal).toHaveBeenCalledWith({ + type: DECISION_NOTIFICATION_TYPES.FLAG, + userId: 'test_user', + attributes: { country: 'US' }, + decisionInfo: expect.objectContaining({ + decisionEventDispatched: true, + }), + }); + }); + + it('should respect sendFlagDecisions setting for holdout events - true', async () => { + // Set sendFlagDecisions to true + projectConfig.sendFlagDecisions = true; + + const mockEventDispatcher = { + dispatchEvent: vi.fn(() => Promise.resolve({ statusCode: 200 })), + }; + const eventProcessor = getForwardingEventProcessor(mockEventDispatcher); + const processSpy = vi.spyOn(eventProcessor, 'process'); + + const projectConfigManager = getMockProjectConfigManager({ + initConfig: projectConfig, + }); + + const optimizelyWithConfig = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager, + eventProcessor, + jsonSchemaValidator, + logger, + odpManager, + disposable: true, + cmabService: {} as any + }); + + // Add notification listener + const notificationSpyLocal = vi.fn(); + optimizelyWithConfig.notificationCenter.addNotificationListener( + NOTIFICATION_TYPES.DECISION, + notificationSpyLocal + ); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const decisionServiceLocal = optimizelyWithConfig.decisionService; + vi.spyOn(decisionServiceLocal, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: false, + result: { + variation: projectConfig.holdouts[0].variations[0], + experiment: projectConfig.holdouts[0], + decisionSource: DECISION_SOURCES.HOLDOUT, + }, + reasons: [], + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely: optimizelyWithConfig, + userId: 'test_user', + attributes: { country: 'US' }, + }); + + await optimizelyWithConfig.decideAsync(user, 'flag_1', []); + + // Impression event should be dispatched for holdouts + expect(processSpy).toHaveBeenCalledOnce(); + + // Verify notification shows decisionEventDispatched: true + expect(notificationSpyLocal).toHaveBeenCalledWith({ + type: DECISION_NOTIFICATION_TYPES.FLAG, + userId: 'test_user', + attributes: { country: 'US' }, + decisionInfo: expect.objectContaining({ + decisionEventDispatched: true, + }), + }); + }); + + it('should return correct variable values for holdout decision', async () => { + vi.spyOn(decisionService, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: false, + result: { + variation: projectConfig.holdouts[0].variations[0], + experiment: projectConfig.holdouts[0], + decisionSource: DECISION_SOURCES.HOLDOUT, + }, + reasons: [], + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely, + userId: 'test_user', + attributes: { country: 'US' }, + }); + + const decision = await optimizely.decideAsync(user, 'flag_1', []); + + expect(decision.enabled).toBe(false); + expect(decision.variables).toBeDefined(); + expect(typeof decision.variables).toBe('object'); + + // Verify notification includes variable information + expect(flagNotificationSpy).toHaveBeenCalledWith({ + type: DECISION_NOTIFICATION_TYPES.FLAG, + userId: 'test_user', + attributes: { country: 'US' }, + decisionInfo: expect.objectContaining({ + variables: expect.any(Object), + flagKey: 'flag_1', + enabled: false, + }), + }); + + expect(activateNotificationSpy).toHaveBeenCalledWith(expect.objectContaining({ + experiment: null, + holdout: projectConfig.holdouts[0], + userId: 'test_user', + attributes: { country: 'US' }, + variation: projectConfig.holdouts[0].variations[0] + })); + }); + + it('should handle disable decision event option for holdout', async () => { + const mockEventDispatcher = { + dispatchEvent: vi.fn(() => Promise.resolve({ statusCode: 200 })), + }; + const eventProcessor = getForwardingEventProcessor(mockEventDispatcher); + const processSpy = vi.spyOn(eventProcessor, 'process'); + + const projectConfigManager = getMockProjectConfigManager({ + initConfig: projectConfig, + }); + + const optimizelyWithEventProcessor = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager, + eventProcessor, + jsonSchemaValidator, + logger, + odpManager, + disposable: true, + cmabService: {} as any + }); + + // Add notification listener + const notificationSpyLocal = vi.fn(); + optimizelyWithEventProcessor.notificationCenter.addNotificationListener( + NOTIFICATION_TYPES.DECISION, + notificationSpyLocal + ); + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const decisionServiceLocal = optimizelyWithEventProcessor.decisionService; + vi.spyOn(decisionServiceLocal, 'resolveVariationsForFeatureList').mockImplementation(() => { + return Value.of('async', [{ + error: false, + result: { + variation: projectConfig.holdouts[0].variations[0], + experiment: projectConfig.holdouts[0], + decisionSource: DECISION_SOURCES.HOLDOUT, + }, + reasons: [], + }]); + }); + + const user = new OptimizelyUserContext({ + optimizely: optimizelyWithEventProcessor, + userId: 'test_user', + attributes: { country: 'US' }, + }); + + const decision = await optimizelyWithEventProcessor.decideAsync(user, 'flag_1', [OptimizelyDecideOption.DISABLE_DECISION_EVENT]); + + expect(decision.enabled).toBe(false); + expect(decision.ruleKey).toBe('holdout_test_key'); + + // No impression event should be dispatched + expect(processSpy).not.toHaveBeenCalled(); + + // Verify notification shows decisionEventDispatched: false + expect(notificationSpyLocal).toHaveBeenCalledWith({ + type: DECISION_NOTIFICATION_TYPES.FLAG, + userId: 'test_user', + attributes: { country: 'US' }, + decisionInfo: expect.objectContaining({ + decisionEventDispatched: false, + }), + }); + }); + }); + + it('should flush eventProcessor and odpManager on flushImmediately()', async () => { + const projectConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + + const eventProcessor = getForwardingEventProcessor(eventDispatcher); + const odpManager = extractOdpManager(createOdpManager({})); + + const optimizely = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager, + jsonSchemaValidator, + logger, + eventProcessor, + odpManager, + disposable: true, + cmabService: {} as any + }); + + odpManager?.updateConfig({ integrated: false }); + await optimizely.onReady(); + + const eventProcessorFlushSpy = vi.spyOn(eventProcessor, 'flushImmediately').mockResolvedValue(Promise.resolve()); + const odpManagerFlushSpy = vi.spyOn(odpManager!, 'flushImmediately').mockResolvedValue(Promise.resolve()); + + await optimizely.flushImmediately(); + + expect(eventProcessorFlushSpy).toHaveBeenCalled(); + expect(odpManagerFlushSpy).toHaveBeenCalled(); + + expect(optimizely.isRunning()).toBe(true); + }); +}); diff --git a/packages/optimizely-sdk/lib/optimizely/index.tests.js b/lib/optimizely/index.tests.js similarity index 74% rename from packages/optimizely-sdk/lib/optimizely/index.tests.js rename to lib/optimizely/index.tests.js index e29a4af43..d3f350bba 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.tests.js +++ b/lib/optimizely/index.tests.js @@ -1,161 +1,191 @@ -/**************************************************************************** - * Copyright 2016-2022, Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ +/** + * Copyright 2016-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import { assert, expect } from 'chai'; import sinon from 'sinon'; import { sprintf } from '../utils/fns'; -import { NOTIFICATION_TYPES } from '../utils/enums'; -import eventProcessor from '../plugins/event_processor'; -import * as logging from '../modules/logging'; - -import Optimizely from './'; +import { NOTIFICATION_TYPES } from '../notification_center/type'; +import Optimizely, { INVALID_ATTRIBUTES, INVALID_IDENTIFIER } from './'; import OptimizelyUserContext from '../optimizely_user_context'; import { OptimizelyDecideOption } from '../shared_types'; import AudienceEvaluator from '../core/audience_evaluator'; -import bluebird from 'bluebird'; import * as bucketer from '../core/bucketer'; -import * as projectConfigManager from '../core/project_config/project_config_manager'; import * as enums from '../utils/enums'; -import eventDispatcher from '../plugins/event_dispatcher/index.node'; -import errorHandler from '../plugins/error_handler'; import fns from '../utils/fns'; -import * as logger from '../plugins/logger'; import * as decisionService from '../core/decision_service'; import * as jsonSchemaValidator from '../utils/json_schema_validator'; -import * as projectConfig from '../core/project_config'; +import * as projectConfig from '../project_config/project_config'; import testData from '../tests/test_data'; -import { createForwardingEventProcessor } from '../plugins/event_processor/forwarding_event_processor'; -import { createEventProcessor } from '../plugins/event_processor'; -import { createNotificationCenter } from '../core/notification_center'; -import { createHttpPollingDatafileManager } from '../plugins/datafile_manager/http_polling_datafile_manager'; +import { getForwardingEventProcessor } from '../event_processor/event_processor_factory'; +import { createNotificationCenter } from '../notification_center'; +import { createProjectConfig } from '../project_config/project_config'; +import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; +import { DECISION_NOTIFICATION_TYPES } from '../notification_center/type'; +import { + FEATURE_NOT_ENABLED_FOR_USER, + INVALID_CLIENT_ENGINE, + INVALID_DEFAULT_DECIDE_OPTIONS, + NOT_ACTIVATING_USER, + VALID_USER_PROFILE_SERVICE, +} from 'log_message'; +import { + NOT_TRACKING_USER, + EVENT_KEY_NOT_FOUND, + INVALID_EXPERIMENT_KEY, + SERVICE_STOPPED_BEFORE_RUNNING +} from 'error_message'; + +import { ONREADY_TIMEOUT, INSTANCE_CLOSED } from './'; +import { + AUDIENCE_EVALUATION_RESULT_COMBINED, + USER_NOT_IN_EXPERIMENT, + FEATURE_HAS_NO_EXPERIMENTS, + USER_HAS_NO_VARIATION, + USER_HAS_VARIATION, + USER_NOT_BUCKETED_INTO_TARGETING_RULE, + USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, + USER_IN_ROLLOUT, + USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, + FORCED_BUCKETING_FAILED, + USER_HAS_FORCED_VARIATION, + USER_FORCED_IN_VARIATION, + RETURNING_STORED_VARIATION, + EXPERIMENT_NOT_RUNNING, +} from '../core/decision_service'; + +import { USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP } from '../core/bucketer'; +import { resolvablePromise } from '../utils/promise/resolvablePromise'; +import { holdout } from '../feature_toggle'; -var ERROR_MESSAGES = enums.ERROR_MESSAGES; var LOG_LEVEL = enums.LOG_LEVEL; -var LOG_MESSAGES = enums.LOG_MESSAGES; var DECISION_SOURCES = enums.DECISION_SOURCES; var DECISION_MESSAGES = enums.DECISION_MESSAGES; -var DECISION_NOTIFICATION_TYPES = enums.DECISION_NOTIFICATION_TYPES; var FEATURE_VARIABLE_TYPES = enums.FEATURE_VARIABLE_TYPES; var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); +const getMockEventDispatcher = () => { + const dispatcher = { + dispatchEvent: sinon.spy(() => Promise.resolve({ statusCode: 200 })), + } + return dispatcher; +} + +const getMockEventProcessor = (notificationCenter) => { + return getForwardingEventProcessor(getMockEventDispatcher(), notificationCenter); +} + +const getMockErrorNotifier = () => { + return { + notify: sinon.spy(), + } +}; + +var createLogger = () => ({ + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + child: () => createLogger(), +}) + +const getOptlyInstance = ({ datafileObj, defaultDecideOptions }) => { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(datafileObj), + }); + const eventDispatcher = getMockEventDispatcher(); + const eventProcessor = getForwardingEventProcessor(eventDispatcher); + + const errorNotifier = getMockErrorNotifier(); + + const notificationCenter = createNotificationCenter({ logger: createdLogger, errorNotifier }); + var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); + + const optlyInstance = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager: mockConfigManager, + errorNotifier, + eventProcessor, + jsonSchemaValidator: jsonSchemaValidator, + logger: createdLogger, + isValidInstance: true, + eventBatchSize: 1, + defaultDecideOptions: defaultDecideOptions || [], + notificationCenter, + }); + + sinon.stub(notificationCenter, 'sendNotifications'); + + return { optlyInstance, eventProcessor, eventDispatcher, notificationCenter, errorNotifier, createdLogger } +} + describe('lib/optimizely', function() { - var ProjectConfigManagerStub; - var globalStubErrorHandler; - var stubLogHandler; var clock; beforeEach(function() { - logging.setLogLevel('notset'); - stubLogHandler = { - log: sinon.stub(), - }; - logging.setLogHandler(stubLogHandler); - globalStubErrorHandler = { - handleError: sinon.stub(), - }; - logging.setErrorHandler(globalStubErrorHandler); - ProjectConfigManagerStub = sinon.stub(projectConfigManager, 'createProjectConfigManager').callsFake(function(config) { - var currentConfig = config.datafile ? projectConfig.createProjectConfig(config.datafile) : null; - return { - stop: sinon.stub(), - getConfig: sinon.stub().returns(currentConfig), - onUpdate: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns({ then: function() {} }), - }; - }); - sinon.stub(eventDispatcher, 'dispatchEvent'); + // sinon.stub(eventDispatcher, 'dispatchEvent'); clock = sinon.useFakeTimers(new Date()); }); afterEach(function() { - ProjectConfigManagerStub.restore(); - logging.resetErrorHandler(); - logging.resetLogger(); - eventDispatcher.dispatchEvent.restore(); + // eventDispatcher.dispatchEvent.restore(); clock.restore(); }); describe('constructor', function() { - var stubErrorHandler = { handleError: function() {} }; var stubEventDispatcher = { dispatchEvent: function() { - return bluebird.resolve(null); + return Promise.resolve(null); }, }; - var createdLogger = logger.createLogger({ logLevel: LOG_LEVEL.INFO }); - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: stubErrorHandler }); - var eventProcessor = createForwardingEventProcessor(stubEventDispatcher); + var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); + var notificationCenter = createNotificationCenter({ logger: createdLogger }); + var eventProcessor = getForwardingEventProcessor(stubEventDispatcher); beforeEach(function() { - sinon.stub(stubErrorHandler, 'handleError'); - sinon.stub(createdLogger, 'log'); + sinon.stub(createdLogger, 'debug'); + sinon.stub(createdLogger, 'info'); + sinon.stub(createdLogger, 'warn'); + sinon.stub(createdLogger, 'error'); }); afterEach(function() { - stubErrorHandler.handleError.restore(); - createdLogger.log.restore(); + createdLogger.debug.restore(); + createdLogger.info.restore(); + createdLogger.warn.restore(); + createdLogger.error.restore(); }); describe('constructor', function() { - it('should construct an instance of the Optimizely class', function() { - var optlyInstance = new Optimizely({ - clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), - errorHandler: stubErrorHandler, - eventDispatcher: stubEventDispatcher, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - notificationCenter, - eventProcessor, - }); - assert.instanceOf(optlyInstance, Optimizely); - }); - - it('should construct an instance of the Optimizely class when datafile is JSON string', function() { - var optlyInstance = new Optimizely({ - clientEngine: 'node-sdk', - datafile: JSON.stringify(testData.getTestProjectConfig()), - errorHandler: stubErrorHandler, - eventDispatcher: stubEventDispatcher, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - notificationCenter, - eventProcessor, - }); - assert.instanceOf(optlyInstance, Optimizely); - }); - it('should log if the client engine passed in is invalid', function() { new Optimizely({ - datafile: testData.getTestProjectConfig(), - errorHandler: stubErrorHandler, + projectConfigManager: getMockProjectConfigManager(), eventDispatcher: stubEventDispatcher, logger: createdLogger, notificationCenter, eventProcessor, }); - sinon.assert.called(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_CLIENT_ENGINE, 'OPTIMIZELY', 'undefined')); + sinon.assert.called(createdLogger.info); + + assert.deepEqual(createdLogger.info.args[0], [INVALID_CLIENT_ENGINE, undefined]); }); it('should log if the defaultDecideOptions passed in are invalid', function() { new Optimizely({ + projectConfigManager: getMockProjectConfigManager(), clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), - errorHandler: stubErrorHandler, + eventDispatcher: stubEventDispatcher, logger: createdLogger, defaultDecideOptions: 'invalid_options', @@ -163,16 +193,14 @@ describe('lib/optimizely', function() { eventProcessor, }); - sinon.assert.called(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_DEFAULT_DECIDE_OPTIONS, 'OPTIMIZELY')); + sinon.assert.called(createdLogger.debug); + assert.deepEqual(createdLogger.debug.args[0], [INVALID_DEFAULT_DECIDE_OPTIONS]); }); it('should allow passing `react-sdk` as the clientEngine', function() { var instance = new Optimizely({ + projectConfigManager: getMockProjectConfigManager(), clientEngine: 'react-sdk', - datafile: testData.getTestProjectConfig(), - errorHandler: stubErrorHandler, eventDispatcher: stubEventDispatcher, logger: createdLogger, notificationCenter, @@ -197,24 +225,31 @@ describe('lib/optimizely', function() { save: function() {}, }; + const cmabService = {}; + new Optimizely({ clientEngine: 'node-sdk', logger: createdLogger, - datafile: testData.getTestProjectConfig(), + projectConfigManager: getMockProjectConfigManager(), jsonSchemaValidator: jsonSchemaValidator, userProfileService: userProfileServiceInstance, notificationCenter, + cmabService, eventProcessor, }); sinon.assert.calledWith(decisionService.createDecisionService, { userProfileService: userProfileServiceInstance, + userProfileServiceAsync: undefined, logger: createdLogger, + cmabService, UNSTABLE_conditionEvaluators: undefined, }); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, 'OPTIMIZELY: Valid user profile service provided.'); + sinon.assert.calledWith( + createdLogger.info, + VALID_USER_PROFILE_SERVICE, + ); }); it('should pass in a null user profile to the decision service if the provided user profile is invalid', function() { @@ -222,102 +257,34 @@ describe('lib/optimizely', function() { save: function() {}, }; + const cmabService = {}; + new Optimizely({ clientEngine: 'node-sdk', logger: createdLogger, - datafile: testData.getTestProjectConfig(), + projectConfigManager: getMockProjectConfigManager(), jsonSchemaValidator: jsonSchemaValidator, userProfileService: invalidUserProfile, notificationCenter, eventProcessor, + cmabService, }); sinon.assert.calledWith(decisionService.createDecisionService, { - userProfileService: null, + userProfileService: undefined, + userProfileServiceAsync: undefined, logger: createdLogger, UNSTABLE_conditionEvaluators: undefined, + cmabService, }); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - logMessage, - "USER_PROFILE_SERVICE_VALIDATOR: Provided user profile service instance is in an invalid format: Missing function 'lookup'." - ); - }); - }); - - describe('when an sdkKey is provided', function() { - it('should not log an error when sdkKey is provided and datafile is not provided', function() { - new Optimizely({ - clientEngine: 'node-sdk', - errorHandler: stubErrorHandler, - eventDispatcher: eventDispatcher, - isValidInstance: true, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', createdLogger), - notificationCenter, - eventProcessor, - }); - sinon.assert.notCalled(stubErrorHandler.handleError); - }); - - it('passes datafile, datafileOptions, sdkKey, and other options to the project config manager', function() { - var config = testData.getTestProjectConfig(); - let datafileOptions = { - autoUpdate: true, - updateInterval: 2 * 60 * 1000, - } - let datafileManager = createHttpPollingDatafileManager('12345', createdLogger, undefined, datafileOptions); - new Optimizely({ - clientEngine: 'node-sdk', - datafile: config, - datafileOptions: datafileOptions, - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, - isValidInstance: true, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - sdkKey: '12345', - datafileManager: datafileManager, - notificationCenter, - eventProcessor, - }); - sinon.assert.calledOnce(projectConfigManager.createProjectConfigManager); - sinon.assert.calledWithExactly(projectConfigManager.createProjectConfigManager, { - datafile: config, - jsonSchemaValidator: jsonSchemaValidator, - sdkKey: '12345', - datafileManager: datafileManager - }); - }); - }); - - it('should support constructing two instances using the same datafile object', function() { - var datafile = testData.getTypedAudiencesConfig(); - var optlyInstance = new Optimizely({ - clientEngine: 'node-sdk', - datafile: datafile, - errorHandler: stubErrorHandler, - eventDispatcher: stubEventDispatcher, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - notificationCenter, - eventProcessor - }); - assert.instanceOf(optlyInstance, Optimizely); - var optlyInstance2 = new Optimizely({ - clientEngine: 'node-sdk', - datafile: datafile, - errorHandler: stubErrorHandler, - eventDispatcher: stubEventDispatcher, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - notificationCenter, - eventProcessor, + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual( + // logMessage, + // "USER_PROFILE_SERVICE_VALIDATOR: Provided user profile service instance is in an invalid format: Missing function 'lookup'." + // ); + sinon.assert.called(createdLogger.warn); }); - assert.instanceOf(optlyInstance2, Optimizely); }); }); }); @@ -326,18 +293,22 @@ describe('lib/optimizely', function() { var optlyInstance; var bucketStub; var fakeDecisionResponse; - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler }); - var eventProcessor = createForwardingEventProcessor(eventDispatcher, notificationCenter); - var createdLogger = logger.createLogger({ + var eventDispatcher = getMockEventDispatcher(); + var notificationCenter = createNotificationCenter({ logger: createdLogger }); + var eventProcessor = getForwardingEventProcessor(eventDispatcher, notificationCenter); + var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO, logToConsole: false, }); beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + projectConfigManager: mockConfigManager, + jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -347,15 +318,22 @@ describe('lib/optimizely', function() { }); bucketStub = sinon.stub(bucketer, 'bucket'); - sinon.stub(errorHandler, 'handleError'); - sinon.stub(createdLogger, 'log'); + + sinon.stub(createdLogger, 'debug'); + sinon.stub(createdLogger, 'info'); + sinon.stub(createdLogger, 'warn'); + sinon.stub(createdLogger, 'error'); sinon.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); }); afterEach(function() { + eventDispatcher.dispatchEvent.reset(); bucketer.bucket.restore(); - errorHandler.handleError.restore(); - createdLogger.log.restore(); + + createdLogger.debug.restore(); + createdLogger.info.restore(); + createdLogger.warn.restore(); + createdLogger.error.restore(); fns.uuid.restore(); }); @@ -393,6 +371,7 @@ describe('lib/optimizely', function() { rule_type: 'experiment', variation_key: 'variation', enabled: true, + cmab_uuid: undefined, }, }, ], @@ -412,7 +391,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -457,8 +436,8 @@ describe('lib/optimizely', function() { rule_type: 'experiment', variation_key: 'variationWithAudience', enabled: true, + cmab_uuid: undefined, }, - }, ], events: [ @@ -484,7 +463,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -527,6 +506,7 @@ describe('lib/optimizely', function() { rule_type: 'experiment', variation_key: 'variationWithAudience', enabled: true, + cmab_uuid: undefined, }, }, ], @@ -553,7 +533,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -601,6 +581,7 @@ describe('lib/optimizely', function() { rule_type: 'experiment', variation_key: 'variationWithAudience', enabled: true, + cmab_uuid: undefined, }, }, ], @@ -645,7 +626,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -708,6 +689,7 @@ describe('lib/optimizely', function() { rule_type: 'experiment', variation_key: 'var2exp2', enabled: true, + cmab_uuid: undefined, }, }, ], @@ -727,7 +709,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -770,6 +752,7 @@ describe('lib/optimizely', function() { rule_type: 'experiment', variation_key: 'var2exp1', enabled: true, + cmab_uuid: undefined, }, }, ], @@ -796,7 +779,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -814,18 +797,13 @@ describe('lib/optimizely', function() { bucketStub.returns(fakeDecisionResponse); assert.isNull(optlyInstance.activate('testExperiment', 'testUser')); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.called(createdLogger.log); + sinon.assert.called(createdLogger.info); sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'testUser' - ); - - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.INFO, - LOG_MESSAGES.NOT_ACTIVATING_USER, 'OPTIMIZELY', 'testUser', 'testExperiment' + createdLogger.info, + NOT_ACTIVATING_USER, + 'testUser', + 'testExperiment' ); }); @@ -833,21 +811,10 @@ describe('lib/optimizely', function() { assert.isNull(optlyInstance.activate('testExperimentWithAudiences', 'testUser', { browser_type: 'chrome' })); sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'testUser' - ); - - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.INFO, - LOG_MESSAGES.USER_NOT_IN_EXPERIMENT, 'DECISION_SERVICE', 'testUser', 'testExperimentWithAudiences' - ); - - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.INFO, - LOG_MESSAGES.NOT_ACTIVATING_USER, 'OPTIMIZELY', 'testUser', 'testExperimentWithAudiences' + createdLogger.info, + NOT_ACTIVATING_USER, + 'testUser', + 'testExperimentWithAudiences' ); }); @@ -855,58 +822,48 @@ describe('lib/optimizely', function() { assert.isNull(optlyInstance.activate('groupExperiment1', 'testUser', { browser_type: 'chrome' })); sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'testUser' - ); - - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.INFO, - LOG_MESSAGES.USER_NOT_IN_EXPERIMENT, 'DECISION_SERVICE', 'testUser', 'groupExperiment1' - ); - - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.INFO, - LOG_MESSAGES.NOT_ACTIVATING_USER, 'OPTIMIZELY', 'testUser', 'groupExperiment1' + createdLogger.info, + NOT_ACTIVATING_USER, + 'testUser', + 'groupExperiment1' ); }); it('should return null if experiment is not running', function() { assert.isNull(optlyInstance.activate('testExperimentNotRunning', 'testUser')); - sinon.assert.calledTwice(createdLogger.log); - var logMessage1 = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - logMessage1, - sprintf(LOG_MESSAGES.EXPERIMENT_NOT_RUNNING, 'DECISION_SERVICE', 'testExperimentNotRunning') - ); - var logMessage2 = buildLogMessageFromArgs(createdLogger.log.args[1]); - assert.strictEqual( - logMessage2, - sprintf(LOG_MESSAGES.NOT_ACTIVATING_USER, 'OPTIMIZELY', 'testUser', 'testExperimentNotRunning') + sinon.assert.calledWithExactly( + createdLogger.info, + NOT_ACTIVATING_USER, + 'testUser', + 'testExperimentNotRunning' ); }); it('should throw an error for invalid user ID', function() { + const { optlyInstance, errorNotifier, createdLogger } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + assert.isNull(optlyInstance.activate('testExperiment', null)); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledOnce(errorHandler.handleError); - var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + sinon.assert.calledOnce(errorNotifier.notify); - sinon.assert.calledTwice(createdLogger.log); + // var errorMessage = errorHandler.handleError.lastCall.args[0].message; + // assert.strictEqual(errorMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); - var logMessage1 = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage1, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); - var logMessage2 = buildLogMessageFromArgs(createdLogger.log.args[1]); - assert.strictEqual( - logMessage2, - sprintf(LOG_MESSAGES.NOT_ACTIVATING_USER, 'OPTIMIZELY', 'null', 'testExperiment') - ); + // sinon.assert.calledTwice(createdLogger.log); + + // var logMessage1 = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage1, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + + // var logMessage2 = buildLogMessageFromArgs(createdLogger.log.args[1]); + // assert.strictEqual( + // logMessage2, + // sprintf(NOT_ACTIVATING_USER, 'OPTIMIZELY', 'null', 'testExperiment') + // ); }); it('should log an error for invalid experiment key', function() { @@ -914,34 +871,46 @@ describe('lib/optimizely', function() { sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledTwice(createdLogger.log); - var logMessage1 = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - logMessage1, - sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, 'OPTIMIZELY', 'invalidExperimentKey') + sinon.assert.calledWithExactly( + createdLogger.debug, + INVALID_EXPERIMENT_KEY, + 'invalidExperimentKey' ); - var logMessage2 = buildLogMessageFromArgs(createdLogger.log.args[1]); - assert.strictEqual( - logMessage2, - sprintf(LOG_MESSAGES.NOT_ACTIVATING_USER, 'OPTIMIZELY', 'testUser', 'invalidExperimentKey') + + sinon.assert.calledWithExactly( + createdLogger.info, + NOT_ACTIVATING_USER, + 'testUser', + 'invalidExperimentKey' ); }); it('should throw an error for invalid attributes', function() { + const { optlyInstance, errorNotifier, createdLogger } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + sinon.stub(createdLogger, 'info'); + assert.isNull(optlyInstance.activate('testExperimentWithAudiences', 'testUser', [])); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledOnce(errorHandler.handleError); - var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); - - sinon.assert.calledTwice(createdLogger.log); - var logMessage1 = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage1, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); - var logMessage2 = buildLogMessageFromArgs(createdLogger.log.args[1]); - assert.strictEqual( - logMessage2, - sprintf(LOG_MESSAGES.NOT_ACTIVATING_USER, 'OPTIMIZELY', 'testUser', 'testExperimentWithAudiences') + sinon.assert.calledOnce(errorNotifier.notify); + // var errorMessage = errorHandler.handleError.lastCall.args[0].message; + // assert.strictEqual(errorMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + + // sinon.assert.calledTwice(createdLogger.log); + // var logMessage1 = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage1, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + // var logMessage2 = buildLogMessageFromArgs(createdLogger.log.args[1]); + // assert.strictEqual( + // logMessage2, + // sprintf(NOT_ACTIVATING_USER, 'OPTIMIZELY', 'testUser', 'testExperimentWithAudiences') + // ); + sinon.assert.calledWithExactly( + createdLogger.info, + NOT_ACTIVATING_USER, + 'testUser', + 'testExperimentWithAudiences' ); }); @@ -951,12 +920,16 @@ describe('lib/optimizely', function() { reasons: [], }; bucketStub.returns(fakeDecisionResponse); + + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + var instance = new Optimizely({ - datafile: testData.getTestProjectConfig(), - errorHandler: errorHandler, + projectConfigManager: mockConfigManager, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, - logger: logger.createLogger({ + logger: createLogger({ logLevel: enums.LOG_LEVEL.DEBUG, logToConsole: false, }), @@ -984,18 +957,7 @@ describe('lib/optimizely', function() { var activate = optlyInstance.activate('testExperiment', 'user1'); assert.strictEqual(activate, 'control'); - sinon.assert.calledThrice(Optimizely.prototype.validateInputs); - - var logMessage0 = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - logMessage0, - sprintf(LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'user1') - ); - var logMessage1 = buildLogMessageFromArgs(createdLogger.log.args[1]); - assert.strictEqual( - logMessage1, - sprintf(LOG_MESSAGES.USER_FORCED_IN_VARIATION, 'DECISION_SERVICE', 'user1', 'control') - ); + sinon.assert.calledTwice(Optimizely.prototype.validateInputs); var expectedObj = { url: 'https://logx.optimizely.com/v1/events', @@ -1030,7 +992,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1038,26 +1000,6 @@ describe('lib/optimizely', function() { }); }); - it('should not activate when optimizely object is not a valid instance', function() { - var instance = new Optimizely({ - datafile: {}, - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, - logger: createdLogger, - eventProcessor, - notificationCenter, - }); - - createdLogger.log.reset(); - - instance.activate('testExperiment', 'testUser'); - - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'activate')); - - sinon.assert.notCalled(eventDispatcher.dispatchEvent); - }); }); describe('#track', function() { @@ -1092,7 +1034,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1130,7 +1072,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1175,7 +1117,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1213,7 +1155,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1251,7 +1193,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1301,7 +1243,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1348,7 +1290,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1391,7 +1333,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1436,7 +1378,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1485,7 +1427,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1498,7 +1440,7 @@ describe('lib/optimizely', function() { optlyInstance.track('testEvent', 'testUser', undefined, '4200'); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledOnce(createdLogger.log); + sinon.assert.calledOnce(createdLogger.error); }); it('should track a user for an experiment not running', function() { @@ -1530,7 +1472,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1575,7 +1517,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1613,7 +1555,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1658,7 +1600,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -1668,39 +1610,48 @@ describe('lib/optimizely', function() { }); it('should throw an error for invalid user ID', function() { + const { optlyInstance, errorNotifier, createdLogger } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + + sinon.stub(createdLogger, 'info'); + optlyInstance.track('testEvent', null); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledOnce(errorHandler.handleError); - var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + sinon.assert.calledOnce(errorNotifier.notify); - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + // var errorMessage = errorHandler.handleError.lastCall.args[0].message; + // assert.strictEqual(errorMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + + // sinon.assert.calledOnce(createdLogger.log); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); }); it('should log a warning for an event key that is not in the datafile and a warning for not tracking user', function() { - optlyInstance.track('invalidEventKey', 'testUser'); + const { optlyInstance, errorNotifier, createdLogger, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + + sinon.stub(createdLogger, 'warn'); - sinon.assert.calledTwice(createdLogger.log); + optlyInstance.track('invalidEventKey', 'testUser'); - var logCall1 = createdLogger.log.getCall(0); sinon.assert.calledWithExactly( - logCall1, - LOG_LEVEL.WARNING, - LOG_MESSAGES.EVENT_KEY_NOT_FOUND, 'OPTIMIZELY', 'invalidEventKey' + createdLogger.warn, + EVENT_KEY_NOT_FOUND, + 'invalidEventKey' ); - var logCall2 = createdLogger.log.getCall(1); sinon.assert.calledWithExactly( - logCall2, - LOG_LEVEL.WARNING, - LOG_MESSAGES.NOT_TRACKING_USER, 'OPTIMIZELY', 'testUser' + createdLogger.warn, + NOT_TRACKING_USER, + 'testUser' ); - sinon.assert.notCalled(errorHandler.handleError); + // sinon.assert.notCalled(errorHandler.handleError); }); it('should throw an error for invalid attributes', function() { @@ -1708,27 +1659,30 @@ describe('lib/optimizely', function() { sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledOnce(errorHandler.handleError); - var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + // sinon.assert.calledOnce(errorHandler.handleError); + // var errorMessage = errorHandler.handleError.lastCall.args[0].message; + // assert.strictEqual(errorMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + // sinon.assert.calledOnce(createdLogger.log); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); }); it('should not throw an error for an event key without associated experiment IDs', function() { optlyInstance.track('testEventWithoutExperiments', 'testUser'); - sinon.assert.notCalled(errorHandler.handleError); + // sinon.assert.notCalled(errorHandler.handleError); }); it('should track when logger is in DEBUG mode', function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + var instance = new Optimizely({ - datafile: testData.getTestProjectConfig(), - errorHandler: errorHandler, + projectConfigManager: mockConfigManager, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, - logger: logger.createLogger({ + logger: createLogger({ logLevel: enums.LOG_LEVEL.DEBUG, logToConsole: false, }), @@ -1741,27 +1695,6 @@ describe('lib/optimizely', function() { instance.track('testEvent', 'testUser'); sinon.assert.calledOnce(eventDispatcher.dispatchEvent); }); - - it('should not track when optimizely object is not a valid instance', function() { - var instance = new Optimizely({ - datafile: {}, - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, - logger: createdLogger, - eventProcessor, - notificationCenter, - }); - - createdLogger.log.reset(); - - instance.track('testExperiment', 'testUser'); - - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'track')); - - sinon.assert.notCalled(eventDispatcher.dispatchEvent); - }); }); describe('#getVariation', function() { @@ -1776,13 +1709,6 @@ describe('lib/optimizely', function() { assert.strictEqual(variation, 'variation'); sinon.assert.calledOnce(bucketer.bucket); - sinon.assert.called(createdLogger.log); - - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'testUser' - ); }); it('should call bucketer and return variation key with attributes', function() { @@ -1798,7 +1724,6 @@ describe('lib/optimizely', function() { assert.strictEqual(getVariation, 'variationWithAudience'); sinon.assert.calledOnce(bucketer.bucket); - sinon.assert.called(createdLogger.log); }); it('should return null if user is not in audience or experiment is not running', function() { @@ -1809,65 +1734,54 @@ describe('lib/optimizely', function() { assert.isNull(getVariationReturnsNull2); sinon.assert.notCalled(bucketer.bucket); - sinon.assert.called(createdLogger.log); - - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'testUser' - ); - - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.INFO, - LOG_MESSAGES.USER_NOT_IN_EXPERIMENT, 'DECISION_SERVICE', 'testUser', 'testExperimentWithAudiences' - ); - - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.INFO, - LOG_MESSAGES.EXPERIMENT_NOT_RUNNING, 'DECISION_SERVICE', 'testExperimentNotRunning' - ); }); it('should throw an error for invalid user ID', function() { + const { optlyInstance, errorNotifier, createdLogger, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + var getVariationWithError = optlyInstance.getVariation('testExperiment', null); assert.isNull(getVariationWithError); - sinon.assert.calledOnce(errorHandler.handleError); - var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + sinon.assert.calledOnce(errorNotifier.notify); + // var errorMessage = errorHandler.handleError.lastCall.args[0].message; + // assert.strictEqual(errorMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + // sinon.assert.calledOnce(createdLogger.log); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); }); it('should log an error for invalid experiment key', function() { var getVariationWithError = optlyInstance.getVariation('invalidExperimentKey', 'testUser'); assert.isNull(getVariationWithError); - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - logMessage, - sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, 'OPTIMIZELY', 'invalidExperimentKey') + sinon.assert.calledWithExactly( + createdLogger.debug, + INVALID_EXPERIMENT_KEY, + 'invalidExperimentKey' ); }); it('should throw an error for invalid attributes', function() { + const { optlyInstance, errorNotifier, createdLogger } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + var getVariationWithError = optlyInstance.getVariation('testExperimentWithAudiences', 'testUser', []); assert.isNull(getVariationWithError); - sinon.assert.calledOnce(errorHandler.handleError); - var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + sinon.assert.calledOnce(errorNotifier.notify); + + // var errorMessage = errorHandler.handleError.lastCall.args[0].message; + // assert.strictEqual(errorMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + // sinon.assert.calledOnce(createdLogger.log); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); }); describe('whitelisting', function() { @@ -1883,42 +1797,8 @@ describe('lib/optimizely', function() { var getVariation = optlyInstance.getVariation('testExperiment', 'user1'); assert.strictEqual(getVariation, 'control'); - sinon.assert.calledTwice(Optimizely.prototype.validateInputs); - - sinon.assert.calledTwice(createdLogger.log); - - var logMessage0 = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - logMessage0, - sprintf(LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'user1') - ); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[1]); - assert.strictEqual( - logMessage, - sprintf(LOG_MESSAGES.USER_FORCED_IN_VARIATION, 'DECISION_SERVICE', 'user1', 'control') - ); - }); - }); - - it('should not return variation when optimizely object is not a valid instance', function() { - var instance = new Optimizely({ - datafile: {}, - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, - logger: createdLogger, - eventProcessor, - notificationCenter, + sinon.assert.calledOnce(Optimizely.prototype.validateInputs); }); - - createdLogger.log.reset(); - - instance.getVariation('testExperiment', 'testUser'); - - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'getVariation')); - - sinon.assert.notCalled(eventDispatcher.dispatchEvent); }); describe('order of bucketing operations', function() { @@ -1966,41 +1846,38 @@ describe('lib/optimizely', function() { it('should return null when set has not been called', function() { var forcedVariation = optlyInstance.getForcedVariation('testExperiment', 'user1'); assert.strictEqual(forcedVariation, null); - - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, 'DECISION_SERVICE', 'user1')); }); it('should return null with a null experimentKey', function() { var forcedVariation = optlyInstance.getForcedVariation(null, 'user1'); assert.strictEqual(forcedVariation, null); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key')); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key')); }); it('should return null with an undefined experimentKey', function() { var forcedVariation = optlyInstance.getForcedVariation(undefined, 'user1'); assert.strictEqual(forcedVariation, null); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key')); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key')); }); it('should return null with a null userId', function() { var forcedVariation = optlyInstance.getForcedVariation('testExperiment', null); assert.strictEqual(forcedVariation, null); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); }); it('should return null with an undefined userId', function() { var forcedVariation = optlyInstance.getForcedVariation('testExperiment', undefined); assert.strictEqual(forcedVariation, null); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); }); }); @@ -2008,12 +1885,6 @@ describe('lib/optimizely', function() { it('should be able to set a forced variation', function() { var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'user1', 'control'); assert.strictEqual(didSetVariation, true); - - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - logMessage, - sprintf(LOG_MESSAGES.USER_MAPPED_TO_FORCED_VARIATION, 'DECISION_SERVICE', 111128, 111127, 'user1') - ); }); it('should override bucketing in optlyInstance.getVariation', function() { @@ -2044,25 +1915,6 @@ describe('lib/optimizely', function() { var forcedVariation2 = optlyInstance.getForcedVariation('testExperiment', 'user1'); assert.strictEqual(forcedVariation2, null); - - var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - var variationIsMappedLogMessage = buildLogMessageFromArgs(createdLogger.log.args[1]); - var variationMappingRemovedLogMessage = buildLogMessageFromArgs(createdLogger.log.args[2]); - - assert.strictEqual( - setVariationLogMessage, - sprintf(LOG_MESSAGES.USER_MAPPED_TO_FORCED_VARIATION, 'DECISION_SERVICE', 111128, 111127, 'user1') - ); - - assert.strictEqual( - variationIsMappedLogMessage, - sprintf(LOG_MESSAGES.USER_HAS_FORCED_VARIATION, 'DECISION_SERVICE', 'control', 'testExperiment', 'user1') - ); - - assert.strictEqual( - variationMappingRemovedLogMessage, - sprintf(LOG_MESSAGES.VARIATION_REMOVED_FOR_USER, 'DECISION_SERVICE', 'testExperiment', 'user1') - ); }); it('should be able to set multiple experiments for one user', function() { @@ -2086,28 +1938,11 @@ describe('lib/optimizely', function() { 'definitely_not_valid_variation_key' ); assert.strictEqual(didSetVariation, false); - - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - logMessage, - sprintf( - ERROR_MESSAGES.NO_VARIATION_FOR_EXPERIMENT_KEY, - 'DECISION_SERVICE', - 'definitely_not_valid_variation_key', - 'testExperiment' - ) - ); }); it('should not set an invalid experiment', function() { var didSetVariation = optlyInstance.setForcedVariation('definitely_not_valid_exp_key', 'user1', 'control'); assert.strictEqual(didSetVariation, false); - - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - logMessage, - sprintf(ERROR_MESSAGES.EXPERIMENT_KEY_NOT_IN_DATAFILE, 'PROJECT_CONFIG', 'definitely_not_valid_exp_key') - ); }); it('should return null for user has no forced variation for experiment', function() { @@ -2116,78 +1951,61 @@ describe('lib/optimizely', function() { var forcedVariation = optlyInstance.getForcedVariation('testExperimentLaunched', 'user1'); assert.strictEqual(forcedVariation, null); - - var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - setVariationLogMessage, - sprintf(LOG_MESSAGES.USER_MAPPED_TO_FORCED_VARIATION, 'DECISION_SERVICE', 111128, 111127, 'user1') - ); - - var noVariationToGetLogMessage = buildLogMessageFromArgs(createdLogger.log.args[1]); - assert.strictEqual( - noVariationToGetLogMessage, - sprintf( - LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, - 'DECISION_SERVICE', - 'testExperimentLaunched', - 'user1' - ) - ); }); it('should return false for a null experimentKey', function() { var didSetVariation = optlyInstance.setForcedVariation(null, 'user1', 'control'); assert.strictEqual(didSetVariation, false); - var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - setVariationLogMessage, - sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key') - ); + // var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual( + // setVariationLogMessage, + // sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key') + // ); }); it('should return false for an undefined experimentKey', function() { var didSetVariation = optlyInstance.setForcedVariation(undefined, 'user1', 'control'); assert.strictEqual(didSetVariation, false); - var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - setVariationLogMessage, - sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key') - ); + // var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual( + // setVariationLogMessage, + // sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key') + // ); }); it('should return false for an empty experimentKey', function() { var didSetVariation = optlyInstance.setForcedVariation('', 'user1', 'control'); assert.strictEqual(didSetVariation, false); - var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - setVariationLogMessage, - sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key') - ); + // var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual( + // setVariationLogMessage, + // sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'experiment_key') + // ); }); it('should return false for a null userId', function() { var didSetVariation = optlyInstance.setForcedVariation('testExperiment', null, 'control'); assert.strictEqual(didSetVariation, false); - var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - setVariationLogMessage, - sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id') - ); + // var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual( + // setVariationLogMessage, + // sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id') + // ); }); it('should return false for an undefined userId', function() { var didSetVariation = optlyInstance.setForcedVariation('testExperiment', undefined, 'control'); assert.strictEqual(didSetVariation, false); - var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - setVariationLogMessage, - sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id') - ); + // var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual( + // setVariationLogMessage, + // sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id') + // ); }); it('should return true for an empty userId', function() { @@ -2198,23 +2016,11 @@ describe('lib/optimizely', function() { it('should return false for a null variationKey', function() { var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'user1', null); assert.strictEqual(didSetVariation, false); - - var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - setVariationLogMessage, - sprintf(ERROR_MESSAGES.USER_NOT_IN_FORCED_VARIATION, 'DECISION_SERVICE', 'user1') - ); }); it('should return false for an undefined variationKey', function() { var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'user1', undefined); assert.strictEqual(didSetVariation, false); - - var setVariationLogMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - setVariationLogMessage, - sprintf(ERROR_MESSAGES.USER_NOT_IN_FORCED_VARIATION, 'DECISION_SERVICE', 'user1') - ); }); it('should not override check for not running experiments in getVariation', function() { @@ -2227,18 +2033,6 @@ describe('lib/optimizely', function() { var variation = optlyInstance.getVariation('testExperimentNotRunning', 'user1', {}); assert.strictEqual(variation, null); - - var logMessage0 = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual( - logMessage0, - sprintf(LOG_MESSAGES.USER_MAPPED_TO_FORCED_VARIATION, 'DECISION_SERVICE', 133338, 133337, 'user1') - ); - - var logMessage1 = buildLogMessageFromArgs(createdLogger.log.args[1]); - assert.strictEqual( - logMessage1, - sprintf(LOG_MESSAGES.EXPERIMENT_NOT_RUNNING, 'DECISION_SERVICE', 'testExperimentNotRunning') - ); }); }); @@ -2247,7 +2041,7 @@ describe('lib/optimizely', function() { assert.isTrue(optlyInstance.validateInputs({ user_id: 'testUser' })); assert.isTrue(optlyInstance.validateInputs({ user_id: '' })); assert.isTrue(optlyInstance.validateInputs({ user_id: 'testUser' }, { browser_type: 'firefox' })); - sinon.assert.notCalled(createdLogger.log); + // sinon.assert.notCalled(createdLogger.log); }); it('should return false and throw an error if user ID is invalid', function() { @@ -2260,26 +2054,31 @@ describe('lib/optimizely', function() { falseUserIdInput = optlyInstance.validateInputs({ user_id: 3.14 }); assert.isFalse(falseUserIdInput); - sinon.assert.calledThrice(errorHandler.handleError); - var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + // sinon.assert.calledThrice(errorHandler.handleError); + // var errorMessage = errorHandler.handleError.lastCall.args[0].message; + // assert.strictEqual(errorMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); - sinon.assert.calledThrice(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + // sinon.assert.calledThrice(createdLogger.log); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); }); it('should return false and throw an error if attributes are invalid', function() { + const { optlyInstance, errorNotifier} = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + var falseUserIdInput = optlyInstance.validateInputs({ user_id: 'testUser' }, []); assert.isFalse(falseUserIdInput); - sinon.assert.calledOnce(errorHandler.handleError); - var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + sinon.assert.calledOnce(errorNotifier.notify); + + // var errorMessage = errorHandler.handleError.lastCall.args[0].message; + // assert.strictEqual(errorMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + // sinon.assert.calledOnce(createdLogger.log); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); }); }); @@ -2333,14 +2132,14 @@ describe('lib/optimizely', function() { }); it('should call a listener added for activate when activate is called', function() { - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateListener); var variationKey = optlyInstance.activate('testExperiment', 'testUser'); assert.strictEqual(variationKey, 'variation'); sinon.assert.calledOnce(activateListener); }); it('should call a listener added for track when track is called', function() { - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackListener); optlyInstance.activate('testExperiment', 'testUser'); optlyInstance.track('testEvent', 'testUser'); sinon.assert.calledOnce(trackListener); @@ -2348,7 +2147,7 @@ describe('lib/optimizely', function() { it('should not call a removed activate listener when activate is called', function() { var listenerId = optlyInstance.notificationCenter.addNotificationListener( - enums.NOTIFICATION_TYPES.ACTIVATE, + NOTIFICATION_TYPES.ACTIVATE, activateListener ); optlyInstance.notificationCenter.removeNotificationListener(listenerId); @@ -2359,7 +2158,7 @@ describe('lib/optimizely', function() { it('should not call a removed track listener when track is called', function() { var listenerId = optlyInstance.notificationCenter.addNotificationListener( - enums.NOTIFICATION_TYPES.TRACK, + NOTIFICATION_TYPES.TRACK, trackListener ); optlyInstance.notificationCenter.removeNotificationListener(listenerId); @@ -2369,9 +2168,9 @@ describe('lib/optimizely', function() { }); it('removeNotificationListener should only remove the listener with the argument ID', function() { - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateListener); var trackListenerId = optlyInstance.notificationCenter.addNotificationListener( - enums.NOTIFICATION_TYPES.TRACK, + NOTIFICATION_TYPES.TRACK, trackListener ); optlyInstance.notificationCenter.removeNotificationListener(trackListenerId); @@ -2381,8 +2180,8 @@ describe('lib/optimizely', function() { }); it('should clear all notification listeners when clearAllNotificationListeners is called', function() { - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateListener); - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackListener); optlyInstance.notificationCenter.clearAllNotificationListeners(); optlyInstance.activate('testExperiment', 'testUser'); optlyInstance.track('testEvent', 'testUser'); @@ -2392,9 +2191,9 @@ describe('lib/optimizely', function() { }); it('should clear listeners of certain notification type when clearNotificationListeners is called', function() { - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateListener); - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackListener); - optlyInstance.notificationCenter.clearNotificationListeners(enums.NOTIFICATION_TYPES.ACTIVATE); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackListener); + optlyInstance.notificationCenter.clearNotificationListeners(NOTIFICATION_TYPES.ACTIVATE); optlyInstance.activate('testExperiment', 'testUser'); optlyInstance.track('testEvent', 'testUser'); @@ -2402,13 +2201,6 @@ describe('lib/optimizely', function() { sinon.assert.calledOnce(trackListener); }); - it('should only call the listener once after the same listener was added twice', function() { - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateListener); - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateListener); - optlyInstance.activate('testExperiment', 'testUser'); - sinon.assert.calledOnce(activateListener); - }); - it('should not add a listener with an invalid type argument', function() { var listenerId = optlyInstance.notificationCenter.addNotificationListener( 'not a notification type', @@ -2422,16 +2214,16 @@ describe('lib/optimizely', function() { }); it('should call multiple notification listeners for activate when activate is called', function() { - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateListener); - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateListener2); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateListener2); optlyInstance.activate('testExperiment', 'testUser'); sinon.assert.calledOnce(activateListener); sinon.assert.calledOnce(activateListener2); }); it('should call multiple notification listeners for track when track is called', function() { - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackListener); - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackListener2); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackListener2); optlyInstance.activate('testExperiment', 'testUser'); optlyInstance.track('testEvent', 'testUser'); sinon.assert.calledOnce(trackListener); @@ -2439,7 +2231,7 @@ describe('lib/optimizely', function() { }); it('should pass the correct arguments to an activate listener when activate is called', function() { - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateListener); optlyInstance.activate('testExperiment', 'testUser'); var expectedImpressionEvent = { httpVerb: 'POST', @@ -2458,10 +2250,11 @@ describe('lib/optimizely', function() { variation_id: '111129', metadata: { flag_key: '', - rule_key: "testExperiment", - rule_type: "experiment", - variation_key: "variation", + rule_key: 'testExperiment', + rule_type: 'experiment', + variation_key: 'variation', enabled: true, + cmab_uuid: undefined, }, }, ], @@ -2481,7 +2274,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -2489,6 +2282,7 @@ describe('lib/optimizely', function() { var instanceExperiments = optlyInstance.projectConfigManager.getConfig().experiments; var expectedArgument = { experiment: instanceExperiments[0], + holdout: null, userId: 'testUser', attributes: undefined, variation: instanceExperiments[0].variations[1], @@ -2501,7 +2295,7 @@ describe('lib/optimizely', function() { var attributes = { browser_type: 'firefox', }; - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.ACTIVATE, activateListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.ACTIVATE, activateListener); optlyInstance.activate('testExperiment', 'testUser', attributes); var expectedImpressionEvent = { httpVerb: 'POST', @@ -2520,10 +2314,11 @@ describe('lib/optimizely', function() { variation_id: '111129', metadata: { flag_key: '', - rule_key: "testExperiment", - rule_type: "experiment", - variation_key: "variation", + rule_key: 'testExperiment', + rule_type: 'experiment', + variation_key: 'variation', enabled: true, + cmab_uuid: undefined, }, }, ], @@ -2550,7 +2345,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -2558,6 +2353,7 @@ describe('lib/optimizely', function() { var instanceExperiments = optlyInstance.projectConfigManager.getConfig().experiments; var expectedArgument = { experiment: instanceExperiments[0], + holdout: null, userId: 'testUser', attributes: attributes, variation: instanceExperiments[0].variations[1], @@ -2567,7 +2363,7 @@ describe('lib/optimizely', function() { }); it('should pass the correct arguments to a track listener when track is called', function() { - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackListener); optlyInstance.activate('testExperiment', 'testUser'); optlyInstance.track('testEvent', 'testUser'); var expectedConversionEvent = { @@ -2596,7 +2392,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -2615,7 +2411,7 @@ describe('lib/optimizely', function() { var attributes = { browser_type: 'firefox', }; - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackListener); optlyInstance.activate('testExperiment', 'testUser', attributes); optlyInstance.track('testEvent', 'testUser', attributes); var expectedConversionEvent = { @@ -2651,7 +2447,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -2674,7 +2470,7 @@ describe('lib/optimizely', function() { value: 1.234, non_revenue: 'abc', }; - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.TRACK, trackListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.TRACK, trackListener); optlyInstance.activate('testExperiment', 'testUser', attributes); optlyInstance.track('testEvent', 'testUser', attributes, eventTags); var expectedConversionEvent = { @@ -2715,7 +2511,7 @@ describe('lib/optimizely', function() { ], revision: '42', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: false, enrich_decisions: true, }, @@ -2738,11 +2534,15 @@ describe('lib/optimizely', function() { describe('activate', function() { beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + projectConfigManager: mockConfigManager, + + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -2751,7 +2551,7 @@ describe('lib/optimizely', function() { }); optlyInstance.notificationCenter.addNotificationListener( - enums.NOTIFICATION_TYPES.DECISION, + NOTIFICATION_TYPES.DECISION, decisionListener ); }); @@ -2797,10 +2597,15 @@ describe('lib/optimizely', function() { describe('getVariation', function() { beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), - errorHandler: errorHandler, + projectConfigManager: mockConfigManager, + + eventProcessor, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -2810,7 +2615,7 @@ describe('lib/optimizely', function() { }); optlyInstance.notificationCenter.addNotificationListener( - enums.NOTIFICATION_TYPES.DECISION, + NOTIFICATION_TYPES.DECISION, decisionListener ); }); @@ -2849,10 +2654,14 @@ describe('lib/optimizely', function() { }); it('should send notification with variation key and type feature-test when getVariation returns feature experiment variation', function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + }); + var optly = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: errorHandler, + projectConfigManager: mockConfigManager, + eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -2861,7 +2670,7 @@ describe('lib/optimizely', function() { notificationCenter, }); - optly.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionListener); + optly.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionListener); fakeDecisionResponse = { result: '594099', @@ -2886,10 +2695,14 @@ describe('lib/optimizely', function() { var sandbox = sinon.sandbox.create(); beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: errorHandler, + projectConfigManager: mockConfigManager, + jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -2899,7 +2712,7 @@ describe('lib/optimizely', function() { }); optlyInstance.notificationCenter.addNotificationListener( - enums.NOTIFICATION_TYPES.DECISION, + NOTIFICATION_TYPES.DECISION, decisionListener ); }); @@ -3045,10 +2858,10 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, false); sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.INFO, - '%s: Feature %s is not enabled for user %s.', - 'OPTIMIZELY', 'test_feature', 'user1' + createdLogger.info, + FEATURE_NOT_ENABLED_FOR_USER, + 'test_feature', + 'user1' ); var expectedArguments = { @@ -3240,7 +3053,7 @@ describe('lib/optimizely', function() { variableKey: 'button_info', variableValue: { num_buttons: 1, - text: "first variation", + text: 'first variation', }, variableType: FEATURE_VARIABLE_TYPES.JSON, source: DECISION_SOURCES.FEATURE_TEST, @@ -3394,11 +3207,9 @@ describe('lib/optimizely', function() { }); it('returns the right value from getAllFeatureVariables and send notification with featureEnabled true', function() { - var result = optlyInstance.getAllFeatureVariables( - 'test_feature_for_experiment', - 'user1', - { test_attribute: 'test_value' } - ); + var result = optlyInstance.getAllFeatureVariables('test_feature_for_experiment', 'user1', { + test_attribute: 'test_value', + }); assert.deepEqual(result, { is_button_animated: true, button_width: 20.25, @@ -3597,11 +3408,9 @@ describe('lib/optimizely', function() { }); it('returns the right value from getAllFeatureVariables and send notification with featureEnabled false', function() { - var result = optlyInstance.getAllFeatureVariables( - 'test_feature_for_experiment', - 'user1', - { test_attribute: 'test_value' } - ); + var result = optlyInstance.getAllFeatureVariables('test_feature_for_experiment', 'user1', { + test_attribute: 'test_value', + }); assert.deepEqual(result, { is_button_animated: false, button_width: 50.55, @@ -3883,11 +3692,9 @@ describe('lib/optimizely', function() { }); it('returns the right value from getAllFeatureVariables and send notification with featureEnabled true', function() { - var result = optlyInstance.getAllFeatureVariables( - 'test_feature', - 'user1', - { test_attribute: 'test_value' } - ); + var result = optlyInstance.getAllFeatureVariables('test_feature', 'user1', { + test_attribute: 'test_value', + }); assert.deepEqual(result, { new_content: true, price: 4.99, @@ -4031,7 +3838,7 @@ describe('lib/optimizely', function() { }); assert.deepEqual(result, { count: 1, - message: 'Hello' + message: 'Hello', }); sinon.assert.calledWith(decisionListener, { type: DECISION_NOTIFICATION_TYPES.FEATURE_VARIABLE, @@ -4161,11 +3968,9 @@ describe('lib/optimizely', function() { }); it('returns the default value from getAllFeatureVariables and send notification with featureEnabled false', function() { - var result = optlyInstance.getAllFeatureVariables( - 'test_feature', - 'user1', - { test_attribute: 'test_value' } - ); + var result = optlyInstance.getAllFeatureVariables('test_feature', 'user1', { + test_attribute: 'test_value', + }); assert.deepEqual(result, { new_content: false, price: 14.99, @@ -4456,11 +4261,9 @@ describe('lib/optimizely', function() { }); it('returns the default value from getAllFeatureVariables and send notification with featureEnabled false', function() { - var result = optlyInstance.getAllFeatureVariables( - 'test_feature_for_experiment', - 'user1', - { test_attribute: 'test_value' } - ); + var result = optlyInstance.getAllFeatureVariables('test_feature_for_experiment', 'user1', { + test_attribute: 'test_value', + }); assert.deepEqual(result, { is_button_animated: false, button_width: 50.55, @@ -4469,7 +4272,7 @@ describe('lib/optimizely', function() { button_info: { num_buttons: 0, text: 'default value', - } + }, }); sinon.assert.calledWith(decisionListener, { type: DECISION_NOTIFICATION_TYPES.ALL_FEATURE_VARIABLES, @@ -4486,7 +4289,7 @@ describe('lib/optimizely', function() { button_info: { num_buttons: 0, text: 'default value', - } + }, }, source: DECISION_SOURCES.ROLLOUT, sourceInfo: {}, @@ -4503,23 +4306,25 @@ describe('lib/optimizely', function() { describe('decide APIs', function() { var optlyInstance; var bucketStub; - var createdLogger = logger.createLogger({ + var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO, logToConsole: false, }); - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); - var eventProcessor = createEventProcessor({ - dispatcher: eventDispatcher, - batchSize: 1, - notificationCenter: notificationCenter, - }); + var notificationCenter = createNotificationCenter({ logger: createdLogger, }); + var eventDispatcher = getMockEventDispatcher(); + var eventProcessor = getForwardingEventProcessor( + eventDispatcher, + ); describe('#createUserContext', function() { beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), - errorHandler: errorHandler, + projectConfigManager: mockConfigManager, eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -4530,15 +4335,22 @@ describe('lib/optimizely', function() { }); bucketStub = sinon.stub(bucketer, 'bucket'); - sinon.stub(errorHandler, 'handleError'); - sinon.stub(createdLogger, 'log'); + + sinon.stub(createdLogger, 'debug'); + sinon.stub(createdLogger, 'info'); + sinon.stub(createdLogger, 'warn'); + sinon.stub(createdLogger, 'error'); sinon.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); }); afterEach(function() { + eventDispatcher.dispatchEvent.reset(); bucketer.bucket.restore(); - errorHandler.handleError.restore(); - createdLogger.log.restore(); + + createdLogger.debug.restore(); + createdLogger.info.restore(); + createdLogger.warn.restore(); + createdLogger.error.restore(); fns.uuid.restore(); }); @@ -4561,8 +4373,29 @@ describe('lib/optimizely', function() { assert.deepEqual(userId, user.getUserId()); }); + it('should create OptimizelyUserContext when input userId is an empty string', function() { + var userId = ''; + var user = optlyInstance.createUserContext(userId); + assert.instanceOf(user, OptimizelyUserContext); + assert.deepEqual(optlyInstance, user.getOptimizely()); + assert.deepEqual({}, user.getAttributes()); + assert.deepEqual(userId, user.getUserId()); + }); + + it('should throw error when input userId is null', function() { + assert.throws(() => { + optlyInstance.createUserContext(null); + }, Error, INVALID_IDENTIFIER); + }); + + it('should throw error when input userId is undefined', function() { + assert.throws(() => { + optlyInstance.createUserContext(undefined); + }, Error, INVALID_IDENTIFIER); + }); + it('should create multiple instances of OptimizelyUserContext', function() { - var userId1 = 'testUser1' + var userId1 = 'testUser1'; var userId2 = 'testUser2'; var attributes1 = { test_attribute: 'test_value' }; var user1 = optlyInstance.createUserContext(userId1, attributes1); @@ -4577,56 +4410,61 @@ describe('lib/optimizely', function() { assert.deepEqual(user2.getUserId(), userId2); }); - it('should call the error handler for invalid user ID and return null', function() { - assert.isNull(optlyInstance.createUserContext(null)); - sinon.assert.calledOnce(errorHandler.handleError); - var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + it('should call the error handler for invalid user ID and throw', function() { + const { optlyInstance, errorNotifier, createdLogger } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + assert.throws(() => optlyInstance.createUserContext(1), Error, INVALID_IDENTIFIER); + sinon.assert.calledOnce(errorNotifier.notify); + // var errorMessage = errorHandler.handleError.lastCall.args[0].message; + // assert.strictEqual(errorMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); + // sinon.assert.calledOnce(createdLogger.log); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_INPUT_FORMAT, 'OPTIMIZELY', 'user_id')); }); - it('should call the error handler for invalid attributes and return null', function() { - assert.isNull(optlyInstance.createUserContext('user1', 'invalid_attributes')); - sinon.assert.calledOnce(errorHandler.handleError); - var errorMessage = errorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + it('should call the error handler for invalid attributes and throw', function() { + const { optlyInstance, errorNotifier, createdLogger } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + assert.throws(() => optlyInstance.createUserContext('user1', 'invalid_attributes'), Error, INVALID_ATTRIBUTES); + sinon.assert.calledOnce(errorNotifier.notify); + // var errorMessage = errorHandler.handleError.lastCall.args[0].message; + // assert.strictEqual(errorMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + // sinon.assert.calledOnce(createdLogger.log); + // var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); + // assert.strictEqual(logMessage, sprintf(INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); }); }); describe('#decide', function() { var userId = 'tester'; describe('with empty default decide options', function() { + let optlyInstance, notificationCenter, createdLogger; beforeEach(function() { - optlyInstance = new Optimizely({ - clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - isValidInstance: true, - eventBatchSize: 1, - defaultDecideOptions: [], - notificationCenter, - eventProcessor, - }); + + ({ optlyInstance, notificationCenter, createdLogger, eventDispatcher} = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + })); - sinon.stub(optlyInstance.notificationCenter, 'sendNotifications'); - sinon.stub(errorHandler, 'handleError'); - sinon.stub(createdLogger, 'log'); + + + sinon.stub(createdLogger, 'debug'); + sinon.stub(createdLogger, 'info'); + sinon.stub(createdLogger, 'warn'); + sinon.stub(createdLogger, 'error'); sinon.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); }); afterEach(function() { - errorHandler.handleError.restore(); - createdLogger.log.restore(); + eventDispatcher.dispatchEvent.reset(); + + createdLogger.debug.restore(); + createdLogger.info.restore(); + createdLogger.warn.restore(); + createdLogger.error.restore(); fns.uuid.restore(); - optlyInstance.notificationCenter.sendNotifications.restore(); + notificationCenter.sendNotifications.restore(); }); it('should return error decision object when provided flagKey is invalid and do not dispatch an event', function() { @@ -4643,14 +4481,14 @@ describe('lib/optimizely', function() { ruleKey: null, flagKey: flagKey, userContext: user, - reasons: [ sprintf(DECISION_MESSAGES.FLAG_KEY_INVALID, flagKey) ], - } + reasons: [sprintf(DECISION_MESSAGES.FLAG_KEY_INVALID, flagKey)], + }; assert.deepEqual(decision, expectedDecision); sinon.assert.notCalled(eventDispatcher.dispatchEvent); }); it('should return error decision object when SDK is not ready and do not dispatch an event', function() { - optlyInstance.projectConfigManager.getConfig.returns(null); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(null); var flagKey = 'feature_2'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, @@ -4664,13 +4502,22 @@ describe('lib/optimizely', function() { ruleKey: null, flagKey: flagKey, userContext: user, - reasons: [ DECISION_MESSAGES.SDK_NOT_READY ], - } + reasons: [DECISION_MESSAGES.SDK_NOT_READY], + }; assert.deepEqual(decision, expectedDecision); sinon.assert.notCalled(eventDispatcher.dispatchEvent); }); it('should make a decision for feature_test and dispatch an event', function() { + const { optlyInstance, eventDispatcher } = getOptlyInstance( + { + datafileObj: testData.getTestDecideProjectConfig(), + } + ); + + const notificationCenter = optlyInstance.notificationCenter; + sinon.stub(notificationCenter, 'sendNotifications'); + var flagKey = 'feature_2'; var expectedVariables = optlyInstance.getAllFeatureVariables(flagKey, userId); var user = new OptimizelyUserContext({ @@ -4686,7 +4533,7 @@ describe('lib/optimizely', function() { flagKey: flagKey, userContext: user, reasons: [], - } + }; assert.deepEqual(decision, expectedDecision); sinon.assert.calledOnce(eventDispatcher.dispatchEvent); var expectedImpressionEvent = { @@ -4710,6 +4557,7 @@ describe('lib/optimizely', function() { rule_type: 'feature-test', variation_key: 'variation_with_traffic', enabled: true, + cmab_uuid: undefined, }, }, ], @@ -4736,15 +4584,15 @@ describe('lib/optimizely', function() { ], revision: '241', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: true, enrich_decisions: true, }, }; var callArgs = eventDispatcher.dispatchEvent.getCalls()[0].args; assert.deepEqual(callArgs[0], expectedImpressionEvent); - sinon.assert.callCount(optlyInstance.notificationCenter.sendNotifications, 4) - var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(3).args; + sinon.assert.callCount(notificationCenter.sendNotifications, 4); + var notificationCallArgs = notificationCenter.sendNotifications.getCall(3).args; var expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -4754,25 +4602,36 @@ describe('lib/optimizely', function() { decisionInfo: { flagKey: 'feature_2', enabled: true, - ruleKey: "exp_no_audience", - variationKey: "variation_with_traffic", + ruleKey: 'exp_no_audience', + variationKey: 'variation_with_traffic', variables: { i_42: 42 }, decisionEventDispatched: true, reasons: [], + experimentId: '10420810910', + variationId: '10418551353', }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); it('should make a decision and do not dispatch an event with DISABLE_DECISION_EVENT passed in decide options', function() { + const { optlyInstance, eventDispatcher } = getOptlyInstance( + { + datafileObj: testData.getTestDecideProjectConfig(), + } + ); + + const notificationCenter = optlyInstance.notificationCenter; + sinon.stub(notificationCenter, 'sendNotifications'); + var flagKey = 'feature_2'; var expectedVariables = optlyInstance.getAllFeatureVariables(flagKey, userId); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, }); - var decision = optlyInstance.decide(user, flagKey, [ OptimizelyDecideOption.DISABLE_DECISION_EVENT ]); + var decision = optlyInstance.decide(user, flagKey, [OptimizelyDecideOption.DISABLE_DECISION_EVENT]); var expectedDecision = { variationKey: 'variation_with_traffic', enabled: true, @@ -4781,11 +4640,11 @@ describe('lib/optimizely', function() { flagKey: flagKey, userContext: user, reasons: [], - } + }; assert.deepEqual(decision, expectedDecision); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledTwice(optlyInstance.notificationCenter.sendNotifications); - var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(1).args; + sinon.assert.calledTwice(notificationCenter.sendNotifications); + var notificationCallArgs = notificationCenter.sendNotifications.getCall(1).args; var expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -4795,24 +4654,38 @@ describe('lib/optimizely', function() { decisionInfo: { flagKey: 'feature_2', enabled: true, - ruleKey: "exp_no_audience", - variationKey: "variation_with_traffic", + ruleKey: 'exp_no_audience', + variationKey: 'variation_with_traffic', variables: { i_42: 42 }, decisionEventDispatched: false, reasons: [], + experimentId: '10420810910', + variationId: '10418551353', }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); it('should make a decision with excluded variables and do not dispatch an event with DISABLE_DECISION_EVENT and EXCLUDE_VARIABLES passed in decide options', function() { + const { optlyInstance, eventDispatcher } = getOptlyInstance( + { + datafileObj: testData.getTestDecideProjectConfig(), + } + ); + + const notificationCenter = optlyInstance.notificationCenter; + sinon.stub(notificationCenter, 'sendNotifications'); + var flagKey = 'feature_2'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, }); - var decision = optlyInstance.decide(user, flagKey, [ OptimizelyDecideOption.DISABLE_DECISION_EVENT, OptimizelyDecideOption.EXCLUDE_VARIABLES ]); + var decision = optlyInstance.decide(user, flagKey, [ + OptimizelyDecideOption.DISABLE_DECISION_EVENT, + OptimizelyDecideOption.EXCLUDE_VARIABLES, + ]); var expectedDecision = { variationKey: 'variation_with_traffic', enabled: true, @@ -4821,11 +4694,11 @@ describe('lib/optimizely', function() { flagKey: flagKey, userContext: user, reasons: [], - } + }; assert.deepEqual(decision, expectedDecision); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledOnce(optlyInstance.notificationCenter.sendNotifications); - var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(0).args; + sinon.assert.calledOnce(notificationCenter.sendNotifications); + var notificationCallArgs = notificationCenter.sendNotifications.getCall(0).args; var expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -4836,17 +4709,28 @@ describe('lib/optimizely', function() { flagKey: 'feature_2', enabled: true, ruleKey: 'exp_no_audience', - variationKey: "variation_with_traffic", + variationKey: 'variation_with_traffic', variables: {}, decisionEventDispatched: false, reasons: [], + experimentId: '10420810910', + variationId: '10418551353', }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); it('should make a decision for rollout and dispatch an event when sendFlagDecisions is set to true', function() { + const { optlyInstance, eventDispatcher } = getOptlyInstance( + { + datafileObj: testData.getTestDecideProjectConfig(), + } + ); + + const notificationCenter = optlyInstance.notificationCenter; + sinon.stub(notificationCenter, 'sendNotifications'); + var flagKey = 'feature_1'; var expectedVariables = optlyInstance.getAllFeatureVariables(flagKey, userId); var user = new OptimizelyUserContext({ @@ -4862,11 +4746,11 @@ describe('lib/optimizely', function() { flagKey: flagKey, userContext: user, reasons: [], - } + }; assert.deepEqual(decision, expectedDecision); sinon.assert.calledOnce(eventDispatcher.dispatchEvent); - sinon.assert.callCount(optlyInstance.notificationCenter.sendNotifications, 4); - var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(3).args; + sinon.assert.callCount(notificationCenter.sendNotifications, 4); + var notificationCallArgs = notificationCenter.sendNotifications.getCall(3).args; var expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -4881,16 +4765,27 @@ describe('lib/optimizely', function() { variables: expectedVariables, decisionEventDispatched: true, reasons: [], + experimentId: '18322080788', + variationId: '18257766532', }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); it('should make a decision for rollout and do not dispatch an event when sendFlagDecisions is set to false', function() { + const { optlyInstance, eventDispatcher } = getOptlyInstance( + { + datafileObj: testData.getTestDecideProjectConfig(), + } + ); + + const notificationCenter = optlyInstance.notificationCenter; + sinon.stub(notificationCenter, 'sendNotifications'); + var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.sendFlagDecisions = false; - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var flagKey = 'feature_1'; var expectedVariables = optlyInstance.getAllFeatureVariables(flagKey, userId); var user = new OptimizelyUserContext({ @@ -4906,11 +4801,11 @@ describe('lib/optimizely', function() { flagKey: flagKey, userContext: user, reasons: [], - } + }; assert.deepEqual(decision, expectedDecision); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledTwice(optlyInstance.notificationCenter.sendNotifications); - var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(1).args; + sinon.assert.calledTwice(notificationCenter.sendNotifications); + var notificationCallArgs = notificationCenter.sendNotifications.getCall(1).args; var expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -4925,13 +4820,24 @@ describe('lib/optimizely', function() { variables: expectedVariables, decisionEventDispatched: false, reasons: [], + experimentId: '18322080788', + variationId: '18257766532', }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); it('should make a decision when variation is null and dispatch an event', function() { + const { optlyInstance, eventDispatcher } = getOptlyInstance( + { + datafileObj: testData.getTestDecideProjectConfig(), + } + ); + + const notificationCenter = optlyInstance.notificationCenter; + sinon.stub(notificationCenter, 'sendNotifications'); + var flagKey = 'feature_3'; var expectedVariables = optlyInstance.getAllFeatureVariables(flagKey, userId); var user = new OptimizelyUserContext({ @@ -4947,11 +4853,11 @@ describe('lib/optimizely', function() { flagKey: flagKey, userContext: user, reasons: [], - } + }; assert.deepEqual(decision, expectedDecision); sinon.assert.calledOnce(eventDispatcher.dispatchEvent); - sinon.assert.callCount(optlyInstance.notificationCenter.sendNotifications, 4); - var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(3).args; + sinon.assert.callCount(notificationCenter.sendNotifications, 4); + var notificationCallArgs = notificationCenter.sendNotifications.getCall(3).args; var expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -4966,47 +4872,29 @@ describe('lib/optimizely', function() { variables: expectedVariables, decisionEventDispatched: true, reasons: [], + experimentId: null, + variationId: null, }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); }); describe('with EXCLUDE_VARIABLES flag in default decide options', function() { - beforeEach(function() { - optlyInstance = new Optimizely({ - clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - isValidInstance: true, - eventBatchSize: 1, - defaultDecideOptions: [ OptimizelyDecideOption.EXCLUDE_VARIABLES ], - eventProcessor, - notificationCenter, + it('should exclude variables in decision object and dispatch an event', function() { + const { optlyInstance, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + defaultDecideOptions: [OptimizelyDecideOption.EXCLUDE_VARIABLES], }); - sinon.stub(optlyInstance.notificationCenter, 'sendNotifications'); - sinon.stub(errorHandler, 'handleError'); - sinon.stub(createdLogger, 'log'); - sinon.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); - }); - - afterEach(function() { - optlyInstance.notificationCenter.sendNotifications.restore(); - errorHandler.handleError.restore(); - createdLogger.log.restore(); - fns.uuid.restore(); - }); + const notificationCenter = optlyInstance.notificationCenter; + sinon.stub(notificationCenter, 'sendNotifications'); - it('should exclude variables in decision object and dispatch an event', function() { var flagKey = 'feature_2'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); var decision = optlyInstance.decide(user, flagKey); var expectedDecisionObj = { @@ -5017,11 +4905,11 @@ describe('lib/optimizely', function() { flagKey: flagKey, userContext: user, reasons: [], - } + }; assert.deepEqual(decision, expectedDecisionObj); sinon.assert.calledOnce(eventDispatcher.dispatchEvent); - sinon.assert.calledThrice(optlyInstance.notificationCenter.sendNotifications); - var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(2).args; + sinon.assert.calledThrice(notificationCenter.sendNotifications); + var notificationCallArgs = notificationCenter.sendNotifications.getCall(2).args; var expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -5036,19 +4924,29 @@ describe('lib/optimizely', function() { variables: {}, decisionEventDispatched: true, reasons: [], + experimentId: "10420810910", + variationId: "10418551353", }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); it('should exclude variables in decision object and do not dispatch an event when DISABLE_DECISION_EVENT is passed in decide options', function() { + const { optlyInstance, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + defaultDecideOptions: [OptimizelyDecideOption.EXCLUDE_VARIABLES], + }) + + const notificationCenter = optlyInstance.notificationCenter; + sinon.stub(notificationCenter, 'sendNotifications'); + var flagKey = 'feature_2'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); - var decision = optlyInstance.decide(user, flagKey, [ OptimizelyDecideOption.DISABLE_DECISION_EVENT ]); + var decision = optlyInstance.decide(user, flagKey, [OptimizelyDecideOption.DISABLE_DECISION_EVENT]); var expectedDecisionObj = { variationKey: 'variation_with_traffic', enabled: true, @@ -5057,11 +4955,11 @@ describe('lib/optimizely', function() { flagKey: flagKey, userContext: user, reasons: [], - } + }; assert.deepEqual(decision, expectedDecisionObj); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledOnce(optlyInstance.notificationCenter.sendNotifications); - var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(0).args; + sinon.assert.calledOnce(notificationCenter.sendNotifications); + var notificationCallArgs = notificationCenter.sendNotifications.getCall(0).args; var expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -5076,25 +4974,31 @@ describe('lib/optimizely', function() { variables: {}, decisionEventDispatched: false, reasons: [], + experimentId: '10420810910', + variationId: '10418551353', }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); }); describe('with DISABLE_DECISION_EVENT flag in default decide options', function() { beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + projectConfigManager: mockConfigManager, + + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, eventBatchSize: 1, - defaultDecideOptions: [ OptimizelyDecideOption.DISABLE_DECISION_EVENT ], + defaultDecideOptions: [OptimizelyDecideOption.DISABLE_DECISION_EVENT], notificationCenter, eventProcessor, }); @@ -5108,10 +5012,10 @@ describe('lib/optimizely', function() { it('should make a decision and do not dispatch an event', function() { var flagKey = 'feature_2'; - var expectedVariables= optlyInstance.getAllFeatureVariables(flagKey, userId); + var expectedVariables = optlyInstance.getAllFeatureVariables(flagKey, userId); var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); var decision = optlyInstance.decide(user, flagKey); var expectedDecisionObj = { @@ -5122,7 +5026,7 @@ describe('lib/optimizely', function() { flagKey: flagKey, userContext: user, reasons: [], - } + }; assert.deepEqual(decision, expectedDecisionObj); sinon.assert.notCalled(eventDispatcher.dispatchEvent); sinon.assert.calledTwice(optlyInstance.notificationCenter.sendNotifications); @@ -5141,25 +5045,31 @@ describe('lib/optimizely', function() { variables: expectedVariables, decisionEventDispatched: false, reasons: [], + experimentId: '10420810910', + variationId: '10418551353', }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); }); describe('with INCLUDE_REASONS flag in default decide options', function() { beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + projectConfigManager: mockConfigManager, + + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, eventBatchSize: 1, - defaultDecideOptions: [ OptimizelyDecideOption.INCLUDE_REASONS ], + defaultDecideOptions: [OptimizelyDecideOption.INCLUDE_REASONS], notificationCenter, eventProcessor, }); @@ -5168,25 +5078,22 @@ describe('lib/optimizely', function() { }); afterEach(function() { + eventDispatcher.dispatchEvent.reset(); optlyInstance.notificationCenter.sendNotifications.restore(); }); it('should include reason when experiment is not running', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); - newConfig.experiments[0].status = "NotRunning"; - optlyInstance.projectConfigManager.getConfig.returns(newConfig); - var flagKey = "feature_1"; + newConfig.experiments[0].status = 'NotRunning'; + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); + var flagKey = 'feature_1'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.EXPERIMENT_NOT_RUNNING, - 'DECISION_SERVICE', - 'exp_with_audience' - ) + sprintf(EXPERIMENT_NOT_RUNNING, 'exp_with_audience') ); }); @@ -5199,40 +5106,39 @@ describe('lib/optimizely', function() { lookup: sinon.stub().returns({ user_id: userId, experiment_bucket_map: { - '10420810910': { // "exp_no_audience" + '10420810910': { + // "exp_no_audience" variation_id: variationId2, }, }, }), - save: sinon.stub() + save: sinon.stub(), }; + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }); + var optlyInstanceWithUserProfile = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), - errorHandler: errorHandler, + projectConfigManager: mockConfigManager, + eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, userProfileService: mockUserProfileServiceInstance, logger: createdLogger, isValidInstance: true, eventBatchSize: 1, - defaultDecideOptions: [ OptimizelyDecideOption.INCLUDE_REASONS ], + defaultDecideOptions: [OptimizelyDecideOption.INCLUDE_REASONS], notificationCenter, eventProcessor, }); var user = new OptimizelyUserContext({ optimizely: optlyInstanceWithUserProfile, - userId + userId, }); var decision = optlyInstanceWithUserProfile.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.RETURNING_STORED_VARIATION, - 'DECISION_SERVICE', - variationKey2, - experimentKey, - userId - ) + sprintf(RETURNING_STORED_VARIATION, variationKey2, experimentKey, userId) ); }); @@ -5241,19 +5147,14 @@ describe('lib/optimizely', function() { var variationKey = 'b'; var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[0].forcedVariations[userId] = variationKey; - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.USER_FORCED_IN_VARIATION, - 'DECISION_SERVICE', - userId, - variationKey - ) + sprintf(USER_FORCED_IN_VARIATION, userId, variationKey) ); }); @@ -5264,20 +5165,14 @@ describe('lib/optimizely', function() { optlyInstance.decisionService.forcedVariationMap[userId] = { '10390977673': variationKey }; var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.variationIdMap[variationKey] = { key: variationKey }; - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.USER_HAS_FORCED_VARIATION, - 'DECISION_SERVICE', - variationKey, - experimentKey, - userId - ) + sprintf(USER_HAS_FORCED_VARIATION, variationKey, experimentKey, userId) ); }); @@ -5286,19 +5181,15 @@ describe('lib/optimizely', function() { var variationKey = 'invalid-key'; var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[0].forcedVariations[userId] = variationKey; - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); var decision = optlyInstance.decide(user, flagKey); + expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.FORCED_BUCKETING_FAILED, - 'DECISION_SERVICE', - variationKey, - userId - ) + sprintf(FORCED_BUCKETING_FAILED, variationKey, userId) ); }); @@ -5306,17 +5197,12 @@ describe('lib/optimizely', function() { var flagKey = 'feature_1'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); user.setAttribute('country', 'US'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, - 'DECISION_SERVICE', - userId, - '1' - ) + sprintf(USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, userId, '1') ); }); @@ -5324,17 +5210,12 @@ describe('lib/optimizely', function() { var flagKey = 'feature_1'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); user.setAttribute('country', 'CA'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, - 'DECISION_SERVICE', - userId, - '1' - ) + sprintf(USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, userId, '1') ); }); @@ -5342,17 +5223,12 @@ describe('lib/optimizely', function() { var flagKey = 'feature_1'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); user.setAttribute('country', 'US'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.USER_IN_ROLLOUT, - 'DECISION_SERVICE', - userId, - flagKey - ) + sprintf(USER_IN_ROLLOUT, userId, flagKey) ); }); @@ -5360,17 +5236,12 @@ describe('lib/optimizely', function() { var flagKey = 'feature_1'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); user.setAttribute('country', 'KO'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, - 'DECISION_SERVICE', - userId, - 'Everyone Else' - ) + sprintf(USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, userId, 'Everyone Else') ); }); @@ -5378,17 +5249,12 @@ describe('lib/optimizely', function() { var flagKey = 'feature_1'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); user.setAttribute('browser', 'safari'); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.USER_NOT_BUCKETED_INTO_TARGETING_RULE, - 'DECISION_SERVICE', - userId, - '2' - ) + sprintf(USER_NOT_BUCKETED_INTO_TARGETING_RULE, userId, '2') ); }); @@ -5398,17 +5264,11 @@ describe('lib/optimizely', function() { var variationKey = 'variation_with_traffic'; var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.USER_HAS_VARIATION, - 'DECISION_SERVICE', - userId, - variationKey, - experimentKey, - ) + sprintf(USER_HAS_VARIATION, userId, variationKey, experimentKey) ); }); @@ -5417,21 +5277,16 @@ describe('lib/optimizely', function() { var experimentKey = 'exp_no_audience'; var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[1].trafficAllocation = []; - newConfig.experiments[1].trafficAllocation.push({ endOfRange: 0, entityId: 'any' }) - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + newConfig.experiments[1].trafficAllocation.push({ endOfRange: 0, entityId: 'any' }); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, - attributes: { 'age': 25 }, + attributes: { age: 25 }, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.USER_HAS_NO_VARIATION, - 'DECISION_SERVICE', - userId, - experimentKey, - ) + sprintf(USER_HAS_NO_VARIATION, userId, experimentKey) ); }); @@ -5442,20 +5297,15 @@ describe('lib/optimizely', function() { var groupId = '13142870430'; var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.featureFlags[2].experimentIds.push(experimentId); - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, }); var decision = optlyInstance.decide(user, flagKey); + expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, - 'BUCKETER', - userId, - experimentKey, - groupId - ) + sprintf(USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP, userId, experimentKey, groupId) ); }); @@ -5463,18 +5313,14 @@ describe('lib/optimizely', function() { var flagKey = 'feature_3'; var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.groups[0].trafficAllocation = []; - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.FEATURE_HAS_NO_EXPERIMENTS, - 'DECISION_SERVICE', - flagKey - ) + sprintf(FEATURE_HAS_NO_EXPERIMENTS, flagKey) ); }); @@ -5487,12 +5333,7 @@ describe('lib/optimizely', function() { }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( - sprintf( - LOG_MESSAGES.USER_NOT_IN_EXPERIMENT, - 'DECISION_SERVICE', - userId, - experimentKey - ) + sprintf(USER_NOT_IN_EXPERIMENT, userId, experimentKey) ); }); @@ -5503,7 +5344,7 @@ describe('lib/optimizely', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[0].audienceIds = []; newConfig.experiments[0].audienceIds.push(audienceId); - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, @@ -5511,8 +5352,7 @@ describe('lib/optimizely', function() { var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( sprintf( - LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', + AUDIENCE_EVALUATION_RESULT_COMBINED, 'experiment', experimentKey, 'FALSE' @@ -5527,17 +5367,16 @@ describe('lib/optimizely', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[0].audienceIds = []; newConfig.experiments[0].audienceIds.push(audienceId); - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, - attirutes: { 'country': 25 } + attirutes: { country: 25 }, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( sprintf( - LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', + AUDIENCE_EVALUATION_RESULT_COMBINED, 'experiment', experimentKey, 'FALSE' @@ -5552,17 +5391,16 @@ describe('lib/optimizely', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[0].audienceIds = []; newConfig.experiments[0].audienceIds.push(audienceId); - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, - attirutes: { 'age': 10000 } + attirutes: { age: 10000 }, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( sprintf( - LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', + AUDIENCE_EVALUATION_RESULT_COMBINED, 'experiment', experimentKey, 'FALSE' @@ -5577,17 +5415,16 @@ describe('lib/optimizely', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[0].audienceIds = []; newConfig.experiments[0].audienceIds.push(audienceId); - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, - attirutes: { 'age': 25 } + attirutes: { age: 25 }, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( sprintf( - LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', + AUDIENCE_EVALUATION_RESULT_COMBINED, 'experiment', experimentKey, 'FALSE' @@ -5602,17 +5439,16 @@ describe('lib/optimizely', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[0].audienceIds = []; newConfig.experiments[0].audienceIds.push(audienceId); - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, - attirutes: { 'age': 25 } + attirutes: { age: 25 }, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( sprintf( - LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', + AUDIENCE_EVALUATION_RESULT_COMBINED, 'experiment', experimentKey, 'FALSE' @@ -5627,17 +5463,16 @@ describe('lib/optimizely', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[0].audienceIds = []; newConfig.experiments[0].audienceIds.push(audienceId); - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, - attirutes: { 'age': 25 } + attirutes: { age: 25 }, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( sprintf( - LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', + AUDIENCE_EVALUATION_RESULT_COMBINED, 'experiment', experimentKey, 'FALSE' @@ -5652,17 +5487,16 @@ describe('lib/optimizely', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[0].audienceIds = []; newConfig.experiments[0].audienceIds.push(audienceId); - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, userId, - attirutes: { 'age': 25 } + attirutes: { age: 25 }, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( sprintf( - LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', + AUDIENCE_EVALUATION_RESULT_COMBINED, 'experiment', experimentKey, 'FALSE' @@ -5677,16 +5511,15 @@ describe('lib/optimizely', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.experiments[0].audienceIds = []; newConfig.experiments[0].audienceIds.push(audienceId); - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var user = new OptimizelyUserContext({ optimizely: optlyInstance, - userId + userId, }); var decision = optlyInstance.decide(user, flagKey); expect(decision.reasons).to.include( sprintf( - LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT_COMBINED, - 'DECISION_SERVICE', + AUDIENCE_EVALUATION_RESULT_COMBINED, 'experiment', experimentKey, 'FALSE' @@ -5699,7 +5532,7 @@ describe('lib/optimizely', function() { var mockUserProfileServiceInstance; var optlyInstanceWithUserProfile; it('should bucket if there was no previously bucketed variation and save bucketing decision to the user profile', function() { - var flagKey = 'feature_2'; // embedding experiment: 'exp_no_audience' + var flagKey = 'feature_2'; // embedding experiment: 'exp_no_audience' var variationId1 = '10418551353'; var variationKey1 = 'variation_with_traffic'; mockUserProfileServiceInstance = { @@ -5707,12 +5540,16 @@ describe('lib/optimizely', function() { user_id: userId, experiment_bucket_map: {}, }), - save: sinon.stub() + save: sinon.stub(), }; + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }); + optlyInstanceWithUserProfile = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), - errorHandler: errorHandler, + projectConfigManager: mockConfigManager, + eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, userProfileService: mockUserProfileServiceInstance, @@ -5724,7 +5561,7 @@ describe('lib/optimizely', function() { }); var user = new OptimizelyUserContext({ optimizely: optlyInstanceWithUserProfile, - userId + userId, }); var decision1 = optlyInstanceWithUserProfile.decide(user, flagKey); // should return variationId1 as no stored variation exists @@ -5735,7 +5572,7 @@ describe('lib/optimizely', function() { describe('with IGNORE_USER_PROFILE_SERVICE flag in decide options', function() { it('should bypass user profile service', function() { - var flagKey = 'feature_2'; // embedding experiment: 'exp_no_audience' + var flagKey = 'feature_2'; // embedding experiment: 'exp_no_audience' var variationId1 = '10418551353'; var variationId2 = '10418510624'; var variationKey1 = 'variation_with_traffic'; @@ -5744,17 +5581,22 @@ describe('lib/optimizely', function() { lookup: sinon.stub().returns({ user_id: userId, experiment_bucket_map: { - '10420810910': { // 'exp_no_audience' + '10420810910': { + // 'exp_no_audience' variation_id: variationId2, }, }, }), - save: sinon.stub() + save: sinon.stub(), }; + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }); + optlyInstanceWithUserProfile = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), - errorHandler: errorHandler, + projectConfigManager: mockConfigManager, + eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, userProfileService: mockUserProfileServiceInstance, @@ -5766,12 +5608,14 @@ describe('lib/optimizely', function() { }); var user = new OptimizelyUserContext({ optimizely: optlyInstanceWithUserProfile, - userId + userId, }); var decision1 = optlyInstanceWithUserProfile.decide(user, flagKey); // should return variationId2 set by UPS assert.equal(variationKey2, decision1.variationKey); - var decision2 = optlyInstanceWithUserProfile.decide(user, flagKey, [ OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE ]); + var decision2 = optlyInstanceWithUserProfile.decide(user, flagKey, [ + OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE, + ]); // should ignore variationId2 set by UPS and return variationId1 assert.equal(variationKey1, decision2.variationKey); // also should not save either @@ -5781,37 +5625,42 @@ describe('lib/optimizely', function() { describe('with IGNORE_USER_PROFILE_SERVICE flag in default decide options', function() { it('should bypass user profile service', function() { - var flagKey = 'feature_2'; // embedding experiment: 'exp_no_audience' + var flagKey = 'feature_2'; // embedding experiment: 'exp_no_audience' var variationId2 = '10418510624'; var variationKey1 = 'variation_with_traffic'; mockUserProfileServiceInstance = { lookup: sinon.stub().returns({ user_id: userId, experiment_bucket_map: { - '10420810910': { // 'exp_no_audience' + '10420810910': { + // 'exp_no_audience' variation_id: variationId2, }, }, }), - save: sinon.stub() + save: sinon.stub(), }; + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }); + optlyInstanceWithUserProfile = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), - errorHandler: errorHandler, + projectConfigManager: mockConfigManager, + eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, userProfileService: mockUserProfileServiceInstance, logger: createdLogger, isValidInstance: true, eventBatchSize: 1, - defaultDecideOptions: [ OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE ], + defaultDecideOptions: [OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE], notificationCenter, eventProcessor, }); var user = new OptimizelyUserContext({ optimizely: optlyInstanceWithUserProfile, - userId + userId, }); var decision = optlyInstanceWithUserProfile.decide(user, flagKey); // should ignore variationId2 set by UPS and return variationId1 @@ -5823,35 +5672,17 @@ describe('lib/optimizely', function() { }); }); + describe('#decideForKeys', function() { var userId = 'tester'; - beforeEach(function() { - optlyInstance = new Optimizely({ - clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - isValidInstance: true, - eventBatchSize: 1, - defaultDecideOptions: [], - notificationCenter, - eventProcessor, - }); - - sinon.stub(optlyInstance.notificationCenter, 'sendNotifications'); - }); - - afterEach(function() { - optlyInstance.notificationCenter.sendNotifications.restore(); - }); - it('should return decision results map with single flag key provided for feature_test and dispatch an event', function() { var flagKey = 'feature_2'; + const { optlyInstance, eventDispatcher } = getOptlyInstance({ datafileObj: testData.getTestDecideProjectConfig() }); + sinon.stub(optlyInstance.notificationCenter, 'sendNotifications'); var user = optlyInstance.createUserContext(userId); var expectedVariables = optlyInstance.getAllFeatureVariables(flagKey, userId); - var decisionsMap = optlyInstance.decideForKeys(user, [ flagKey ]); + + var decisionsMap = optlyInstance.decideForKeys(user, [flagKey]); var decision = decisionsMap[flagKey]; var expectedDecision = { variationKey: 'variation_with_traffic', @@ -5861,11 +5692,11 @@ describe('lib/optimizely', function() { flagKey: flagKey, userContext: user, reasons: [], - } + }; assert.deepEqual(Object.values(decisionsMap).length, 1); assert.deepEqual(decision, expectedDecision); sinon.assert.calledOnce(eventDispatcher.dispatchEvent); - sinon.assert.callCount(optlyInstance.notificationCenter.sendNotifications, 4) + sinon.assert.callCount(optlyInstance.notificationCenter.sendNotifications, 4); var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(3).args; var decisionEventDispatched = notificationCallArgs[1].decisionInfo.decisionEventDispatched; assert.deepEqual(decisionEventDispatched, true); @@ -5873,7 +5704,9 @@ describe('lib/optimizely', function() { it('should return decision results map with two flag keys provided and dispatch events', function() { var flagKeysArray = ['feature_1', 'feature_2']; + const { optlyInstance, eventDispatcher } = getOptlyInstance({ datafileObj: testData.getTestDecideProjectConfig() }); var user = optlyInstance.createUserContext(userId); + var expectedVariables1 = optlyInstance.getAllFeatureVariables(flagKeysArray[0], userId); var expectedVariables2 = optlyInstance.getAllFeatureVariables(flagKeysArray[1], userId); var decisionsMap = optlyInstance.decideForKeys(user, flagKeysArray); @@ -5887,7 +5720,7 @@ describe('lib/optimizely', function() { flagKey: flagKeysArray[0], userContext: user, reasons: [], - } + }; var expectedDecision2 = { variationKey: 'variation_with_traffic', enabled: true, @@ -5896,7 +5729,7 @@ describe('lib/optimizely', function() { flagKey: flagKeysArray[1], userContext: user, reasons: [], - } + }; assert.deepEqual(Object.values(decisionsMap).length, 2); assert.deepEqual(decision1, expectedDecision1); assert.deepEqual(decision2, expectedDecision2); @@ -5906,9 +5739,14 @@ describe('lib/optimizely', function() { it('should return decision results map with only enabled flags when ENABLED_FLAGS_ONLY flag is passed in and dispatch events', function() { var flagKey1 = 'feature_2'; var flagKey2 = 'feature_3'; + const { optlyInstance, eventDispatcher } = getOptlyInstance({ datafileObj: testData.getTestDecideProjectConfig() }); var user = optlyInstance.createUserContext(userId, { gender: 'female' }); var expectedVariables = optlyInstance.getAllFeatureVariables(flagKey1, userId); - var decisionsMap = optlyInstance.decideForKeys(user, [ flagKey1, flagKey2 ], [ OptimizelyDecideOption.ENABLED_FLAGS_ONLY ]); + var decisionsMap = optlyInstance.decideForKeys( + user, + [flagKey1, flagKey2], + [OptimizelyDecideOption.ENABLED_FLAGS_ONLY] + ); var decision = decisionsMap[flagKey1]; var expectedDecision = { variationKey: 'variation_with_traffic', @@ -5918,21 +5756,25 @@ describe('lib/optimizely', function() { flagKey: flagKey1, userContext: user, reasons: [], - } + }; assert.deepEqual(Object.values(decisionsMap).length, 1); assert.deepEqual(decision, expectedDecision); sinon.assert.calledTwice(eventDispatcher.dispatchEvent); }); - }); - - describe('#decideAll', function() { - var userId = 'tester'; - describe('with empty default decide options', function() { + describe('UPS Batching', function() { + var userProfileServiceInstance = { + lookup: function() {}, + save: function() {}, + }; beforeEach(function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', datafile: testData.getTestDecideProjectConfig(), - errorHandler: errorHandler, + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }), + userProfileService: userProfileServiceInstance, + eventDispatcher: eventDispatcher, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, @@ -5943,16 +5785,70 @@ describe('lib/optimizely', function() { eventProcessor, }); - sinon.stub(optlyInstance.notificationCenter, 'sendNotifications'); + sinon.stub(optlyInstance.decisionService.userProfileService, 'lookup') + sinon.stub(optlyInstance.decisionService.userProfileService, 'save') + // }); - afterEach(function() { - optlyInstance.notificationCenter.sendNotifications.restore(); + it('Should call UPS methods only once', function() { + var flagKeysArray = ['feature_1', 'feature_2']; + var user = optlyInstance.createUserContext(userId); + var expectedVariables1 = optlyInstance.getAllFeatureVariables(flagKeysArray[0], userId); + var expectedVariables2 = optlyInstance.getAllFeatureVariables(flagKeysArray[1], userId); + optlyInstance.decisionService.userProfileService.save.resetHistory(); + optlyInstance.decisionService.userProfileService.lookup.resetHistory(); + var decisionsMap = optlyInstance.decideForKeys(user, flagKeysArray); + var decision1 = decisionsMap[flagKeysArray[0]]; + var decision2 = decisionsMap[flagKeysArray[1]]; + var expectedDecision1 = { + variationKey: '18257766532', + enabled: true, + variables: expectedVariables1, + ruleKey: '18322080788', + flagKey: flagKeysArray[0], + userContext: user, + reasons: [], + }; + var expectedDecision2 = { + variationKey: 'variation_with_traffic', + enabled: true, + variables: expectedVariables2, + ruleKey: 'exp_no_audience', + flagKey: flagKeysArray[1], + userContext: user, + reasons: [], + }; + var userProfile = { + user_id: userId, + experiment_bucket_map: { + '10420810910': { // ruleKey from expectedDecision1 + variation_id: '10418551353' // variationKey from expectedDecision1 + } + } + }; + + assert.deepEqual(Object.values(decisionsMap).length, 2); + assert.deepEqual(decision1, expectedDecision1); + assert.deepEqual(decision2, expectedDecision2); + // UPS batch assertion + sinon.assert.calledOnce(optlyInstance.decisionService.userProfileService.lookup); + sinon.assert.calledOnce(optlyInstance.decisionService.userProfileService.save); + + // UPS save assertion + sinon.assert.calledWithExactly(optlyInstance.decisionService.userProfileService.save, userProfile); }); + }) + + }); + + describe('#decideAll', function() { + var userId = 'tester'; + describe('with empty default decide options', function() { it('should return decision results map with all flag keys provided and dispatch events', function() { + const { optlyInstance, eventDispatcher } = getOptlyInstance({ datafileObj: testData.getTestDecideProjectConfig() }); var configObj = optlyInstance.projectConfigManager.getConfig(); - var allFlagKeysArray = Object.keys(configObj.featureKeyMap); + var allFlagKeysArray = Object.keys(configObj.featureKeyMap); var user = optlyInstance.createUserContext(userId); var expectedVariables1 = optlyInstance.getAllFeatureVariables(allFlagKeysArray[0], userId); var expectedVariables2 = optlyInstance.getAllFeatureVariables(allFlagKeysArray[1], userId); @@ -5969,7 +5865,7 @@ describe('lib/optimizely', function() { flagKey: allFlagKeysArray[0], userContext: user, reasons: [], - } + }; var expectedDecision2 = { variationKey: 'variation_with_traffic', enabled: true, @@ -5978,7 +5874,7 @@ describe('lib/optimizely', function() { flagKey: allFlagKeysArray[1], userContext: user, reasons: [], - } + }; var expectedDecision3 = { variationKey: null, enabled: false, @@ -5987,7 +5883,7 @@ describe('lib/optimizely', function() { flagKey: allFlagKeysArray[2], userContext: user, reasons: [], - } + }; assert.deepEqual(Object.values(decisionsMap).length, allFlagKeysArray.length); assert.deepEqual(decision1, expectedDecision1); assert.deepEqual(decision2, expectedDecision2); @@ -5998,10 +5894,11 @@ describe('lib/optimizely', function() { it('should return decision results map with only enabled flags when ENABLED_FLAGS_ONLY flag is passed in and dispatch events', function() { var flagKey1 = 'feature_1'; var flagKey2 = 'feature_2'; + const { optlyInstance, eventDispatcher } = getOptlyInstance({ datafileObj: testData.getTestDecideProjectConfig() }); var user = optlyInstance.createUserContext(userId, { gender: 'female' }); var expectedVariables1 = optlyInstance.getAllFeatureVariables(flagKey1, userId); var expectedVariables2 = optlyInstance.getAllFeatureVariables(flagKey2, userId); - var decisionsMap = optlyInstance.decideAll(user, [ OptimizelyDecideOption.ENABLED_FLAGS_ONLY ]); + var decisionsMap = optlyInstance.decideAll(user, [OptimizelyDecideOption.ENABLED_FLAGS_ONLY]); var decision1 = decisionsMap[flagKey1]; var decision2 = decisionsMap[flagKey2]; var expectedDecision1 = { @@ -6012,7 +5909,7 @@ describe('lib/optimizely', function() { flagKey: flagKey1, userContext: user, reasons: [], - } + }; var expectedDecision2 = { variationKey: 'variation_with_traffic', enabled: true, @@ -6021,7 +5918,7 @@ describe('lib/optimizely', function() { flagKey: flagKey2, userContext: user, reasons: [], - } + }; assert.deepEqual(Object.values(decisionsMap).length, 2); assert.deepEqual(decision1, expectedDecision1); assert.deepEqual(decision2, expectedDecision2); @@ -6030,31 +5927,13 @@ describe('lib/optimizely', function() { }); describe('with ENABLED_FLAGS_ONLY flag in default decide options', function() { - beforeEach(function() { - optlyInstance = new Optimizely({ - clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - isValidInstance: true, - eventBatchSize: 1, - defaultDecideOptions: [ OptimizelyDecideOption.ENABLED_FLAGS_ONLY ], - eventProcessor, - notificationCenter, - }); - - sinon.stub(optlyInstance.notificationCenter, 'sendNotifications'); - }); - - afterEach(function() { - optlyInstance.notificationCenter.sendNotifications.restore(); - }); - it('should return decision results map with only enabled flags and dispatch events', function() { var flagKey1 = 'feature_1'; var flagKey2 = 'feature_2'; + const { optlyInstance, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + defaultDecideOptions: [OptimizelyDecideOption.ENABLED_FLAGS_ONLY] + }); var user = optlyInstance.createUserContext(userId, { gender: 'female' }); var expectedVariables1 = optlyInstance.getAllFeatureVariables(flagKey1, userId); var expectedVariables2 = optlyInstance.getAllFeatureVariables(flagKey2, userId); @@ -6069,7 +5948,7 @@ describe('lib/optimizely', function() { flagKey: flagKey1, userContext: user, reasons: [], - } + }; var expectedDecision2 = { variationKey: 'variation_with_traffic', enabled: true, @@ -6078,7 +5957,7 @@ describe('lib/optimizely', function() { flagKey: flagKey2, userContext: user, reasons: [], - } + }; assert.deepEqual(Object.values(decisionsMap).length, 2); assert.deepEqual(decision1, expectedDecision1); assert.deepEqual(decision2, expectedDecision2); @@ -6088,8 +5967,14 @@ describe('lib/optimizely', function() { it('should return decision results map with only enabled flags and excluded variables when EXCLUDE_VARIABLES_FLAG is passed in', function() { var flagKey1 = 'feature_1'; var flagKey2 = 'feature_2'; + + const { optlyInstance, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + defaultDecideOptions: [OptimizelyDecideOption.ENABLED_FLAGS_ONLY] + }); + var user = optlyInstance.createUserContext(userId, { gender: 'female' }); - var decisionsMap = optlyInstance.decideAll(user, [ OptimizelyDecideOption.EXCLUDE_VARIABLES ]); + var decisionsMap = optlyInstance.decideAll(user, [OptimizelyDecideOption.EXCLUDE_VARIABLES]); var decision1 = decisionsMap[flagKey1]; var decision2 = decisionsMap[flagKey2]; var expectedDecision1 = { @@ -6100,7 +5985,7 @@ describe('lib/optimizely', function() { flagKey: flagKey1, userContext: user, reasons: [], - } + }; var expectedDecision2 = { variationKey: 'variation_with_traffic', enabled: true, @@ -6109,35 +5994,104 @@ describe('lib/optimizely', function() { flagKey: flagKey2, userContext: user, reasons: [], - } + }; assert.deepEqual(Object.values(decisionsMap).length, 2); assert.deepEqual(decision1, expectedDecision1); assert.deepEqual(decision2, expectedDecision2); sinon.assert.calledThrice(eventDispatcher.dispatchEvent); }); }); + + describe('UPS batching', function() { + beforeEach(function() { + var userProfileServiceInstance = { + lookup: function() {}, + save: function() {}, + }; + + optlyInstance = new Optimizely({ + clientEngine: 'node-sdk', + datafile: testData.getTestDecideProjectConfig(), + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()), + }), + userProfileService: userProfileServiceInstance, + + eventDispatcher: eventDispatcher, + jsonSchemaValidator: jsonSchemaValidator, + logger: createdLogger, + isValidInstance: true, + eventBatchSize: 1, + defaultDecideOptions: [OptimizelyDecideOption.ENABLED_FLAGS_ONLY], + eventProcessor, + notificationCenter, + }); + + sinon.stub(optlyInstance.decisionService.userProfileService, 'lookup') + sinon.stub(optlyInstance.decisionService.userProfileService, 'save') + }); + + it('should call UPS methods only once', function() { + var flagKey1 = 'feature_1'; + var flagKey2 = 'feature_2'; + var user = optlyInstance.createUserContext(userId, { gender: 'female' }); + var decisionsMap = optlyInstance.decideAll(user, [OptimizelyDecideOption.EXCLUDE_VARIABLES]); + var decision1 = decisionsMap[flagKey1]; + var decision2 = decisionsMap[flagKey2]; + var expectedDecision1 = { + variationKey: '18257766532', + enabled: true, + variables: {}, + ruleKey: '18322080788', + flagKey: flagKey1, + userContext: user, + reasons: [], + }; + var expectedDecision2 = { + variationKey: 'variation_with_traffic', + enabled: true, + variables: {}, + ruleKey: 'exp_no_audience', + flagKey: flagKey2, + userContext: user, + reasons: [], + }; + + // Decision assertion + assert.deepEqual(Object.values(decisionsMap).length, 2); + assert.deepEqual(decision1, expectedDecision1); + assert.deepEqual(decision2, expectedDecision2); + + // UPS batch assertion + sinon.assert.calledOnce(optlyInstance.decisionService.userProfileService.lookup); + sinon.assert.calledOnce(optlyInstance.decisionService.userProfileService.save); + }) + }); }); }); //tests separated out from APIs because of mock bucketing describe('getVariationBucketingIdAttribute', function() { var optlyInstance; - var createdLogger = logger.createLogger({ + var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO, logToConsole: false, }); - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); - var eventProcessor = createEventProcessor({ - dispatcher: eventDispatcher, - batchSize: 1, - notificationCenter: notificationCenter, - }); + var notificationCenter = createNotificationCenter({ logger: createdLogger, }); + var eventDispatcher = getMockEventDispatcher(); + var eventProcessor = getForwardingEventProcessor( + eventDispatcher, + ); beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + projectConfigManager: mockConfigManager, + + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -6146,6 +6100,10 @@ describe('lib/optimizely', function() { }); }); + afterEach(() => { + eventDispatcher.dispatchEvent.reset(); + }) + var userAttributes = { browser_type: 'firefox', }; @@ -6182,25 +6140,30 @@ describe('lib/optimizely', function() { describe('feature management', function() { var sandbox = sinon.sandbox.create(); - var createdLogger = logger.createLogger({ + var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO, logToConsole: false, }); var optlyInstance; var fakeDecisionResponse; - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); - var eventProcessor = createEventProcessor({ - dispatcher: eventDispatcher, - batchSize: 1, - notificationCenter: notificationCenter, - }); + var notificationCenter = createNotificationCenter({ logger: createdLogger, }); + var eventDispatcher = { + dispatchEvent: () => Promise.resolve({ statusCode: 200 }), + }; + + var eventProcessor = getForwardingEventProcessor( + eventDispatcher, + ); beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + projectConfigManager: mockConfigManager, + jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -6209,7 +6172,7 @@ describe('lib/optimizely', function() { eventProcessor, }); - sandbox.stub(errorHandler, 'handleError'); + sandbox.stub(eventDispatcher, 'dispatchEvent'); sandbox.stub(createdLogger, 'log'); sandbox.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); sandbox.stub(fns, 'currentTimestamp').returns(1509489766569); @@ -6229,12 +6192,8 @@ describe('lib/optimizely', function() { it('returns false if the instance is invalid', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: { - lasers: 300, - message: 'this is not a valid datafile', - }, - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + projectConfigManager: getMockProjectConfigManager(), + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, eventProcessor, @@ -6242,10 +6201,10 @@ describe('lib/optimizely', function() { }); var result = optlyInstance.isFeatureEnabled('test_feature_for_experiment', 'user1'); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Optimizely object is not valid. Failing isFeatureEnabled.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Optimizely object is not valid. Failing isFeatureEnabled.' + // ); }); describe('when the user bucketed into a variation of an experiment with the feature', function() { @@ -6266,6 +6225,10 @@ describe('lib/optimizely', function() { }; sandbox.stub(optlyInstance.decisionService, 'getVariationForFeature').returns(fakeDecisionResponse); }); + + afterEach(() => { + eventDispatcher.dispatchEvent.reset(); + }); it('returns true and dispatches an impression event', function() { var user = optlyInstance.createUserContext('user1', attributes); @@ -6302,6 +6265,7 @@ describe('lib/optimizely', function() { rule_type: 'feature-test', variation_key: 'variation', enabled: true, + cmab_uuid: undefined, }, }, ], @@ -6334,73 +6298,72 @@ describe('lib/optimizely', function() { ], revision: '35', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: true, enrich_decisions: true, }, }; var callArgs = eventDispatcher.dispatchEvent.getCalls()[0].args; assert.deepEqual(callArgs[0], expectedImpressionEvent); - assert.isFunction(callArgs[1]); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature test_feature_for_experiment is enabled for user user1.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature test_feature_for_experiment is enabled for user user1.' + // ); }); it('returns false and does not dispatch an impression event when feature key is null', function() { var result = optlyInstance.isFeatureEnabled(null, 'user1', attributes); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.ERROR, - 'OPTIMIZELY: Provided feature_key is in an invalid format.' - ); + // sinon.assert.calledWithExactly( + // createdLogger.log, + // LOG_LEVEL.ERROR, + // 'OPTIMIZELY: Provided feature_key is in an invalid format.' + // ); }); it('returns false when user id is null', function() { var result = optlyInstance.isFeatureEnabled('test_feature_for_experiment', null, attributes); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.ERROR, - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // sinon.assert.calledWithExactly( + // createdLogger.log, + // LOG_LEVEL.ERROR, + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns false when feature key and user id are null', function() { var result = optlyInstance.isFeatureEnabled(null, null, attributes); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.ERROR, - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // sinon.assert.calledWithExactly( + // createdLogger.log, + // LOG_LEVEL.ERROR, + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns false when feature key is undefined', function() { var result = optlyInstance.isFeatureEnabled(undefined, 'user1', attributes); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.ERROR, - 'OPTIMIZELY: Provided feature_key is in an invalid format.' - ); + // sinon.assert.calledWithExactly( + // createdLogger.log, + // LOG_LEVEL.ERROR, + // 'OPTIMIZELY: Provided feature_key is in an invalid format.' + // ); }); it('returns false when user id is undefined', function() { var result = optlyInstance.isFeatureEnabled('test_feature_for_experiment', undefined, attributes); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.ERROR, - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // sinon.assert.calledWithExactly( + // createdLogger.log, + // LOG_LEVEL.ERROR, + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns false when feature key and user id are undefined', function() { @@ -6413,44 +6376,44 @@ describe('lib/optimizely', function() { var result = optlyInstance.isFeatureEnabled(); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.ERROR, - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // sinon.assert.calledWithExactly( + // createdLogger.log, + // LOG_LEVEL.ERROR, + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns false when user id is an object', function() { var result = optlyInstance.isFeatureEnabled('test_feature_for_experiment', {}, attributes); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.ERROR, - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // sinon.assert.calledWithExactly( + // createdLogger.log, + // LOG_LEVEL.ERROR, + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns false when user id is a number', function() { var result = optlyInstance.isFeatureEnabled('test_feature_for_experiment', 72, attributes); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.ERROR, - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // sinon.assert.calledWithExactly( + // createdLogger.log, + // LOG_LEVEL.ERROR, + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns false when feature key is an array', function() { var result = optlyInstance.isFeatureEnabled(['a', 'feature'], 'user1', attributes); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - sinon.assert.calledWithExactly( - createdLogger.log, - LOG_LEVEL.ERROR, - 'OPTIMIZELY: Provided feature_key is in an invalid format.' - ); + // sinon.assert.calledWithExactly( + // createdLogger.log, + // LOG_LEVEL.ERROR, + // 'OPTIMIZELY: Provided feature_key is in an invalid format.' + // ); }); it('returns true when user id is an empty string', function() { @@ -6489,6 +6452,10 @@ describe('lib/optimizely', function() { sandbox.stub(optlyInstance.decisionService, 'getVariationForFeature').returns(fakeDecisionResponse); result = optlyInstance.isFeatureEnabled('shared_feature', 'user1', attributes); }); + + afterEach(() => { + eventDispatcher.dispatchEvent.reset(); + }); it('should return false', function() { assert.strictEqual(result, false); @@ -6526,6 +6493,7 @@ describe('lib/optimizely', function() { rule_type: 'feature-test', variation_key: 'control', enabled: false, + cmab_uuid: undefined, }, }, ], @@ -6558,14 +6526,13 @@ describe('lib/optimizely', function() { ], revision: '35', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: true, enrich_decisions: true, }, }; var callArgs = eventDispatcher.dispatchEvent.getCalls()[0].args; assert.deepEqual(callArgs[0], expectedImpressionEvent); - assert.isFunction(callArgs[1]); }); }); @@ -6581,6 +6548,10 @@ describe('lib/optimizely', function() { }); }); + afterEach(() => { + eventDispatcher.dispatchEvent.reset(); + }) + it('should return false', function() { var result = optlyInstance.isFeatureEnabled('shared_feature', 'user1', attributes); var user = optlyInstance.createUserContext('user1', attributes); @@ -6621,10 +6592,10 @@ describe('lib/optimizely', function() { }); assert.strictEqual(result, true); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature test_feature is enabled for user user1.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature test_feature is enabled for user user1.' + // ); }); }); @@ -6650,10 +6621,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature test_feature is not enabled for user user1.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature test_feature is not enabled for user user1.' + // ); }); }); }); @@ -6675,33 +6646,33 @@ describe('lib/optimizely', function() { it('returns false and does not dispatch an event when sendFlagDecisions is not defined', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.sendFlagDecisions = undefined; - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var result = optlyInstance.isFeatureEnabled('test_feature', 'user1'); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature test_feature is not enabled for user user1.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature test_feature is not enabled for user user1.' + // ); }); it('returns false and does not dispatch an event when sendFlagDecisions is set to false', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.sendFlagDecisions = false; - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var result = optlyInstance.isFeatureEnabled('test_feature', 'user1'); assert.strictEqual(result, false); sinon.assert.notCalled(eventDispatcher.dispatchEvent); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature test_feature is not enabled for user user1.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature test_feature is not enabled for user user1.' + // ); }); it('returns false and dispatch an event when sendFlagDecisions is set to true', function() { var newConfig = optlyInstance.projectConfigManager.getConfig(); newConfig.sendFlagDecisions = true; - optlyInstance.projectConfigManager.getConfig.returns(newConfig); + optlyInstance.projectConfigManager.getConfig = sinon.stub().returns(newConfig); var result = optlyInstance.isFeatureEnabled('test_feature', 'user1'); assert.strictEqual(result, false); sinon.assert.calledOnce(eventDispatcher.dispatchEvent); @@ -6726,6 +6697,7 @@ describe('lib/optimizely', function() { rule_type: 'rollout', variation_key: '', enabled: false, + cmab_uuid: undefined, }, }, ], @@ -6752,7 +6724,7 @@ describe('lib/optimizely', function() { ], revision: '35', client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, + client_version: enums.CLIENT_VERSION, anonymize_ip: true, enrich_decisions: true, }, @@ -6770,15 +6742,15 @@ describe('lib/optimizely', function() { }); }); + afterEach(() => { + eventDispatcher.dispatchEvent.reset(); + }) + it('returns an empty array if the instance is invalid', function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: { - lasers: 300, - message: 'this is not a valid datafile', - }, - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + projectConfigManager: getMockProjectConfigManager(), + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, eventProcessor, @@ -6786,10 +6758,10 @@ describe('lib/optimizely', function() { }); var result = optlyInstance.getEnabledFeatures('user1', { test_attribute: 'test_value' }); assert.deepEqual(result, []); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Optimizely object is not valid. Failing getEnabledFeatures.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Optimizely object is not valid. Failing getEnabledFeatures.' + // ); }); it('returns only enabled features for the specified user and attributes', function() { @@ -6814,11 +6786,14 @@ describe('lib/optimizely', function() { }); it('return features that are enabled for the user and send notification for every feature', function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfigWithFeatures()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + projectConfigManager: mockConfigManager, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -6828,18 +6803,16 @@ describe('lib/optimizely', function() { var decisionListener = sinon.spy(); var attributes = { test_attribute: 'test_value' }; - optlyInstance.notificationCenter.addNotificationListener(enums.NOTIFICATION_TYPES.DECISION, decisionListener); + optlyInstance.notificationCenter.addNotificationListener(NOTIFICATION_TYPES.DECISION, decisionListener); var result = optlyInstance.getEnabledFeatures('test_user', attributes); assert.strictEqual(result.length, 5); - assert.deepEqual(result, - [ - 'test_feature_2', - 'test_feature_for_experiment', - 'shared_feature', - 'test_feature_in_exclusion_group', - 'test_feature_in_multiple_experiments' - ] - ); + assert.deepEqual(result, [ + 'test_feature_2', + 'test_feature_for_experiment', + 'shared_feature', + 'test_feature_in_exclusion_group', + 'test_feature_in_multiple_experiments', + ]); sinon.assert.calledWithExactly(decisionListener.getCall(0), { type: DECISION_NOTIFICATION_TYPES.FEATURE, @@ -6956,10 +6929,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, true); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "true" for variable "is_button_animated" of feature flag "test_feature_for_experiment"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "true" for variable "is_button_animated" of feature flag "test_feature_for_experiment"' + // ); }); it('returns the right value from getFeatureVariable when variable type is double', function() { @@ -6967,10 +6940,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 20.25); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "20.25" for variable "button_width" of feature flag "test_feature_for_experiment"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "20.25" for variable "button_width" of feature flag "test_feature_for_experiment"' + // ); }); it('returns the right value from getFeatureVariable when variable type is integer', function() { @@ -6978,10 +6951,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 2); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "2" for variable "num_buttons" of feature flag "test_feature_for_experiment"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "2" for variable "num_buttons" of feature flag "test_feature_for_experiment"' + // ); }); it('returns the right value from getFeatureVariable when variable type is string', function() { @@ -6989,10 +6962,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Buy me NOW'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "Buy me NOW" for variable "button_txt" of feature flag "test_feature_for_experiment"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "Buy me NOW" for variable "button_txt" of feature flag "test_feature_for_experiment"' + // ); }); it('returns the right value from getFeatureVariable when variable type is json', function() { @@ -7003,10 +6976,10 @@ describe('lib/optimizely', function() { num_buttons: 1, text: 'first variation', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "{ "num_buttons": 1, "text": "first variation"}" for variable "button_info" of feature flag "test_feature_for_experiment"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "{ "num_buttons": 1, "text": "first variation"}" for variable "button_info" of feature flag "test_feature_for_experiment"' + // ); }); it('returns the right value from getFeatureVariableBoolean', function() { @@ -7017,10 +6990,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, true); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "true" for variable "is_button_animated" of feature flag "test_feature_for_experiment"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "true" for variable "is_button_animated" of feature flag "test_feature_for_experiment"' + // ); }); it('returns the right value from getFeatureVariableDouble', function() { @@ -7031,10 +7004,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, 20.25); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "20.25" for variable "button_width" of feature flag "test_feature_for_experiment"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "20.25" for variable "button_width" of feature flag "test_feature_for_experiment"' + // ); }); it('returns the right value from getFeatureVariableInteger', function() { @@ -7045,10 +7018,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, 2); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "2" for variable "num_buttons" of feature flag "test_feature_for_experiment"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "2" for variable "num_buttons" of feature flag "test_feature_for_experiment"' + // ); }); it('returns the right value from getFeatureVariableString', function() { @@ -7056,10 +7029,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Buy me NOW'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "Buy me NOW" for variable "button_txt" of feature flag "test_feature_for_experiment"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "Buy me NOW" for variable "button_txt" of feature flag "test_feature_for_experiment"' + // ); }); it('returns the right value from getFeatureVariableJSON', function() { @@ -7070,10 +7043,10 @@ describe('lib/optimizely', function() { num_buttons: 1, text: 'first variation', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "{ "num_buttons": 1, "text": "first variation"}" for variable "button_info" of feature flag "test_feature_for_experiment"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "{ "num_buttons": 1, "text": "first variation"}" for variable "button_info" of feature flag "test_feature_for_experiment"' + // ); }); it('returns the right values from getAllFeatureVariables', function() { @@ -7090,51 +7063,51 @@ describe('lib/optimizely', function() { text: 'first variation', }, }); - sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - '2', - 'num_buttons', - 'test_feature_for_experiment', - ); - sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - 'true', - 'is_button_animated', - 'test_feature_for_experiment', - ); - sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - 'Buy me NOW', - 'button_txt', - 'test_feature_for_experiment' - ); - sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - '20.25', - 'button_width', - 'test_feature_for_experiment' - ); - sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - '{ "num_buttons": 1, "text": "first variation"}', - 'button_info', - 'test_feature_for_experiment' - ); + // sinon.assert.calledWith( + // createdLogger.log, + // LOG_LEVEL.INFO, + // '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + // 'OPTIMIZELY', + // '2', + // 'num_buttons', + // 'test_feature_for_experiment' + // ); + // sinon.assert.calledWith( + // createdLogger.log, + // LOG_LEVEL.INFO, + // '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + // 'OPTIMIZELY', + // 'true', + // 'is_button_animated', + // 'test_feature_for_experiment' + // ); + // sinon.assert.calledWith( + // createdLogger.log, + // LOG_LEVEL.INFO, + // '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + // 'OPTIMIZELY', + // 'Buy me NOW', + // 'button_txt', + // 'test_feature_for_experiment' + // ); + // sinon.assert.calledWith( + // createdLogger.log, + // LOG_LEVEL.INFO, + // '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + // 'OPTIMIZELY', + // '20.25', + // 'button_width', + // 'test_feature_for_experiment' + // ); + // sinon.assert.calledWith( + // createdLogger.log, + // LOG_LEVEL.INFO, + // '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + // 'OPTIMIZELY', + // '{ "num_buttons": 1, "text": "first variation"}', + // 'button_info', + // 'test_feature_for_experiment' + // ); }); describe('when the variable is not used in the variation', function() { @@ -7150,10 +7123,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "is_button_animated" is not used in variation "variation". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "is_button_animated" is not used in variation "variation". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is double', function() { @@ -7161,10 +7134,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 50.55); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "button_width" is not used in variation "variation". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "button_width" is not used in variation "variation". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is integer', function() { @@ -7172,10 +7145,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 10); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "num_buttons" is not used in variation "variation". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "num_buttons" is not used in variation "variation". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is string', function() { @@ -7183,10 +7156,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Buy me'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "button_txt" is not used in variation "variation". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "button_txt" is not used in variation "variation". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is json', function() { @@ -7197,10 +7170,10 @@ describe('lib/optimizely', function() { num_buttons: 0, text: 'default value', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "button_info" is not used in variation "variation". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "button_info" is not used in variation "variation". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariableBoolean', function() { @@ -7211,10 +7184,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "is_button_animated" is not used in variation "variation". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "is_button_animated" is not used in variation "variation". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariableDouble', function() { @@ -7225,10 +7198,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, 50.55); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "button_width" is not used in variation "variation". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "button_width" is not used in variation "variation". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariableInteger', function() { @@ -7239,10 +7212,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, 10); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "num_buttons" is not used in variation "variation". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "num_buttons" is not used in variation "variation". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariableString', function() { @@ -7253,27 +7226,24 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, 'Buy me'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "button_txt" is not used in variation "variation". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "button_txt" is not used in variation "variation". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariableJSON', function() { - var result = optlyInstance.getFeatureVariableJSON( - 'test_feature_for_experiment', - 'button_info', - 'user1', - { test_attribute: 'test_value' } - ); + var result = optlyInstance.getFeatureVariableJSON('test_feature_for_experiment', 'button_info', 'user1', { + test_attribute: 'test_value', + }); assert.deepEqual(result, { num_buttons: 0, - text: "default value", + text: 'default value', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "button_info" is not used in variation "variation". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "button_info" is not used in variation "variation". Returning default value.' + // ); }); it('returns the right values from getAllFeatureVariables', function() { @@ -7287,49 +7257,49 @@ describe('lib/optimizely', function() { button_txt: 'Buy me', button_info: { num_buttons: 0, - text: "default value", + text: 'default value', }, }); - sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'num_buttons', - 'variation' - ); - sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'is_button_animated', - 'variation' - ); - sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'button_txt', - 'variation' - ); - sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'button_width', - 'variation' - ); - sinon.assert.calledWith( - createdLogger.log, - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'button_info', - 'variation' - ); + // sinon.assert.calledWith( + // createdLogger.info, + // // LOG_LEVEL.INFO, + // '%s: Variable "%s" is not used in variation "%s". Returning default value.', + // 'OPTIMIZELY', + // 'num_buttons', + // 'variation' + // ); + // sinon.assert.calledWith( + // createdLogger.log, + // LOG_LEVEL.INFO, + // '%s: Variable "%s" is not used in variation "%s". Returning default value.', + // 'OPTIMIZELY', + // 'is_button_animated', + // 'variation' + // ); + // sinon.assert.calledWith( + // createdLogger.log, + // LOG_LEVEL.INFO, + // '%s: Variable "%s" is not used in variation "%s". Returning default value.', + // 'OPTIMIZELY', + // 'button_txt', + // 'variation' + // ); + // sinon.assert.calledWith( + // createdLogger.log, + // LOG_LEVEL.INFO, + // '%s: Variable "%s" is not used in variation "%s". Returning default value.', + // 'OPTIMIZELY', + // 'button_width', + // 'variation' + // ); + // sinon.assert.calledWith( + // createdLogger.log, + // LOG_LEVEL.INFO, + // '%s: Variable "%s" is not used in variation "%s". Returning default value.', + // 'OPTIMIZELY', + // 'button_info', + // 'variation' + // ); }); }); }); @@ -7361,10 +7331,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "false".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "false".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is double', function() { @@ -7372,10 +7342,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 50.55); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "50.55".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "50.55".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is integer', function() { @@ -7383,10 +7353,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 10); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "10".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "10".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is string', function() { @@ -7394,10 +7364,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Buy me'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "Buy me".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "Buy me".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is json', function() { @@ -7406,12 +7376,12 @@ describe('lib/optimizely', function() { }); assert.deepEqual(result, { num_buttons: 0, - text: "default value", + text: 'default value', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "{ "num_buttons": 0, "text": "default value"}".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "{ "num_buttons": 0, "text": "default value"}".' + // ); }); it('returns the variable default value from getFeatureVariableBoolean', function() { @@ -7422,10 +7392,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "false".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "false".' + // ); }); it('returns the variable default value from getFeatureVariableDouble', function() { @@ -7436,10 +7406,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, 50.55); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "50.55".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "50.55".' + // ); }); it('returns the variable default value from getFeatureVariableInteger', function() { @@ -7450,10 +7420,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, 10); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "10".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "10".' + // ); }); it('returns the variable default value from getFeatureVariableString', function() { @@ -7461,10 +7431,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Buy me'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "Buy me".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "Buy me".' + // ); }); it('returns the variable default value from getFeatureVariableJSON', function() { @@ -7475,10 +7445,10 @@ describe('lib/optimizely', function() { num_buttons: 0, text: 'default value', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "{ "num_buttons": 0, "text": "default value"}".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature_for_experiment" is not enabled for user user1. Returning the default variable value "{ "num_buttons": 0, "text": "default value"}".' + // ); }); it('returns the right values from getAllFeatureVariables', function() { @@ -7492,51 +7462,51 @@ describe('lib/optimizely', function() { button_txt: 'Buy me', button_info: { num_buttons: 0, - text: "default value", + text: 'default value', }, }); - assert.deepEqual(createdLogger.log.args, [ - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature_for_experiment', - 'user1', - '10' - ], - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature_for_experiment', - 'user1', - 'false' - ], - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature_for_experiment', - 'user1', - 'Buy me' - ], - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature_for_experiment', - 'user1', - '50.55' - ], - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature_for_experiment', - 'user1', - '{ "num_buttons": 0, "text": "default value"}' - ] - ]); + // assert.deepEqual(createdLogger.log.args, [ + // [ + // LOG_LEVEL.INFO, + // '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + // 'OPTIMIZELY', + // 'test_feature_for_experiment', + // 'user1', + // '10', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + // 'OPTIMIZELY', + // 'test_feature_for_experiment', + // 'user1', + // 'false', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + // 'OPTIMIZELY', + // 'test_feature_for_experiment', + // 'user1', + // 'Buy me', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + // 'OPTIMIZELY', + // 'test_feature_for_experiment', + // 'user1', + // '50.55', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + // 'OPTIMIZELY', + // 'test_feature_for_experiment', + // 'user1', + // '{ "num_buttons": 0, "text": "default value"}', + // ], + // ]); }); }); }); @@ -7566,10 +7536,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, true); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "true" for variable "new_content" of feature flag "test_feature"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "true" for variable "new_content" of feature flag "test_feature"' + // ); }); it('returns the right value from getFeatureVariable when variable type is double', function() { @@ -7577,10 +7547,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 4.99); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "4.99" for variable "price" of feature flag "test_feature"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "4.99" for variable "price" of feature flag "test_feature"' + // ); }); it('returns the right value from getFeatureVariable when variable type is integer', function() { @@ -7588,10 +7558,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 395); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "395" for variable "lasers" of feature flag "test_feature"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "395" for variable "lasers" of feature flag "test_feature"' + // ); }); it('returns the right value from getFeatureVariable when variable type is string', function() { @@ -7599,10 +7569,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Hello audience'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "Hello audience" for variable "message" of feature flag "test_feature"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "Hello audience" for variable "message" of feature flag "test_feature"' + // ); }); it('returns the right value from getFeatureVariable when variable type is json', function() { @@ -7613,10 +7583,10 @@ describe('lib/optimizely', function() { count: 2, message: 'Hello audience', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "{ "count": 2, "message": "Hello audience" }" for variable "message_info" of feature flag "test_feature"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "{ "count": 2, "message": "Hello audience" }" for variable "message_info" of feature flag "test_feature"' + // ); }); it('returns the right value from getFeatureVariableBoolean', function() { @@ -7624,10 +7594,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, true); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "true" for variable "new_content" of feature flag "test_feature"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "true" for variable "new_content" of feature flag "test_feature"' + // ); }); it('returns the right value from getFeatureVariableDouble', function() { @@ -7635,10 +7605,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 4.99); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "4.99" for variable "price" of feature flag "test_feature"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "4.99" for variable "price" of feature flag "test_feature"' + // ); }); it('returns the right value from getFeatureVariableInteger', function() { @@ -7646,10 +7616,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 395); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "395" for variable "lasers" of feature flag "test_feature"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "395" for variable "lasers" of feature flag "test_feature"' + // ); }); it('returns the right value from getFeatureVariableString', function() { @@ -7657,10 +7627,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Hello audience'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "Hello audience" for variable "message" of feature flag "test_feature"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "Hello audience" for variable "message" of feature flag "test_feature"' + // ); }); it('returns the right value from getFeatureVariableJSON', function() { @@ -7671,10 +7641,10 @@ describe('lib/optimizely', function() { count: 2, message: 'Hello audience', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Got variable value "{ "count": 2, "message": "Hello audience" }" for variable "message_info" of feature flag "test_feature"' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Got variable value "{ "count": 2, "message": "Hello audience" }" for variable "message_info" of feature flag "test_feature"' + // ); }); it('returns the right values from getAllFeatureVariables', function() { @@ -7691,51 +7661,48 @@ describe('lib/optimizely', function() { message: 'Hello audience', }, }); - assert.deepEqual( - createdLogger.log.args, - [ - [ - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - 'true', - 'new_content', - 'test_feature' - ], - [ - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - '395', - 'lasers', - 'test_feature' - ], - [ - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - '4.99', - 'price', - 'test_feature' - ], - [ - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - 'Hello audience', - 'message', - 'test_feature' - ], - [ - LOG_LEVEL.INFO, - '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - 'OPTIMIZELY', - '{ "count": 2, "message": "Hello audience" }', - 'message_info', - 'test_feature' - ] - ] - ) + // assert.deepEqual(createdLogger.log.args, [ + // [ + // LOG_LEVEL.INFO, + // '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + // 'OPTIMIZELY', + // 'true', + // 'new_content', + // 'test_feature', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + // 'OPTIMIZELY', + // '395', + // 'lasers', + // 'test_feature', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + // 'OPTIMIZELY', + // '4.99', + // 'price', + // 'test_feature', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + // 'OPTIMIZELY', + // 'Hello audience', + // 'message', + // 'test_feature', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', + // 'OPTIMIZELY', + // '{ "count": 2, "message": "Hello audience" }', + // 'message_info', + // 'test_feature', + // ], + // ]); }); describe('when the variable is not used in the variation', function() { @@ -7748,10 +7715,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "new_content" is not used in variation "594032". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "new_content" is not used in variation "594032". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is double', function() { @@ -7759,10 +7726,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 14.99); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "price" is not used in variation "594032". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "price" is not used in variation "594032". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is integer', function() { @@ -7770,10 +7737,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 400); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "lasers" is not used in variation "594032". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "lasers" is not used in variation "594032". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is string', function() { @@ -7781,10 +7748,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Hello'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "message" is not used in variation "594032". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "message" is not used in variation "594032". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is json', function() { @@ -7795,10 +7762,10 @@ describe('lib/optimizely', function() { count: 1, message: 'Hello', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "message_info" is not used in variation "594032". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "message_info" is not used in variation "594032". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariableBoolean', function() { @@ -7806,10 +7773,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "new_content" is not used in variation "594032". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "new_content" is not used in variation "594032". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariableDouble', function() { @@ -7817,10 +7784,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 14.99); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "price" is not used in variation "594032". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "price" is not used in variation "594032". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariableInteger', function() { @@ -7828,10 +7795,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 400); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "lasers" is not used in variation "594032". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "lasers" is not used in variation "594032". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariableString', function() { @@ -7839,10 +7806,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Hello'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "message" is not used in variation "594032". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "message" is not used in variation "594032". Returning default value.' + // ); }); it('returns the variable default value from getFeatureVariableJSON', function() { @@ -7851,12 +7818,12 @@ describe('lib/optimizely', function() { }); assert.deepEqual(result, { count: 1, - message: 'Hello' + message: 'Hello', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Variable "message_info" is not used in variation "594032". Returning default value.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Variable "message_info" is not used in variation "594032". Returning default value.' + // ); }); it('returns the right values from getAllFeatureVariables', function() { @@ -7873,47 +7840,43 @@ describe('lib/optimizely', function() { message: 'Hello', }, }); - assert.deepEqual( - createdLogger.log.args, - [ - [ - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'new_content', - '594032' - ], - [ - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'lasers', - '594032' - ], - [ - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'price', - '594032' - ], - [ - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'message', - '594032' - ], - [ - LOG_LEVEL.INFO, - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - 'OPTIMIZELY', - 'message_info', - '594032' - ] - ] - - ) + // assert.deepEqual(createdLogger.log.args, [ + // [ + // LOG_LEVEL.INFO, + // '%s: Variable "%s" is not used in variation "%s". Returning default value.', + // 'OPTIMIZELY', + // 'new_content', + // '594032', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Variable "%s" is not used in variation "%s". Returning default value.', + // 'OPTIMIZELY', + // 'lasers', + // '594032', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Variable "%s" is not used in variation "%s". Returning default value.', + // 'OPTIMIZELY', + // 'price', + // '594032', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Variable "%s" is not used in variation "%s". Returning default value.', + // 'OPTIMIZELY', + // 'message', + // '594032', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Variable "%s" is not used in variation "%s". Returning default value.', + // 'OPTIMIZELY', + // 'message_info', + // '594032', + // ], + // ]); }); }); }); @@ -7942,10 +7905,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "false".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "false".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is double', function() { @@ -7953,10 +7916,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 14.99); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "14.99".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "14.99".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is integer', function() { @@ -7964,10 +7927,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 400); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "400".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "400".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is string', function() { @@ -7975,10 +7938,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Hello'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "Hello".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "Hello".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is json', function() { @@ -7987,12 +7950,12 @@ describe('lib/optimizely', function() { }); assert.deepEqual(result, { count: 1, - message: 'Hello' + message: 'Hello', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "{ "count": 1, "message": "Hello" }".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "{ "count": 1, "message": "Hello" }".' + // ); }); it('returns the variable default value from getFeatureVariableBoolean', function() { @@ -8000,10 +7963,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "false".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "false".' + // ); }); it('returns the variable default value from getFeatureVariableDouble', function() { @@ -8011,10 +7974,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 14.99); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "14.99".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "14.99".' + // ); }); it('returns the variable default value from getFeatureVariableInteger', function() { @@ -8022,10 +7985,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 400); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "400".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "400".' + // ); }); it('returns the variable default value from getFeatureVariableString', function() { @@ -8033,10 +7996,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Hello'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "Hello".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "Hello".' + // ); }); it('returns the variable default value from getFeatureVariableJSON', function() { @@ -8045,12 +8008,12 @@ describe('lib/optimizely', function() { }); assert.deepEqual(result, { count: 1, - message: 'Hello' + message: 'Hello', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "{ "count": 1, "message": "Hello" }".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Feature "test_feature" is not enabled for user user1. Returning the default variable value "{ "count": 1, "message": "Hello" }".' + // ); }); it('returns the right values from getAllFeatureVariables', function() { @@ -8067,51 +8030,48 @@ describe('lib/optimizely', function() { message: 'Hello', }, }); - assert.deepEqual( - createdLogger.log.args, - [ - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature', - 'user1', - 'false' - ], - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature', - 'user1', - '400' - ], - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature', - 'user1', - '14.99' - ], - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature', - 'user1', - 'Hello' - ], - [ - LOG_LEVEL.INFO, - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - 'OPTIMIZELY', - 'test_feature', - 'user1', - '{ "count": 1, "message": "Hello" }' - ] - ] - ) + // assert.deepEqual(createdLogger.log.args, [ + // [ + // LOG_LEVEL.INFO, + // '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + // 'OPTIMIZELY', + // 'test_feature', + // 'user1', + // 'false', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + // 'OPTIMIZELY', + // 'test_feature', + // 'user1', + // '400', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + // 'OPTIMIZELY', + // 'test_feature', + // 'user1', + // '14.99', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + // 'OPTIMIZELY', + // 'test_feature', + // 'user1', + // 'Hello', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', + // 'OPTIMIZELY', + // 'test_feature', + // 'user1', + // '{ "count": 1, "message": "Hello" }', + // ], + // ]); }); }); }); @@ -8135,10 +8095,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "is_button_animated" of feature flag "test_feature_for_experiment".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "is_button_animated" of feature flag "test_feature_for_experiment".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is double', function() { @@ -8146,10 +8106,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 50.55); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_width" of feature flag "test_feature_for_experiment".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_width" of feature flag "test_feature_for_experiment".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is integer', function() { @@ -8157,10 +8117,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 10); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "num_buttons" of feature flag "test_feature_for_experiment".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "num_buttons" of feature flag "test_feature_for_experiment".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is string', function() { @@ -8168,10 +8128,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Buy me'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_txt" of feature flag "test_feature_for_experiment".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_txt" of feature flag "test_feature_for_experiment".' + // ); }); it('returns the variable default value from getFeatureVariable when variable type is json', function() { @@ -8182,10 +8142,10 @@ describe('lib/optimizely', function() { num_buttons: 0, text: 'default value', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_info" of feature flag "test_feature_for_experiment".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_info" of feature flag "test_feature_for_experiment".' + // ); }); it('returns the variable default value from getFeatureVariableBoolean', function() { @@ -8196,10 +8156,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, false); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "is_button_animated" of feature flag "test_feature_for_experiment".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "is_button_animated" of feature flag "test_feature_for_experiment".' + // ); }); it('returns the variable default value from getFeatureVariableDouble', function() { @@ -8207,10 +8167,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 50.55); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_width" of feature flag "test_feature_for_experiment".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_width" of feature flag "test_feature_for_experiment".' + // ); }); it('returns the variable default value from getFeatureVariableInteger', function() { @@ -8218,10 +8178,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 10); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "num_buttons" of feature flag "test_feature_for_experiment".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "num_buttons" of feature flag "test_feature_for_experiment".' + // ); }); it('returns the variable default value from getFeatureVariableString', function() { @@ -8229,10 +8189,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, 'Buy me'); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_txt" of feature flag "test_feature_for_experiment".' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_txt" of feature flag "test_feature_for_experiment".' + // ); }); it('returns the variable default value from getFeatureVariableJSON', function() { @@ -8243,10 +8203,11 @@ describe('lib/optimizely', function() { num_buttons: 0, text: 'default value', }); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_info" of feature flag "test_feature_for_experiment".' - ); + + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: User "user1" is not in any variation or rollout rule. Returning default value for variable "button_info" of feature flag "test_feature_for_experiment".' + // ); }); it('returns the right values from getAllFeatureVariables', function() { @@ -8263,51 +8224,48 @@ describe('lib/optimizely', function() { text: 'default value', }, }); - assert.deepEqual( - createdLogger.log.args, - [ - [ - LOG_LEVEL.INFO, - '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', - 'OPTIMIZELY', - 'user1', - 'num_buttons', - 'test_feature_for_experiment' - ], - [ - LOG_LEVEL.INFO, - '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', - 'OPTIMIZELY', - 'user1', - 'is_button_animated', - 'test_feature_for_experiment' - ], - [ - LOG_LEVEL.INFO, - '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', - 'OPTIMIZELY', - 'user1', - 'button_txt', - 'test_feature_for_experiment' - ], - [ - LOG_LEVEL.INFO, - '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', - 'OPTIMIZELY', - 'user1', - 'button_width', - 'test_feature_for_experiment' - ], - [ - LOG_LEVEL.INFO, - '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', - 'OPTIMIZELY', - 'user1', - 'button_info', - 'test_feature_for_experiment' - ] - ] - ); + // assert.deepEqual(createdLogger.log.args, [ + // [ + // LOG_LEVEL.INFO, + // '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', + // 'OPTIMIZELY', + // 'user1', + // 'num_buttons', + // 'test_feature_for_experiment', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', + // 'OPTIMIZELY', + // 'user1', + // 'is_button_animated', + // 'test_feature_for_experiment', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', + // 'OPTIMIZELY', + // 'user1', + // 'button_txt', + // 'test_feature_for_experiment', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', + // 'OPTIMIZELY', + // 'user1', + // 'button_width', + // 'test_feature_for_experiment', + // ], + // [ + // LOG_LEVEL.INFO, + // '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', + // 'OPTIMIZELY', + // 'user1', + // 'button_info', + // 'test_feature_for_experiment', + // ], + // ]); }); }); @@ -8316,10 +8274,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is undefined when variable type is boolean', function() { @@ -8327,19 +8285,19 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is not provided when variable type is boolean', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'is_button_animated'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is null when variable type is double', function() { @@ -8347,10 +8305,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is undefined when variable type is double', function() { @@ -8358,19 +8316,19 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is not provided when variable type is double', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_width'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is null when variable type is integer', function() { @@ -8378,10 +8336,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is undefined when variable type is integer', function() { @@ -8389,19 +8347,19 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is not provided when variable type is integer', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'num_buttons'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is null when variable type is string', function() { @@ -8409,10 +8367,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is undefined when variable type is string', function() { @@ -8420,19 +8378,19 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is not provided when variable type is string', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_txt'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is null when variable type is json', function() { @@ -8440,10 +8398,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is undefined when variable type is json', function() { @@ -8451,28 +8409,28 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariable if user id is not provided when variable type is json', function() { var result = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'button_info'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableBoolean when called with a non-boolean variable', function() { var result = optlyInstance.getFeatureVariableBoolean('test_feature_for_experiment', 'button_width', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Requested variable type "boolean", but variable is of type "double". Use correct API to retrieve value. Returning None.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Requested variable type "boolean", but variable is of type "double". Use correct API to retrieve value. Returning None.' + // ); }); it('returns null from getFeatureVariableDouble when called with a non-double variable', function() { @@ -8482,37 +8440,37 @@ describe('lib/optimizely', function() { 'user1' ); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Requested variable type "double", but variable is of type "boolean". Use correct API to retrieve value. Returning None.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Requested variable type "double", but variable is of type "boolean". Use correct API to retrieve value. Returning None.' + // ); }); it('returns null from getFeatureVariableInteger when called with a non-integer variable', function() { var result = optlyInstance.getFeatureVariableInteger('test_feature_for_experiment', 'button_width', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Requested variable type "integer", but variable is of type "double". Use correct API to retrieve value. Returning None.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Requested variable type "integer", but variable is of type "double". Use correct API to retrieve value. Returning None.' + // ); }); it('returns null from getFeatureVariableString when called with a non-string variable', function() { var result = optlyInstance.getFeatureVariableString('test_feature_for_experiment', 'num_buttons', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Requested variable type "string", but variable is of type "integer". Use correct API to retrieve value. Returning None.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Requested variable type "string", but variable is of type "integer". Use correct API to retrieve value. Returning None.' + // ); }); it('returns null from getFeatureVariableJSON when called with a non-json variable', function() { var result = optlyInstance.getFeatureVariableJSON('test_feature_for_experiment', 'button_txt', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Requested variable type "json", but variable is of type "string". Use correct API to retrieve value. Returning None.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Requested variable type "json", but variable is of type "string". Use correct API to retrieve value. Returning None.' + // ); }); it('returns null from getFeatureVariableBoolean if user id is null', function() { @@ -8523,10 +8481,10 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableBoolean if user id is undefined', function() { @@ -8537,19 +8495,19 @@ describe('lib/optimizely', function() { { test_attribute: 'test_value' } ); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableBoolean if user id is not provided', function() { var result = optlyInstance.getFeatureVariableBoolean('test_feature_for_experiment', 'is_button_animated'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableDouble if user id is null', function() { @@ -8557,10 +8515,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableDouble if user id is undefined', function() { @@ -8568,19 +8526,19 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableDouble if user id is not provided', function() { var result = optlyInstance.getFeatureVariableDouble('test_feature_for_experiment', 'button_width'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableInteger if user id is null', function() { @@ -8588,10 +8546,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableInteger if user id is undefined', function() { @@ -8599,19 +8557,19 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableInteger if user id is not provided', function() { var result = optlyInstance.getFeatureVariableInteger('test_feature_for_experiment', 'num_buttons'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableString if user id is null', function() { @@ -8619,10 +8577,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableString if user id is undefined', function() { @@ -8630,19 +8588,19 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableString if user id is not provided', function() { var result = optlyInstance.getFeatureVariableString('test_feature_for_experiment', 'button_txt'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableJSON if user id is null', function() { @@ -8650,10 +8608,10 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableJSON if user id is undefined', function() { @@ -8661,19 +8619,19 @@ describe('lib/optimizely', function() { test_attribute: 'test_value', }); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); it('returns null from getFeatureVariableJSON if user id is not provided', function() { var result = optlyInstance.getFeatureVariableJSON('test_feature_for_experiment', 'button_info'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'OPTIMIZELY: Provided user_id is in an invalid format.' - ); + // assert.equal( + // buildLogMessageFromArgs(createdLogger.log.lastCall.args), + // 'OPTIMIZELY: Provided user_id is in an invalid format.' + // ); }); describe('type casting failures', function() { @@ -8689,10 +8647,6 @@ describe('lib/optimizely', function() { 'user1' ); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Unable to cast value falsezzz to type boolean, returning null.' - ); }); }); @@ -8704,10 +8658,6 @@ describe('lib/optimizely', function() { it('should return null and log an error', function() { var result = optlyInstance.getFeatureVariableInteger('test_feature_for_experiment', 'num_buttons', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Unable to cast value zzz123 to type integer, returning null.' - ); }); }); @@ -8719,10 +8669,6 @@ describe('lib/optimizely', function() { it('should return null and log an error', function() { var result = optlyInstance.getFeatureVariableDouble('test_feature_for_experiment', 'button_width', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Unable to cast value zzz44.55 to type double, returning null.' - ); }); }); @@ -8734,10 +8680,6 @@ describe('lib/optimizely', function() { it('should return null and log an error', function() { var result = optlyInstance.getFeatureVariableJSON('test_feature_for_experiment', 'button_info', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Unable to cast value zzz44.55 to type json, returning null.' - ); }); }); }); @@ -8745,46 +8687,26 @@ describe('lib/optimizely', function() { it('returns null from getFeatureVariable if the argument feature key is invalid when variable type is boolean', function() { var result = optlyInstance.getFeatureVariable('thisIsNotAValidKey<><><>', 'is_button_animated', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' - ); }); it('returns null from getFeatureVariable if the argument feature key is invalid when variable type is double', function() { var result = optlyInstance.getFeatureVariable('thisIsNotAValidKey<><><>', 'button_width', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' - ); }); it('returns null from getFeatureVariable if the argument feature key is invalid when variable type is integer', function() { var result = optlyInstance.getFeatureVariable('thisIsNotAValidKey<><><>', 'num_buttons', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' - ); }); it('returns null from getFeatureVariable if the argument feature key is invalid when variable type is string', function() { var result = optlyInstance.getFeatureVariable('thisIsNotAValidKey<><><>', 'button_txt', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' - ); }); it('returns null from getFeatureVariable if the argument feature key is invalid when variable type is json', function() { var result = optlyInstance.getFeatureVariable('thisIsNotAValidKey<><><>', 'button_info', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' - ); }); it('returns null from getFeatureVariable if the argument variable key is invalid', function() { @@ -8794,55 +8716,31 @@ describe('lib/optimizely', function() { 'user1' ); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Variable with key "thisIsNotAVariableKey****" associated with feature with key "test_feature_for_experiment" is not in datafile.' - ); }); it('returns null from getFeatureVariableBoolean if the argument feature key is invalid', function() { var result = optlyInstance.getFeatureVariableBoolean('thisIsNotAValidKey<><><>', 'is_button_animated', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' - ); }); it('returns null from getFeatureVariableDouble if the argument feature key is invalid', function() { var result = optlyInstance.getFeatureVariableDouble('thisIsNotAValidKey<><><>', 'button_width', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' - ); }); it('returns null from getFeatureVariableInteger if the argument feature key is invalid', function() { var result = optlyInstance.getFeatureVariableInteger('thisIsNotAValidKey<><><>', 'num_buttons', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' - ); }); it('returns null from getFeatureVariableString if the argument feature key is invalid', function() { var result = optlyInstance.getFeatureVariableString('thisIsNotAValidKey<><><>', 'button_txt', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' - ); }); it('returns null from getFeatureVariableJSON if the argument feature key is invalid', function() { var result = optlyInstance.getFeatureVariableJSON('thisIsNotAValidKey<><><>', 'button_info', 'user1'); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key thisIsNotAValidKey<><><> is not in datafile.' - ); }); it('returns null from getFeatureVariableBoolean if the argument variable key is invalid', function() { @@ -8852,10 +8750,6 @@ describe('lib/optimizely', function() { 'user1' ); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Variable with key "thisIsNotAVariableKey****" associated with feature with key "test_feature_for_experiment" is not in datafile.' - ); }); it('returns null from getFeatureVariableDouble if the argument variable key is invalid', function() { @@ -8865,10 +8759,6 @@ describe('lib/optimizely', function() { 'user1' ); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Variable with key "thisIsNotAVariableKey****" associated with feature with key "test_feature_for_experiment" is not in datafile.' - ); }); it('returns null from getFeatureVariableInteger if the argument variable key is invalid', function() { @@ -8878,10 +8768,6 @@ describe('lib/optimizely', function() { 'user1' ); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Variable with key "thisIsNotAVariableKey****" associated with feature with key "test_feature_for_experiment" is not in datafile.' - ); }); it('returns null from getFeatureVariableString if the argument variable key is invalid', function() { @@ -8891,10 +8777,6 @@ describe('lib/optimizely', function() { 'user1' ); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Variable with key "thisIsNotAVariableKey****" associated with feature with key "test_feature_for_experiment" is not in datafile.' - ); }); it('returns null from getFeatureVariableJSON if the argument variable key is invalid', function() { @@ -8904,147 +8786,110 @@ describe('lib/optimizely', function() { 'user1' ); assert.strictEqual(result, null); - assert.equal( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Variable with key "thisIsNotAVariableKey****" associated with feature with key "test_feature_for_experiment" is not in datafile.' - ); }); it('returns null from getFeatureVariable when optimizely object is not a valid instance', function() { - var instance = new Optimizely({ - datafile: {}, - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, - logger: createdLogger, - eventProcessor, - notificationCenter, + const { optlyInstance, errorNotifier, createdLogger } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), }); - createdLogger.log.reset(); - - instance.getFeatureVariable('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); + sinon.stub(createdLogger, 'error'); - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariable')); + const val = optlyInstance.getFeatureVariable('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); + assert.strictEqual(val, null); }); it('returns null from getFeatureVariableBoolean when optimizely object is not a valid instance', function() { var instance = new Optimizely({ - datafile: {}, - errorHandler: errorHandler, + projectConfigManager: getMockProjectConfigManager(), eventDispatcher: eventDispatcher, logger: createdLogger, notificationCenter, eventProcessor, }); - createdLogger.log.reset(); - - instance.getFeatureVariableBoolean('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); - - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableBoolean')); + const val = instance.getFeatureVariableBoolean('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); + assert.strictEqual(val, null); }); it('returns null from getFeatureVariableDouble when optimizely object is not a valid instance', function() { var instance = new Optimizely({ - datafile: {}, - errorHandler: errorHandler, + projectConfigManager: getMockProjectConfigManager(), eventDispatcher: eventDispatcher, logger: createdLogger, notificationCenter, eventProcessor, }); - createdLogger.log.reset(); - - instance.getFeatureVariableDouble('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); - - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableDouble')); + const val = instance.getFeatureVariableDouble('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); + assert.strictEqual(val, null); }); it('returns null from getFeatureVariableInteger when optimizely object is not a valid instance', function() { var instance = new Optimizely({ - datafile: {}, - errorHandler: errorHandler, + projectConfigManager: getMockProjectConfigManager(), eventDispatcher: eventDispatcher, logger: createdLogger, notificationCenter, eventProcessor, }); - createdLogger.log.reset(); - - instance.getFeatureVariableInteger('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); - - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableInteger')); + const val = instance.getFeatureVariableInteger('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); + assert.strictEqual(val, null); }); it('returns null from getFeatureVariableString when optimizely object is not a valid instance', function() { var instance = new Optimizely({ - datafile: {}, - errorHandler: errorHandler, + projectConfigManager: getMockProjectConfigManager(), eventDispatcher: eventDispatcher, logger: createdLogger, notificationCenter, eventProcessor, }); - createdLogger.log.reset(); - - instance.getFeatureVariableString('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); - - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableString')); + const val = instance.getFeatureVariableString('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); + assert.strictEqual(val, null); }); it('returns null from getFeatureVariableJSON when optimizely object is not a valid instance', function() { var instance = new Optimizely({ - datafile: {}, - errorHandler: errorHandler, + projectConfigManager: getMockProjectConfigManager(), eventDispatcher: eventDispatcher, logger: createdLogger, notificationCenter, eventProcessor, }); - createdLogger.log.reset(); - - instance.getFeatureVariableJSON('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); - - sinon.assert.calledOnce(createdLogger.log); - var logMessage = buildLogMessageFromArgs(createdLogger.log.args[0]); - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.INVALID_OBJECT, 'OPTIMIZELY', 'getFeatureVariableJSON')); + const val = instance.getFeatureVariableJSON('test_feature_for_experiment', 'thisIsNotAVariableKey****', 'user1'); + assert.strictEqual(val, null); }); }); }); describe('audience match types', function() { var sandbox = sinon.sandbox.create(); - var createdLogger = logger.createLogger({ + var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO, logToConsole: false, }); var optlyInstance; - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); - var eventProcessor = createEventProcessor({ - dispatcher: eventDispatcher, - batchSize: 1, - notificationCenter: notificationCenter, - }); + var notificationCenter = createNotificationCenter({ logger: createdLogger, }); + var eventDispatcher = { + dispatchEvent: () => Promise.resolve({ statusCode: 200 }), + }; + var eventProcessor = getForwardingEventProcessor( + eventDispatcher, + ); beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTypedAudiencesConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTypedAudiencesConfig(), - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + projectConfigManager: mockConfigManager, + + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -9053,7 +8898,7 @@ describe('lib/optimizely', function() { notificationCenter, }); - sandbox.stub(errorHandler, 'handleError'); + sandbox.stub(eventDispatcher, 'dispatchEvent'); sandbox.stub(createdLogger, 'log'); }); @@ -9167,24 +9012,29 @@ describe('lib/optimizely', function() { describe('audience combinations', function() { var sandbox = sinon.sandbox.create(); var evalSpy; - var createdLogger = logger.createLogger({ + var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO, logToConsole: false, }); var optlyInstance; var audienceEvaluator; - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); - var eventProcessor = createEventProcessor({ - dispatcher: eventDispatcher, - batchSize: 1, - notificationCenter: notificationCenter, - }); + var notificationCenter = createNotificationCenter({ logger: createdLogger, }); + var eventDispatcher = { + dispatchEvent: () => Promise.resolve({ statusCode: 200 }), + }; + var eventProcessor = getForwardingEventProcessor( + eventDispatcher, + ); beforeEach(function() { + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTypedAudiencesConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTypedAudiencesConfig(), - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + projectConfigManager: mockConfigManager, + + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -9194,7 +9044,8 @@ describe('lib/optimizely', function() { }); audienceEvaluator = AudienceEvaluator.prototype; - sandbox.stub(errorHandler, 'handleError'); + sandbox.stub(eventDispatcher, 'dispatchEvent'); + sandbox.stub(createdLogger, 'log'); evalSpy = sandbox.spy(audienceEvaluator, 'evaluate'); }); @@ -9271,7 +9122,10 @@ describe('lib/optimizely', function() { optlyInstance.projectConfigManager.getConfig().audiencesById, sinon.match.any ); - assert.deepEqual(evalSpy.getCalls()[0].args[2].getAttributes(), { house: '...Slytherinnn...sss.', favorite_ice_cream: 'matcha' }); + assert.deepEqual(evalSpy.getCalls()[0].args[2].getAttributes(), { + house: '...Slytherinnn...sss.', + favorite_ice_cream: 'matcha', + }); }); it('can exclude a user from a rollout with complex audience conditions via isFeatureEnabled', function() { @@ -9357,318 +9211,40 @@ describe('lib/optimizely', function() { var bucketStub; var fakeDecisionResponse; var notificationCenter; + var eventDispatcher; var eventProcessor; - var createdLogger = logger.createLogger({ + var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO, logToConsole: false, }); beforeEach(function() { bucketStub = sinon.stub(bucketer, 'bucket'); - sinon.stub(errorHandler, 'handleError'); - sinon.stub(createdLogger, 'log'); + + sinon.stub(createdLogger, 'debug'); + sinon.stub(createdLogger, 'info'); + sinon.stub(createdLogger, 'warn'); + sinon.stub(createdLogger, 'error'); sinon.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); - notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); - eventProcessor = createEventProcessor({ - dispatcher: eventDispatcher, - batchSize: 3, - notificationCenter: notificationCenter, - flushInterval: 100, - }); + notificationCenter = createNotificationCenter({ logger: createdLogger, }); + eventDispatcher = getMockEventDispatcher(); + eventProcessor = getForwardingEventProcessor( + eventDispatcher, + ); }); afterEach(function() { + eventDispatcher.dispatchEvent.reset(); bucketer.bucket.restore(); - errorHandler.handleError.restore(); - createdLogger.log.restore(); + + createdLogger.debug.restore(); + createdLogger.info.restore(); + createdLogger.warn.restore(); + createdLogger.error.restore(); fns.uuid.restore(); }); - describe('when eventBatchSize = 3 and eventFlushInterval = 100', function() { - var optlyInstance; - - beforeEach(function() { - optlyInstance = new Optimizely({ - clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, - jsonSchemaValidator: jsonSchemaValidator, - logger: createdLogger, - isValidInstance: true, - eventBatchSize: 3, - eventFlushInterval: 100, - eventProcessor, - notificationCenter, - }); - }); - - afterEach(function() { - optlyInstance.close(); - }); - - it('should send batched events when the maxQueueSize is reached', function() { - fakeDecisionResponse = { - result: '111129', - reasons: [], - }; - bucketStub.returns(fakeDecisionResponse); - var activate = optlyInstance.activate('testExperiment', 'testUser'); - assert.strictEqual(activate, 'variation'); - - optlyInstance.track('testEvent', 'testUser'); - optlyInstance.track('testEvent', 'testUser'); - - sinon.assert.calledOnce(eventDispatcher.dispatchEvent); - - var expectedObj = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - snapshots: [ - { - decisions: [ - { - campaign_id: '4', - experiment_id: '111127', - variation_id: '111129', - metadata: { - flag_key: '', - rule_key: 'testExperiment', - rule_type: 'experiment', - variation_key: 'variation', - enabled: true, - }, - }, - ], - events: [ - { - entity_id: '4', - timestamp: Math.round(new Date().getTime()), - key: 'campaign_activated', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - }, - ], - }, - ], - visitor_id: 'testUser', - attributes: [], - }, - { - attributes: [], - snapshots: [ - { - events: [ - { - entity_id: '111095', - key: 'testEvent', - timestamp: new Date().getTime(), - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - }, - ], - }, - ], - visitor_id: 'testUser', - }, - { - attributes: [], - snapshots: [ - { - events: [ - { - entity_id: '111095', - key: 'testEvent', - timestamp: new Date().getTime(), - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - }, - ], - }, - ], - visitor_id: 'testUser', - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - var eventDispatcherCall = eventDispatcher.dispatchEvent.args[0]; - assert.deepEqual(eventDispatcherCall[0], expectedObj); - }); - - it('should flush the queue when the flushInterval occurs', function() { - var timestamp = new Date().getTime(); - fakeDecisionResponse = { - result: '111129', - reasons: [], - }; - bucketStub.returns(fakeDecisionResponse); - var activate = optlyInstance.activate('testExperiment', 'testUser'); - assert.strictEqual(activate, 'variation'); - - optlyInstance.track('testEvent', 'testUser'); - - sinon.assert.notCalled(eventDispatcher.dispatchEvent); - - clock.tick(100); - - sinon.assert.calledOnce(eventDispatcher.dispatchEvent); - - var expectedObj = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - snapshots: [ - { - decisions: [ - { - campaign_id: '4', - experiment_id: '111127', - variation_id: '111129', - metadata: { - flag_key: '', - rule_key: 'testExperiment', - rule_type: 'experiment', - variation_key: 'variation', - enabled: true, - }, - }, - ], - events: [ - { - entity_id: '4', - timestamp: timestamp, - key: 'campaign_activated', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - }, - ], - }, - ], - visitor_id: 'testUser', - attributes: [], - }, - { - attributes: [], - snapshots: [ - { - events: [ - { - entity_id: '111095', - key: 'testEvent', - timestamp: timestamp, - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - }, - ], - }, - ], - visitor_id: 'testUser', - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - var eventDispatcherCall = eventDispatcher.dispatchEvent.args[0]; - assert.deepEqual(eventDispatcherCall[0], expectedObj); - }); - - it('should flush the queue when optimizely.close() is called', function() { - fakeDecisionResponse = { - result: '111129', - reasons: [], - }; - bucketStub.returns(fakeDecisionResponse); - var activate = optlyInstance.activate('testExperiment', 'testUser'); - assert.strictEqual(activate, 'variation'); - - optlyInstance.track('testEvent', 'testUser'); - - sinon.assert.notCalled(eventDispatcher.dispatchEvent); - - optlyInstance.close(); - - sinon.assert.calledOnce(eventDispatcher.dispatchEvent); - - var expectedObj = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - snapshots: [ - { - decisions: [ - { - campaign_id: '4', - experiment_id: '111127', - variation_id: '111129', - metadata: { - flag_key: '', - rule_key: 'testExperiment', - rule_type: 'experiment', - variation_key: 'variation', - enabled: true, - }, - }, - ], - events: [ - { - entity_id: '4', - timestamp: Math.round(new Date().getTime()), - key: 'campaign_activated', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - }, - ], - }, - ], - visitor_id: 'testUser', - attributes: [], - }, - { - attributes: [], - snapshots: [ - { - events: [ - { - entity_id: '111095', - key: 'testEvent', - timestamp: new Date().getTime(), - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - }, - ], - }, - ], - visitor_id: 'testUser', - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: enums.NODE_CLIENT_VERSION, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - var eventDispatcherCall = eventDispatcher.dispatchEvent.args[0]; - assert.deepEqual(eventDispatcherCall[0], expectedObj); - }); - }); - describe('close method', function() { var eventProcessorStopPromise; var optlyInstance; @@ -9678,18 +9254,26 @@ describe('lib/optimizely', function() { process: sinon.stub(), start: sinon.stub(), stop: sinon.stub(), - }; + onRunning: sinon.stub(), + onTerminated: sinon.stub(), + onDispatch: sinon.stub(), + setLogger: sinon.stub(), + }; }); - describe('when the event processor stop method returns a promise that fulfills', function() { + describe('when the event processor onTerminated method returns a promise that fulfills', function() { beforeEach(function() { eventProcessorStopPromise = Promise.resolve(); - mockEventProcessor.stop.returns(eventProcessorStopPromise); + mockEventProcessor.onTerminated.returns(eventProcessorStopPromise); + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + projectConfigManager: mockConfigManager, + + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -9706,22 +9290,27 @@ describe('lib/optimizely', function() { }); }); - it('returns a promise that fulfills with a successful result object', function() { - return optlyInstance.close().then(function(result) { - assert.deepEqual(result, { success: true }); - }); + it('returns a promise that resolves', function() { + return optlyInstance.close().then().catch(() => { + assert.fail(); + }) }); }); - describe('when the event processor stop method returns a promise that rejects', function() { + describe('when the event processor onTerminated() method returns a promise that rejects', function() { beforeEach(function() { - eventProcessorStopPromise = Promise.reject(new Error('Failed to stop')); - mockEventProcessor.stop.returns(eventProcessorStopPromise); + eventProcessorStopPromise = Promise.reject(new Error('FAILED_TO_STOP')); + eventProcessorStopPromise.catch(() => {}); + mockEventProcessor.onTerminated.returns(eventProcessorStopPromise); + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestProjectConfig()), + }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + projectConfigManager: mockConfigManager, + + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, isValidInstance: true, @@ -9733,17 +9322,17 @@ describe('lib/optimizely', function() { }); afterEach(function() { + eventDispatcher.dispatchEvent.reset(); return eventProcessorStopPromise.catch(function() { // Handle rejected promise - don't want test to fail }); }); - it('returns a promise that fulfills with an unsuccessful result object', function() { - return optlyInstance.close().then(function(result) { - assert.deepEqual(result, { - success: false, - reason: 'Error: Failed to stop', - }); + it('returns a promise that rejects', function() { + return optlyInstance.close().then(() => { + assert.fail('promnise should reject') + }).catch(() => { + }); }); }); @@ -9751,35 +9340,45 @@ describe('lib/optimizely', function() { }); describe('project config management', function() { - var createdLogger = logger.createLogger({ + var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO, logToConsole: false, }); - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); - var eventProcessor = createEventProcessor({ - dispatcher: eventDispatcher, - batchSize: 1, - notificationCenter: notificationCenter, - }); + var notificationCenter = createNotificationCenter({ logger: createdLogger, }); + var eventDispatcher = getMockEventDispatcher(); + var eventProcessor = getForwardingEventProcessor( + eventDispatcher + ); beforeEach(function() { - sinon.stub(errorHandler, 'handleError'); - sinon.stub(createdLogger, 'log'); + + sinon.stub(createdLogger, 'debug'); + sinon.stub(createdLogger, 'info'); + sinon.stub(createdLogger, 'warn'); + sinon.stub(createdLogger, 'error'); }); afterEach(function() { - errorHandler.handleError.restore(); - createdLogger.log.restore(); + createdLogger.debug.restore(); + createdLogger.info.restore(); + createdLogger.warn.restore(); + createdLogger.error.restore(); + eventDispatcher.dispatchEvent.reset(); + }); var optlyInstance; it('should call the project config manager stop method when the close method is called', function() { + const projectConfigManager = getMockProjectConfigManager(); + sinon.stub(projectConfigManager, 'stop'); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + + projectConfigManager, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, sdkKey: '12345', @@ -9788,16 +9387,15 @@ describe('lib/optimizely', function() { notificationCenter, }); optlyInstance.close(); - var fakeManager = projectConfigManager.createProjectConfigManager.getCall(0).returnValue; - sinon.assert.calledOnce(fakeManager.stop); + sinon.assert.calledOnce(projectConfigManager.stop); }); - describe('when no datafile is available yet ', function() { + describe('when no project config is available yet ', function() { beforeEach(function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + projectConfigManager: getMockProjectConfigManager(), + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, sdkKey: '12345', @@ -9807,6 +9405,7 @@ describe('lib/optimizely', function() { }); }); + it('returns fallback values from API methods that return meaningful values', function() { assert.isNull(optlyInstance.activate('my_experiment', 'user1')); assert.isNull(optlyInstance.getVariation('my_experiment', 'user1')); @@ -9843,20 +9442,14 @@ describe('lib/optimizely', function() { clearTimeoutSpy.restore(); }); - it('fulfills the promise with the value from the project config manager ready promise after the project config manager ready promise is fulfilled', function() { - projectConfigManager.createProjectConfigManager.callsFake(function(config) { - var currentConfig = config.datafile ? projectConfig.createProjectConfig(config.datafile) : null; - return { - stop: sinon.stub(), - getConfig: sinon.stub().returns(currentConfig), - onUpdate: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns(Promise.resolve({ success: true })), - }; - }); + it('fulfills the promise after the project config manager onRunning promise is fulfilled', function() { + const projectConfigManager = getMockProjectConfigManager(); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + projectConfigManager, + + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, sdkKey: '12345', @@ -9864,16 +9457,15 @@ describe('lib/optimizely', function() { notificationCenter, eventProcessor, }); - return optlyInstance.onReady().then(function(result) { - assert.deepEqual(result, { success: true }); - }); + + return optlyInstance.onReady(); }); - it('fulfills the promise with an unsuccessful result after the timeout has expired when the project config manager onReady promise still has not resolved', function() { + it('rejects the promise after the timeout has expired when the project config manager onReady promise still has not resolved', function() { + const projectConfigManager = getMockProjectConfigManager({ onRunning: new Promise(function() {}) }); + optlyInstance = new Optimizely({ - clientEngine: 'node-sdk', - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + clientEngine: 'node-sdk', projectConfigManager, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, sdkKey: '12345', @@ -9883,18 +9475,20 @@ describe('lib/optimizely', function() { }); var readyPromise = optlyInstance.onReady({ timeout: 500 }); clock.tick(501); - return readyPromise.then(function(result) { - assert.include(result, { - success: false, - }); + return readyPromise.then(() => { + return Promise.reject(new Error('PROMISE_SHOULD_NOT_HAVE_RESOLVED')); + }, (err) => { + assert.equal(err.message, sprintf(ONREADY_TIMEOUT, 500)); }); }); - it('fulfills the promise with an unsuccessful result after 30 seconds when no timeout argument is provided and the project config manager onReady promise still has not resolved', function() { + it('rejects the promise after 30 seconds when no timeout argument is provided and the project config manager onReady promise still has not resolved', function() { + const projectConfigManager = getMockProjectConfigManager({ onRunning: new Promise(function() {}) }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + projectConfigManager, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, sdkKey: '12345', @@ -9904,18 +9498,20 @@ describe('lib/optimizely', function() { }); var readyPromise = optlyInstance.onReady(); clock.tick(300001); - return readyPromise.then(function(result) { - assert.include(result, { - success: false, - }); + return readyPromise.then(() => { + return Promise.reject(new Error('PROMISE_SHOULD_NOT_HAVE_RESOLVED')); + }, (err) => { + assert.equal(err.message, sprintf(ONREADY_TIMEOUT, 30000)); }); }); - it('fulfills the promise with an unsuccessful result after the instance is closed', function() { + it('rejects the promise after the instance is closed', function() { + const projectConfigManager = getMockProjectConfigManager({ onRunning: new Promise(function() {}) }); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + projectConfigManager, + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, sdkKey: '12345', @@ -9924,19 +9520,26 @@ describe('lib/optimizely', function() { eventProcessor, }); var readyPromise = optlyInstance.onReady({ timeout: 100 }); + optlyInstance.close(); - return readyPromise.then(function(result) { - assert.include(result, { - success: false, - }); + + return readyPromise.then(() => { + return Promise.reject(new Error('PROMISE_SHOULD_NOT_HAVE_RESOLVED')); + }, (err) => { + assert.equal(err.baseMessage, SERVICE_STOPPED_BEFORE_RUNNING); }); }); it('can be called several times with different timeout values and the returned promises behave correctly', function() { + const onRunning = resolvablePromise(); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + projectConfigManager: getMockProjectConfigManager({ + onRunning: onRunning.promise, + }), + + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, sdkKey: '12345', @@ -9949,31 +9552,23 @@ describe('lib/optimizely', function() { var readyPromise3 = optlyInstance.onReady({ timeout: 300 }); clock.tick(101); return readyPromise1 - .then(function() { + .catch(function() { clock.tick(100); return readyPromise2; }) - .then(function() { + .catch(function() { // readyPromise3 has not resolved yet because only 201 ms have elapsed. // Calling close on the instance should resolve readyPromise3 - optlyInstance.close(); + optlyInstance.close().catch(() => {}); return readyPromise3; - }); + }).catch(() => {}); }); it('clears the timeout when the project config manager ready promise fulfills', function() { - projectConfigManager.createProjectConfigManager.callsFake(function(config) { - return { - stop: sinon.stub(), - getConfig: sinon.stub().returns(null), - onUpdate: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns(Promise.resolve({ success: true })), - }; - }); optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + projectConfigManager: getMockProjectConfigManager(), + eventProcessor, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, sdkKey: '12345', @@ -9993,18 +9588,12 @@ describe('lib/optimizely', function() { describe('project config updates', function() { var fakeProjectConfigManager; beforeEach(function() { - fakeProjectConfigManager = { - stop: sinon.stub(), - getConfig: sinon.stub().returns(null), - onUpdate: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns({ then: function() {} }), - }; - projectConfigManager.createProjectConfigManager.returns(fakeProjectConfigManager); + fakeProjectConfigManager = getMockProjectConfigManager(), optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - errorHandler: errorHandler, - eventDispatcher: eventDispatcher, + eventProcessor, + projectConfigManager: fakeProjectConfigManager, jsonSchemaValidator: jsonSchemaValidator, logger: createdLogger, sdkKey: '12345', @@ -10021,10 +9610,13 @@ describe('lib/optimizely', function() { assert.isNull(optlyInstance.activate('myOtherExperiment', 'user98765')); // Project config manager receives new project config object - should use this now - var newConfig = projectConfig.createProjectConfig(testData.getTestProjectConfigWithFeatures()); - fakeProjectConfigManager.getConfig.returns(newConfig); - var updateListener = fakeProjectConfigManager.onUpdate.getCall(0).args[0]; - updateListener(newConfig); + + const datafile = testData.getTestProjectConfigWithFeatures(); + + const newConfig = createProjectConfig(datafile, JSON.stringify(datafile)); + + fakeProjectConfigManager.setConfig(newConfig); + fakeProjectConfigManager.pushUpdate(newConfig); // With the new project config containing this feature, should return true assert.isTrue(optlyInstance.isFeatureEnabled('test_feature_for_experiment', 'user45678')); @@ -10052,9 +9644,9 @@ describe('lib/optimizely', function() { ], }); differentDatafile.revision = '44'; - var differentConfig = projectConfig.createProjectConfig(differentDatafile); - fakeProjectConfigManager.getConfig.returns(differentConfig); - updateListener(differentConfig); + var differentConfig = createProjectConfig(differentDatafile, JSON.stringify(differentDatafile)); + fakeProjectConfigManager.setConfig(differentConfig); + fakeProjectConfigManager.pushUpdate(differentConfig); // activate should return a variation for the new experiment assert.strictEqual(optlyInstance.activate('myOtherExperiment', 'user98765'), 'control'); @@ -10063,12 +9655,12 @@ describe('lib/optimizely', function() { it('emits a notification when the project config manager emits a new project config object', function() { var listener = sinon.spy(); optlyInstance.notificationCenter.addNotificationListener( - enums.NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, + NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, listener ); - var newConfig = projectConfig.createProjectConfig(testData.getTestProjectConfigWithFeatures()); - var updateListener = fakeProjectConfigManager.onUpdate.getCall(0).args[0]; - updateListener(newConfig); + var newConfig = createProjectConfig(testData.getTestProjectConfigWithFeatures()); + fakeProjectConfigManager.pushUpdate(newConfig); + sinon.assert.calledOnce(listener); }); }); @@ -10079,22 +9671,23 @@ describe('lib/optimizely', function() { var bucketStub; var fakeDecisionResponse; var eventDispatcherSpy; - var logger = { log: function() {} }; - var errorHandler = { handleError: function() {} }; - var notificationCenter = createNotificationCenter({ logger, errorHandler }); + var logger = createLogger(); + var notificationCenter = createNotificationCenter({ logger }); var eventProcessor; beforeEach(function() { bucketStub = sinon.stub(bucketer, 'bucket'); - eventDispatcherSpy = sinon.spy(); - eventProcessor = createEventProcessor({ - dispatcher: { dispatchEvent: eventDispatcherSpy }, - batchSize: 1, - notificationCenter: notificationCenter, - }); + eventDispatcherSpy = sinon.spy(() => Promise.resolve({ statusCode: 200 })); + eventProcessor = getForwardingEventProcessor( + { dispatchEvent: eventDispatcherSpy }, + ); + + const datafile = testData.getTestProjectConfig(); + const mockConfigManager = getMockProjectConfigManager(); + mockConfigManager.setConfig(createProjectConfig(datafile, JSON.stringify(datafile))); + optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestProjectConfig(), - errorHandler, + projectConfigManager: mockConfigManager, logger, isValidInstance: true, eventBatchSize: 1, @@ -10111,7 +9704,7 @@ describe('lib/optimizely', function() { it('should trigger a log event notification when an impression event is dispatched', function() { var notificationListener = sinon.spy(); optlyInstance.notificationCenter.addNotificationListener( - enums.NOTIFICATION_TYPES.LOG_EVENT, + NOTIFICATION_TYPES.LOG_EVENT, notificationListener ); fakeDecisionResponse = { @@ -10129,7 +9722,7 @@ describe('lib/optimizely', function() { it('should trigger a log event notification when a conversion event is dispatched', function() { var notificationListener = sinon.spy(); optlyInstance.notificationCenter.addNotificationListener( - enums.NOTIFICATION_TYPES.LOG_EVENT, + NOTIFICATION_TYPES.LOG_EVENT, notificationListener ); optlyInstance.track('testEvent', 'testUser'); diff --git a/packages/optimizely-sdk/lib/optimizely/index.ts b/lib/optimizely/index.ts similarity index 54% rename from packages/optimizely-sdk/lib/optimizely/index.ts rename to lib/optimizely/index.ts index d6fdd8beb..b8707a006 100644 --- a/packages/optimizely-sdk/lib/optimizely/index.ts +++ b/lib/optimizely/index.ts @@ -1,62 +1,108 @@ -/**************************************************************************** - * Copyright 2020-2022, Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ -import { LoggerFacade, ErrorHandler } from '../modules/logging'; +/** + * Copyright 2020-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LoggerFacade } from '../logging/logger'; import { sprintf, objectValues } from '../utils/fns'; -import { NotificationCenter } from '../core/notification_center'; -import { EventProcessor } from '../../lib/modules/event_processor'; +import { createNotificationCenter, DefaultNotificationCenter } from '../notification_center'; +import { EventProcessor } from '../event_processor/event_processor'; + +import { OdpManager } from '../odp/odp_manager'; +import { VuidManager } from '../vuid/vuid_manager'; +import { OdpEvent } from '../odp/event_manager/odp_event'; +import { OptimizelySegmentOption } from '../odp/segment_manager/optimizely_segment_option'; +import { BaseService, ServiceState } from '../service'; import { UserAttributes, EventTags, OptimizelyConfig, - OnReadyResult, UserProfileService, Variation, FeatureFlag, FeatureVariable, - OptimizelyOptions, OptimizelyDecideOption, - OptimizelyDecision + FeatureVariableValue, + OptimizelyDecision, + Client, + UserProfileServiceAsync, + isHoldout, } from '../shared_types'; import { newErrorDecision } from '../optimizely_decision'; import OptimizelyUserContext from '../optimizely_user_context'; -import { createProjectConfigManager, ProjectConfigManager } from '../core/project_config/project_config_manager'; +import { ProjectConfigManager } from '../project_config/project_config_manager'; import { createDecisionService, DecisionService, DecisionObj } from '../core/decision_service'; -import { getImpressionEvent, getConversionEvent } from '../core/event_builder'; -import { buildImpressionEvent, buildConversionEvent } from '../core/event_builder/event_helpers'; -import fns from '../utils/fns' +import { buildLogEvent } from '../event_processor/event_builder/log_event'; +import { buildImpressionEvent, buildConversionEvent } from '../event_processor/event_builder/user_event'; +import { isSafeInteger } from '../utils/fns'; import { validate } from '../utils/attributes_validator'; -import * as enums from '../utils/enums'; import * as eventTagsValidator from '../utils/event_tags_validator'; -import * as projectConfig from '../core/project_config'; +import * as projectConfig from '../project_config/project_config'; import * as userProfileServiceValidator from '../utils/user_profile_service_validator'; import * as stringValidator from '../utils/string_value_validator'; import * as decision from '../core/decision'; + import { - ERROR_MESSAGES, - LOG_LEVEL, - LOG_MESSAGES, DECISION_SOURCES, DECISION_MESSAGES, FEATURE_VARIABLE_TYPES, - DECISION_NOTIFICATION_TYPES, - NOTIFICATION_TYPES + NODE_CLIENT_ENGINE, + CLIENT_VERSION, } from '../utils/enums'; +import { Fn, Maybe, OpType } from '../utils/type'; +import { resolvablePromise } from '../utils/promise/resolvablePromise'; -const MODULE_NAME = 'OPTIMIZELY'; +import { NOTIFICATION_TYPES, DecisionNotificationType, DECISION_NOTIFICATION_TYPES, ActivateListenerPayload } from '../notification_center/type'; +import { + FEATURE_NOT_IN_DATAFILE, + INVALID_INPUT_FORMAT, + NO_EVENT_PROCESSOR, + ODP_EVENT_FAILED, + ODP_EVENT_FAILED_ODP_MANAGER_MISSING, + UNABLE_TO_GET_VUID_VUID_MANAGER_NOT_AVAILABLE, + UNRECOGNIZED_DECIDE_OPTION, + NO_PROJECT_CONFIG_FAILURE, + EVENT_KEY_NOT_FOUND, + NOT_TRACKING_USER, + VARIABLE_REQUESTED_WITH_WRONG_TYPE, +} from 'error_message'; + +import { + FEATURE_ENABLED_FOR_USER, + FEATURE_NOT_ENABLED_FOR_USER, + FEATURE_NOT_ENABLED_RETURN_DEFAULT_VARIABLE_VALUE, + INVALID_CLIENT_ENGINE, + INVALID_DECIDE_OPTIONS, + INVALID_DEFAULT_DECIDE_OPTIONS, + INVALID_EXPERIMENT_KEY_INFO, + NOT_ACTIVATING_USER, + SHOULD_NOT_DISPATCH_ACTIVATE, + TRACK_EVENT, + UPDATED_OPTIMIZELY_CONFIG, + USER_RECEIVED_DEFAULT_VARIABLE_VALUE, + USER_RECEIVED_VARIABLE_VALUE, + VALID_USER_PROFILE_SERVICE, + VARIABLE_NOT_USED_RETURN_DEFAULT_VARIABLE_VALUE, +} from 'log_message'; + +import { SERVICE_STOPPED_BEFORE_RUNNING } from '../service'; + +import { ErrorNotifier } from '../error/error_notifier'; +import { ErrorReporter } from '../error/error_reporter'; +import { OptimizelyError } from '../error/optimizly_error'; +import { Value } from '../utils/promise/operation_value'; +import { CmabService } from '../core/decision_service/cmab/cmab_service'; const DEFAULT_ONREADY_TIMEOUT = 30000; @@ -65,126 +111,184 @@ type InputKey = 'feature_key' | 'user_id' | 'variable_key' | 'experiment_key' | type StringInputs = Partial<Record<InputKey, unknown>>; -export default class Optimizely { - private isOptimizelyConfigValid: boolean; - private disposeOnUpdate: (() => void) | null; - private readyPromise: Promise<{ success: boolean; reason?: string }>; - // readyTimeout is specified as any to make this work in both browser & Node - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private readyTimeouts: { [key: string]: { readyTimeout: any; onClose: () => void } }; - private nextReadyTimeoutId: number; +type DecisionReasons = (string | number)[]; + +export const INSTANCE_CLOSED = 'Instance closed'; +export const ONREADY_TIMEOUT = 'onReady timeout expired after %s ms'; +export const INVALID_IDENTIFIER = 'Invalid identifier'; +export const INVALID_ATTRIBUTES = 'Invalid attributes'; + +/** + * options required to create optimizely object + */ +export type OptimizelyOptions = { + projectConfigManager: ProjectConfigManager; + UNSTABLE_conditionEvaluators?: unknown; + cmabService: CmabService; + clientEngine: string; + clientVersion?: string; + errorNotifier?: ErrorNotifier; + eventProcessor?: EventProcessor; + jsonSchemaValidator?: { + validate(jsonObject: unknown): boolean; + }; + logger?: LoggerFacade; + userProfileService?: UserProfileService | null; + userProfileServiceAsync?: UserProfileServiceAsync | null; + defaultDecideOptions?: OptimizelyDecideOption[]; + odpManager?: OdpManager; + vuidManager?: VuidManager + disposable?: boolean; +} + +export default class Optimizely extends BaseService implements Client { + private cleanupTasks: Map<number, Fn> = new Map(); + private nextCleanupTaskId = 0; private clientEngine: string; private clientVersion: string; - private errorHandler: ErrorHandler; - private logger: LoggerFacade; + private errorNotifier?: ErrorNotifier; + private errorReporter: ErrorReporter; private projectConfigManager: ProjectConfigManager; private decisionService: DecisionService; - private eventProcessor: EventProcessor; + private eventProcessor?: EventProcessor; private defaultDecideOptions: { [key: string]: boolean }; - public notificationCenter: NotificationCenter; + private odpManager?: OdpManager; + public notificationCenter: DefaultNotificationCenter; + private vuidManager?: VuidManager; constructor(config: OptimizelyOptions) { + super(); + let clientEngine = config.clientEngine; if (!clientEngine) { - config.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.INVALID_CLIENT_ENGINE, - MODULE_NAME, - clientEngine, - ); - clientEngine = enums.NODE_CLIENT_ENGINE; + config.logger?.info(INVALID_CLIENT_ENGINE, clientEngine); + clientEngine = NODE_CLIENT_ENGINE; } this.clientEngine = clientEngine; - this.clientVersion = config.clientVersion || enums.NODE_CLIENT_VERSION; - this.errorHandler = config.errorHandler; - this.isOptimizelyConfigValid = config.isValidInstance; + this.clientVersion = config.clientVersion || CLIENT_VERSION; + this.errorNotifier = config.errorNotifier; this.logger = config.logger; + this.projectConfigManager = config.projectConfigManager; + this.errorReporter = new ErrorReporter(this.logger, this.errorNotifier); + this.odpManager = config.odpManager; + this.vuidManager = config.vuidManager; + this.eventProcessor = config.eventProcessor; + + if(config.disposable) { + this.projectConfigManager.makeDisposable(); + this.eventProcessor?.makeDisposable(); + this.odpManager?.makeDisposable(); + } + + // pass a child logger to sub-components + if (this.logger) { + this.projectConfigManager.setLogger(this.logger.child()); + this.eventProcessor?.setLogger(this.logger.child()); + this.odpManager?.setLogger(this.logger.child()); + } let decideOptionsArray = config.defaultDecideOptions ?? []; + if (!Array.isArray(decideOptionsArray)) { - this.logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.INVALID_DEFAULT_DECIDE_OPTIONS, MODULE_NAME); + this.logger?.debug(INVALID_DEFAULT_DECIDE_OPTIONS); decideOptionsArray = []; } const defaultDecideOptions: { [key: string]: boolean } = {}; - decideOptionsArray.forEach((option) => { + decideOptionsArray.forEach(option => { // Filter out all provided default decide options that are not in OptimizelyDecideOption[] if (OptimizelyDecideOption[option]) { defaultDecideOptions[option] = true; } else { - this.logger.log( - LOG_LEVEL.WARNING, - LOG_MESSAGES.UNRECOGNIZED_DECIDE_OPTION, - MODULE_NAME, - option, - ); + this.logger?.warn(UNRECOGNIZED_DECIDE_OPTION, option); } }); this.defaultDecideOptions = defaultDecideOptions; - this.projectConfigManager = createProjectConfigManager({ - datafile: config.datafile, - jsonSchemaValidator: config.jsonSchemaValidator, - sdkKey: config.sdkKey, - datafileManager: config.datafileManager + + this.projectConfigManager = config.projectConfigManager; + this.projectConfigManager.onUpdate((configObj: projectConfig.ProjectConfig) => { + this.logger?.info( + UPDATED_OPTIMIZELY_CONFIG, + configObj.revision, + configObj.projectId + ); + + this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE, undefined); + + this.updateOdpSettings(); }); - this.disposeOnUpdate = this.projectConfigManager.onUpdate( - (configObj: projectConfig.ProjectConfig) => { - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.UPDATED_OPTIMIZELY_CONFIG, - MODULE_NAME, - configObj.revision, - configObj.projectId, - ); - this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.OPTIMIZELY_CONFIG_UPDATE); - } - ); + this.eventProcessor = config.eventProcessor; + this.eventProcessor?.onDispatch((event) => { + this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.LOG_EVENT, event); + }); - const projectConfigManagerReadyPromise = this.projectConfigManager.onReady(); + this.odpManager = config.odpManager; - let userProfileService: UserProfileService | null = null; + let userProfileService: Maybe<UserProfileService> = undefined; if (config.userProfileService) { try { if (userProfileServiceValidator.validate(config.userProfileService)) { userProfileService = config.userProfileService; - this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.VALID_USER_PROFILE_SERVICE, MODULE_NAME); + this.logger?.info(VALID_USER_PROFILE_SERVICE); } - } catch (ex: any) { - this.logger.log(LOG_LEVEL.WARNING, ex.message); + } catch (ex) { + this.logger?.warn(ex); } } this.decisionService = createDecisionService({ userProfileService: userProfileService, + userProfileServiceAsync: config.userProfileServiceAsync || undefined, + cmabService: config.cmabService, logger: this.logger, UNSTABLE_conditionEvaluators: config.UNSTABLE_conditionEvaluators, }); - this.notificationCenter = config.notificationCenter; - - this.eventProcessor = config.eventProcessor; + this.notificationCenter = createNotificationCenter({ logger: this.logger, errorNotifier: this.errorNotifier }); - const eventProcessorStartedPromise = this.eventProcessor.start(); + this.start(); + } - this.readyPromise = Promise.all([projectConfigManagerReadyPromise, eventProcessorStartedPromise]).then(function(promiseResults) { - // Only return status from project config promise because event processor promise does not return any status. - return promiseResults[0]; - }) + start(): void { + if (!this.isNew()) { + return; + } - this.readyTimeouts = {}; - this.nextReadyTimeoutId = 0; + super.start(); + + this.state = ServiceState.Starting; + this.projectConfigManager.start(); + this.eventProcessor?.start(); + this.odpManager?.start(); + + Promise.all([ + this.projectConfigManager.onRunning(), + this.eventProcessor ? this.eventProcessor.onRunning() : Promise.resolve(), + this.odpManager ? this.odpManager.onRunning() : Promise.resolve(), + this.vuidManager ? this.vuidManager.initialize() : Promise.resolve(), + ]).then(() => { + this.state = ServiceState.Running; + this.startPromise.resolve(); + + const vuid = this.vuidManager?.getVuid(); + if (vuid) { + this.odpManager?.setVuid(vuid); + } + }).catch((err) => { + this.state = ServiceState.Failed; + this.errorReporter.report(err); + this.startPromise.reject(err); + }); } /** - * Returns a truthy value if this instance currently has a valid project config - * object, and the initial configuration object that was passed into the - * constructor was also valid. - * @return {boolean} + * Returns the project configuration retrieved from projectConfigManager + * @return {projectConfig.ProjectConfig} */ - isValidInstance(): boolean { - return this.isOptimizelyConfigValid && !!this.projectConfigManager.getConfig(); + getProjectConfig(): projectConfig.ProjectConfig | null { + return this.projectConfigManager.getConfig() || null; } /** @@ -196,8 +300,9 @@ export default class Optimizely { */ activate(experimentKey: string, userId: string, attributes?: UserAttributes): string | null { try { - if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'activate'); + const configObj = this.getProjectConfig(); + if (!configObj) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'activate'); return null; } @@ -205,11 +310,6 @@ export default class Optimizely { return this.notActivatingExperiment(experimentKey, userId); } - const configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return null; - } - try { const variationKey = this.getVariation(experimentKey, userId, attributes); if (variationKey === null) { @@ -218,12 +318,7 @@ export default class Optimizely { // If experiment is not set to 'Running' status, log accordingly and return variation key if (!projectConfig.isRunning(configObj, experimentKey)) { - this.logger.log( - LOG_LEVEL.DEBUG, - LOG_MESSAGES.SHOULD_NOT_DISPATCH_ACTIVATE, - MODULE_NAME, - experimentKey, - ); + this.logger?.debug(SHOULD_NOT_DISPATCH_ACTIVATE, experimentKey); return variationKey; } @@ -232,32 +327,18 @@ export default class Optimizely { const decisionObj = { experiment: experiment, variation: variation, - decisionSource: enums.DECISION_SOURCES.EXPERIMENT - } + decisionSource: DECISION_SOURCES.EXPERIMENT, + }; - this.sendImpressionEvent( - decisionObj, - '', - userId, - true, - attributes - ); + this.sendImpressionEvent(decisionObj, '', userId, true, attributes); return variationKey; - } catch (ex: any) { - this.logger.log(LOG_LEVEL.ERROR, ex.message); - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.NOT_ACTIVATING_USER, - MODULE_NAME, - userId, - experimentKey, - ); - this.errorHandler.handleError(ex); + } catch (ex) { + this.logger?.info(NOT_ACTIVATING_USER, userId, experimentKey); + this.errorReporter.report(ex); return null; } - } catch (e: any) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + } catch (e) { + this.errorReporter.report(e); return null; } } @@ -277,9 +358,14 @@ export default class Optimizely { flagKey: string, userId: string, enabled: boolean, - attributes?: UserAttributes, + attributes?: UserAttributes ): void { - const configObj = this.projectConfigManager.getConfig(); + if (!this.eventProcessor) { + this.logger?.error(NO_EVENT_PROCESSOR); + return; + } + + const configObj = this.getProjectConfig(); if (!configObj) { return; } @@ -293,69 +379,29 @@ export default class Optimizely { clientVersion: this.clientVersion, configObj: configObj, }); - // TODO is it okay to not pass a projectConfig as second argument - this.eventProcessor.process(impressionEvent); - this.emitNotificationCenterActivate(decisionObj, flagKey, userId, enabled, attributes); - } - /** - * Emit the ACTIVATE notification on the notificationCenter - * @param {DecisionObj} decisionObj Decision object - * @param {string} flagKey Key for a feature flag - * @param {string} userId ID of user to whom the variation was shown - * @param {boolean} enabled Boolean representing if feature is enabled - * @param {UserAttributes} attributes Optional user attributes - */ - private emitNotificationCenterActivate( - decisionObj: DecisionObj, - flagKey: string, - userId: string, - enabled: boolean, - attributes?: UserAttributes - ): void { - const configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return; - } - - const ruleType = decisionObj.decisionSource; - const experimentKey = decision.getExperimentKey(decisionObj); - const experimentId = decision.getExperimentId(decisionObj); - const variationKey = decision.getVariationKey(decisionObj); - const variationId = decision.getVariationId(decisionObj); - - let experiment; + this.eventProcessor.process(impressionEvent); - if (experimentId !== null && variationKey !== '') { - experiment = configObj.experimentIdMap[experimentId]; - } + const logEvent = buildLogEvent([impressionEvent]); - const impressionEventOptions = { - attributes: attributes, - clientEngine: this.clientEngine, - clientVersion: this.clientVersion, - configObj: configObj, - experimentId: experimentId, - ruleKey: experimentKey, - flagKey: flagKey, - ruleType: ruleType, + const activateNotificationPayload: ActivateListenerPayload = { + experiment: null, + holdout: null, userId: userId, - enabled: enabled, - variationId: variationId, - logger: this.logger, + attributes: attributes, + variation: decisionObj.variation, + logEvent, }; - const impressionEvent = getImpressionEvent(impressionEventOptions); - let variation; - if (experiment && experiment.variationKeyMap && variationKey !== '') { - variation = experiment.variationKeyMap[variationKey]; + + if (decisionObj.experiment) { + if (isHoldout(decisionObj.experiment)) { + activateNotificationPayload.holdout = decisionObj.experiment; + } else { + activateNotificationPayload.experiment = decisionObj.experiment; + } } - this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, { - experiment: experiment, - userId: userId, - attributes: attributes, - variation: variation, - logEvent: impressionEvent, - }); + + this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.ACTIVATE, activateNotificationPayload); } /** @@ -367,28 +413,25 @@ export default class Optimizely { */ track(eventKey: string, userId: string, attributes?: UserAttributes, eventTags?: EventTags): void { try { - if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'track'); + if (!this.eventProcessor) { + this.logger?.error(NO_EVENT_PROCESSOR); return; } - if (!this.validateInputs({ user_id: userId, event_key: eventKey }, attributes, eventTags)) { + const configObj = this.getProjectConfig(); + if (!configObj) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'track'); return; } - const configObj = this.projectConfigManager.getConfig(); - if (!configObj) { + if (!this.validateInputs({ user_id: userId, event_key: eventKey }, attributes, eventTags)) { return; } + if (!projectConfig.eventWithKeyExists(configObj, eventKey)) { - this.logger.log( - LOG_LEVEL.WARNING, - enums.LOG_MESSAGES.EVENT_KEY_NOT_FOUND, - MODULE_NAME, - eventKey, - ); - this.logger.log(LOG_LEVEL.WARNING, LOG_MESSAGES.NOT_TRACKING_USER, MODULE_NAME, userId); + this.logger?.warn(EVENT_KEY_NOT_FOUND, eventKey); + this.logger?.warn(NOT_TRACKING_USER, userId); return; } @@ -402,56 +445,25 @@ export default class Optimizely { clientEngine: this.clientEngine, clientVersion: this.clientVersion, configObj: configObj, - }); - this.logger.log(LOG_LEVEL.INFO, enums.LOG_MESSAGES.TRACK_EVENT, MODULE_NAME, eventKey, userId); + }, this.logger); + this.logger?.info(TRACK_EVENT, eventKey, userId); // TODO is it okay to not pass a projectConfig as second argument this.eventProcessor.process(conversionEvent); - this.emitNotificationCenterTrack(eventKey, userId, attributes, eventTags); - } catch (e: any) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.NOT_TRACKING_USER, MODULE_NAME, userId); - } - } - /** - * Send TRACK event to notificationCenter - * @param {string} eventKey - * @param {string} userId - * @param {UserAttributes} attributes - * @param {EventTags} eventTags Values associated with the event. - */ - private emitNotificationCenterTrack(eventKey: string, userId: string, attributes?: UserAttributes, eventTags?: EventTags): void { - try { - const configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return; - } - - const conversionEventOptions = { - attributes: attributes, - clientEngine: this.clientEngine, - clientVersion: this.clientVersion, - configObj: configObj, - eventKey: eventKey, - eventTags: eventTags, - logger: this.logger, - userId: userId, - }; - const conversionEvent = getConversionEvent(conversionEventOptions); + const logEvent = buildLogEvent([conversionEvent]); this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.TRACK, { - eventKey: eventKey, - userId: userId, - attributes: attributes, - eventTags: eventTags, - logEvent: conversionEvent, + eventKey, + userId, + attributes, + eventTags, + logEvent, }); - } catch (ex: any) { - this.logger.log(LOG_LEVEL.ERROR, ex.message); - this.errorHandler.handleError(ex); + } catch (e) { + this.errorReporter.report(e); + this.logger?.error(NOT_TRACKING_USER, userId); } } - + /** * Gets variation where visitor will be bucketed. * @param {string} experimentKey @@ -461,8 +473,9 @@ export default class Optimizely { */ getVariation(experimentKey: string, userId: string, attributes?: UserAttributes): string | null { try { - if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getVariation'); + const configObj = this.getProjectConfig(); + if (!configObj) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'getVariation'); return null; } @@ -471,28 +484,18 @@ export default class Optimizely { return null; } - const configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return null; - } - const experiment = configObj.experimentKeyMap[experimentKey]; - if (!experiment) { - this.logger.log( - LOG_LEVEL.DEBUG, - ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, - MODULE_NAME, - experimentKey, - ); + if (!experiment || experiment.isRollout) { + this.logger?.debug(INVALID_EXPERIMENT_KEY_INFO, experimentKey); return null; } const variationKey = this.decisionService.getVariation( configObj, experiment, - this.createUserContext(userId, attributes) as OptimizelyUserContext + this.createInternalUserContext(userId, attributes) as OptimizelyUserContext ).result; - const decisionNotificationType = projectConfig.isFeatureExperiment(configObj, experiment.id) + const decisionNotificationType: DecisionNotificationType = projectConfig.isFeatureExperiment(configObj, experiment.id) ? DECISION_NOTIFICATION_TYPES.FEATURE_TEST : DECISION_NOTIFICATION_TYPES.AB_TEST; @@ -507,14 +510,12 @@ export default class Optimizely { }); return variationKey; - } catch (ex: any) { - this.logger.log(LOG_LEVEL.ERROR, ex.message); - this.errorHandler.handleError(ex); + } catch (ex) { + this.errorReporter.report(ex); return null; } - } catch (e: any) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + } catch (e) { + this.errorReporter.report(e); return null; } } @@ -532,16 +533,15 @@ export default class Optimizely { return false; } - const configObj = this.projectConfigManager.getConfig(); + const configObj = this.getProjectConfig(); if (!configObj) { return false; } try { return this.decisionService.setForcedVariation(configObj, experimentKey, userId, variationKey); - } catch (ex: any) { - this.logger.log(LOG_LEVEL.ERROR, ex.message); - this.errorHandler.handleError(ex); + } catch (ex) { + this.errorReporter.report(ex); return false; } } @@ -557,16 +557,15 @@ export default class Optimizely { return null; } - const configObj = this.projectConfigManager.getConfig(); + const configObj = this.getProjectConfig(); if (!configObj) { return null; } try { return this.decisionService.getForcedVariation(configObj, experimentKey, userId).result; - } catch (ex: any) { - this.logger.log(LOG_LEVEL.ERROR, ex.message); - this.errorHandler.handleError(ex); + } catch (ex) { + this.errorReporter.report(ex); return null; } } @@ -579,25 +578,21 @@ export default class Optimizely { * @return {boolean} True if inputs are valid * */ - private validateInputs( - stringInputs: StringInputs, - userAttributes?: unknown, - eventTags?: unknown - ): boolean { + protected validateInputs(stringInputs: StringInputs, userAttributes?: unknown, eventTags?: unknown): boolean { try { if (stringInputs.hasOwnProperty('user_id')) { const userId = stringInputs['user_id']; if (typeof userId !== 'string' || userId === null || userId === 'undefined') { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, MODULE_NAME, 'user_id')); + throw new OptimizelyError(INVALID_INPUT_FORMAT, 'user_id'); } delete stringInputs['user_id']; } Object.keys(stringInputs).forEach(key => { if (!stringValidator.validate(stringInputs[key as InputKey])) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_INPUT_FORMAT, MODULE_NAME, key)); + throw new OptimizelyError(INVALID_INPUT_FORMAT, key); } - }) + }); if (userAttributes) { validate(userAttributes); } @@ -605,13 +600,10 @@ export default class Optimizely { eventTagsValidator.validate(eventTags); } return true; - - } catch (ex: any) { - this.logger.log(LOG_LEVEL.ERROR, ex.message); - this.errorHandler.handleError(ex); + } catch (ex) { + this.errorReporter.report(ex); return false; } - } /** @@ -621,13 +613,7 @@ export default class Optimizely { * @return {null} */ private notActivatingExperiment(experimentKey: string, userId: string): null { - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.NOT_ACTIVATING_USER, - MODULE_NAME, - userId, - experimentKey, - ); + this.logger?.info(NOT_ACTIVATING_USER, userId, experimentKey); return null; } @@ -638,8 +624,9 @@ export default class Optimizely { */ private filterEmptyValues(map: EventTags | undefined): EventTags | undefined { for (const key in map) { - if (map.hasOwnProperty(key) && (map[key] === null || map[key] === undefined)) { - delete map[key]; + const typedKey = key as keyof EventTags; + if (map.hasOwnProperty(typedKey) && (map[typedKey] === null || map[typedKey] === undefined)) { + delete map[typedKey]; } } return map; @@ -654,13 +641,9 @@ export default class Optimizely { */ isFeatureEnabled(featureKey: string, userId: string, attributes?: UserAttributes): boolean { try { - if (!this.isValidInstance()) { - this.logger.log( - LOG_LEVEL.ERROR, - LOG_MESSAGES.INVALID_OBJECT, - MODULE_NAME, - 'isFeatureEnabled', - ); + const configObj = this.getProjectConfig(); + if (!configObj) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'isFeatureEnabled'); return false; } @@ -668,18 +651,13 @@ export default class Optimizely { return false; } - const configObj = this.projectConfigManager.getConfig(); - if (!configObj) { - return false; - } - const feature = projectConfig.getFeatureFromKey(configObj, featureKey, this.logger); if (!feature) { return false; } let sourceInfo = {}; - const user = this.createUserContext(userId, attributes) as OptimizelyUserContext; + const user = this.createInternalUserContext(userId, attributes) as OptimizelyUserContext; const decisionObj = this.decisionService.getVariationForFeature(configObj, feature, user).result; const decisionSource = decisionObj.decisionSource; const experimentKey = decision.getExperimentKey(decisionObj); @@ -687,7 +665,7 @@ export default class Optimizely { let featureEnabled = decision.getFeatureEnabledFromVariation(decisionObj); - if (decisionSource === DECISION_SOURCES.FEATURE_TEST) { + if (decisionSource === DECISION_SOURCES.FEATURE_TEST || decisionSource === DECISION_SOURCES.HOLDOUT) { sourceInfo = { experimentKey: experimentKey, variationKey: variationKey, @@ -696,33 +674,16 @@ export default class Optimizely { if ( decisionSource === DECISION_SOURCES.FEATURE_TEST || - decisionSource === DECISION_SOURCES.ROLLOUT && projectConfig.getSendFlagDecisionsValue(configObj) + decisionSource === DECISION_SOURCES.HOLDOUT || + (decisionSource === DECISION_SOURCES.ROLLOUT && projectConfig.getSendFlagDecisionsValue(configObj)) ) { - this.sendImpressionEvent( - decisionObj, - feature.key, - userId, - featureEnabled, - attributes - ); + this.sendImpressionEvent(decisionObj, feature.key, userId, featureEnabled, attributes); } if (featureEnabled === true) { - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.FEATURE_ENABLED_FOR_USER, - MODULE_NAME, - featureKey, - userId, - ); + this.logger?.info(FEATURE_ENABLED_FOR_USER, featureKey, userId); } else { - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.FEATURE_NOT_ENABLED_FOR_USER, - MODULE_NAME, - featureKey, - userId, - ); + this.logger?.info(FEATURE_NOT_ENABLED_FOR_USER, featureKey, userId); featureEnabled = false; } @@ -741,9 +702,8 @@ export default class Optimizely { }); return featureEnabled; - } catch (e: any) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + } catch (e) { + this.errorReporter.report(e); return false; } } @@ -758,37 +718,26 @@ export default class Optimizely { getEnabledFeatures(userId: string, attributes?: UserAttributes): string[] { try { const enabledFeatures: string[] = []; - if (!this.isValidInstance()) { - this.logger.log( - LOG_LEVEL.ERROR, - LOG_MESSAGES.INVALID_OBJECT, - MODULE_NAME, - 'getEnabledFeatures', - ); - return enabledFeatures; - } - if (!this.validateInputs({ user_id: userId })) { + const configObj = this.getProjectConfig(); + if (!configObj) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'getEnabledFeatures'); return enabledFeatures; } - const configObj = this.projectConfigManager.getConfig(); - if (!configObj) { + if (!this.validateInputs({ user_id: userId })) { return enabledFeatures; } - objectValues(configObj.featureKeyMap).forEach( - (feature: FeatureFlag) => { - if (this.isFeatureEnabled(feature.key, userId, attributes)) { - enabledFeatures.push(feature.key); - } + objectValues(configObj.featureKeyMap).forEach((feature: FeatureFlag) => { + if (this.isFeatureEnabled(feature.key, userId, attributes)) { + enabledFeatures.push(feature.key); } - ); + }); return enabledFeatures; - } catch (e: any) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + } catch (e) { + this.errorReporter.report(e); return []; } } @@ -812,16 +761,15 @@ export default class Optimizely { variableKey: string, userId: string, attributes?: UserAttributes - ): unknown { + ): FeatureVariableValue { try { - if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariable'); + if (!this.getProjectConfig()) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'getFeatureVariable'); return null; } return this.getFeatureVariableForType(featureKey, variableKey, null, userId, attributes); - } catch (e: any) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + } catch (e) { + this.errorReporter.report(e); return null; } } @@ -853,12 +801,13 @@ export default class Optimizely { variableKey: string, variableType: string | null, userId: string, - attributes?: UserAttributes): unknown { + attributes?: UserAttributes + ): FeatureVariableValue { if (!this.validateInputs({ feature_key: featureKey, variable_key: variableKey, user_id: userId }, attributes)) { return null; } - const configObj = this.projectConfigManager.getConfig(); + const configObj = this.getProjectConfig(); if (!configObj) { return null; } @@ -874,20 +823,24 @@ export default class Optimizely { } if (variableType && variable.type !== variableType) { - this.logger.log( - LOG_LEVEL.WARNING, - LOG_MESSAGES.VARIABLE_REQUESTED_WITH_WRONG_TYPE, - MODULE_NAME, + this.logger?.warn( + VARIABLE_REQUESTED_WITH_WRONG_TYPE, variableType, - variable.type, + variable.type ); return null; } - const user = this.createUserContext(userId, attributes) as OptimizelyUserContext; + const user = this.createInternalUserContext(userId, attributes) as OptimizelyUserContext; const decisionObj = this.decisionService.getVariationForFeature(configObj, featureFlag, user).result; const featureEnabled = decision.getFeatureEnabledFromVariation(decisionObj); - const variableValue = this.getFeatureVariableValueFromVariation(featureKey, featureEnabled, decisionObj.variation, variable, userId); + const variableValue = this.getFeatureVariableValueFromVariation( + featureKey, + featureEnabled, + decisionObj.variation, + variable, + userId + ); let sourceInfo = {}; if ( decisionObj.decisionSource === DECISION_SOURCES.FEATURE_TEST && @@ -939,8 +892,8 @@ export default class Optimizely { variation: Variation | null, variable: FeatureVariable, userId: string - ): unknown { - const configObj = this.projectConfigManager.getConfig(); + ): FeatureVariableValue { + const configObj = this.getProjectConfig(); if (!configObj) { return null; } @@ -951,41 +904,33 @@ export default class Optimizely { if (value !== null) { if (featureEnabled) { variableValue = value; - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.USER_RECEIVED_VARIABLE_VALUE, - MODULE_NAME, + this.logger?.info( + USER_RECEIVED_VARIABLE_VALUE, variableValue, variable.key, - featureKey, + featureKey ); } else { - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.FEATURE_NOT_ENABLED_RETURN_DEFAULT_VARIABLE_VALUE, - MODULE_NAME, + this.logger?.info( + FEATURE_NOT_ENABLED_RETURN_DEFAULT_VARIABLE_VALUE, featureKey, userId, - variableValue, + variableValue ); } } else { - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.VARIABLE_NOT_USED_RETURN_DEFAULT_VARIABLE_VALUE, - MODULE_NAME, + this.logger?.info( + VARIABLE_NOT_USED_RETURN_DEFAULT_VARIABLE_VALUE, variable.key, - variation.key, + variation.key ); } } else { - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.USER_RECEIVED_DEFAULT_VARIABLE_VALUE, - MODULE_NAME, + this.logger?.info( + USER_RECEIVED_DEFAULT_VARIABLE_VALUE, userId, variable.key, - featureKey, + featureKey ); } @@ -1012,14 +957,19 @@ export default class Optimizely { attributes?: UserAttributes ): boolean | null { try { - if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableBoolean'); + if (!this.getProjectConfig()) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'getFeatureVariableBoolean'); return null; } - return this.getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.BOOLEAN, userId, attributes) as boolean | null; - } catch (e: any) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + return this.getFeatureVariableForType( + featureKey, + variableKey, + FEATURE_VARIABLE_TYPES.BOOLEAN, + userId, + attributes + ) as boolean | null; + } catch (e) { + this.errorReporter.report(e); return null; } } @@ -1045,14 +995,19 @@ export default class Optimizely { attributes?: UserAttributes ): number | null { try { - if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableDouble'); + if (!this.getProjectConfig()) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'getFeatureVariableDouble'); return null; } - return this.getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.DOUBLE, userId, attributes) as number | null; - } catch (e: any) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + return this.getFeatureVariableForType( + featureKey, + variableKey, + FEATURE_VARIABLE_TYPES.DOUBLE, + userId, + attributes + ) as number | null; + } catch (e) { + this.errorReporter.report(e); return null; } } @@ -1078,14 +1033,19 @@ export default class Optimizely { attributes?: UserAttributes ): number | null { try { - if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableInteger'); + if (!this.getProjectConfig()) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'getFeatureVariableInteger'); return null; } - return this.getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.INTEGER, userId, attributes) as number | null; - } catch (e: any) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + return this.getFeatureVariableForType( + featureKey, + variableKey, + FEATURE_VARIABLE_TYPES.INTEGER, + userId, + attributes + ) as number | null; + } catch (e) { + this.errorReporter.report(e); return null; } } @@ -1111,14 +1071,19 @@ export default class Optimizely { attributes?: UserAttributes ): string | null { try { - if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableString'); + if (!this.getProjectConfig()) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'getFeatureVariableString'); return null; } - return this.getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.STRING, userId, attributes) as string | null; - } catch (e: any) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + return this.getFeatureVariableForType( + featureKey, + variableKey, + FEATURE_VARIABLE_TYPES.STRING, + userId, + attributes + ) as string | null; + } catch (e) { + this.errorReporter.report(e); return null; } } @@ -1137,21 +1102,15 @@ export default class Optimizely { * invalid, or there is a mismatch with the type * of the variable */ - getFeatureVariableJSON( - featureKey: string, - variableKey: string, - userId: string, - attributes: UserAttributes - ): unknown { + getFeatureVariableJSON(featureKey: string, variableKey: string, userId: string, attributes: UserAttributes): unknown { try { - if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getFeatureVariableJSON'); + if (!this.getProjectConfig()) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'getFeatureVariableJSON'); return null; } return this.getFeatureVariableForType(featureKey, variableKey, FEATURE_VARIABLE_TYPES.JSON, userId, attributes); - } catch (e: any) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + } catch (e) { + this.errorReporter.report(e); return null; } } @@ -1172,17 +1131,14 @@ export default class Optimizely { attributes?: UserAttributes ): { [variableKey: string]: unknown } | null { try { - if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'getAllFeatureVariables'); - return null; - } + const configObj = this.getProjectConfig(); - if (!this.validateInputs({ feature_key: featureKey, user_id: userId }, attributes)) { + if (!configObj) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'getAllFeatureVariables'); return null; } - const configObj = this.projectConfigManager.getConfig(); - if (!configObj) { + if (!this.validateInputs({ feature_key: featureKey, user_id: userId }, attributes)) { return null; } @@ -1191,18 +1147,25 @@ export default class Optimizely { return null; } - const user = this.createUserContext(userId, attributes) as OptimizelyUserContext; + const user = this.createInternalUserContext(userId, attributes) as OptimizelyUserContext; const decisionObj = this.decisionService.getVariationForFeature(configObj, featureFlag, user).result; const featureEnabled = decision.getFeatureEnabledFromVariation(decisionObj); const allVariables: { [variableKey: string]: unknown } = {}; featureFlag.variables.forEach((variable: FeatureVariable) => { - allVariables[variable.key] = this.getFeatureVariableValueFromVariation(featureKey, featureEnabled, decisionObj.variation, variable, userId); + allVariables[variable.key] = this.getFeatureVariableValueFromVariation( + featureKey, + featureEnabled, + decisionObj.variation, + variable, + userId + ); }); let sourceInfo = {}; - if (decisionObj.decisionSource === DECISION_SOURCES.FEATURE_TEST && + if ( + decisionObj.decisionSource === DECISION_SOURCES.FEATURE_TEST && decisionObj.experiment !== null && decisionObj.variation !== null ) { @@ -1225,9 +1188,8 @@ export default class Optimizely { }); return allVariables; - } catch (e: any) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + } catch (e) { + this.errorReporter.report(e); return null; } } @@ -1270,18 +1232,35 @@ export default class Optimizely { */ getOptimizelyConfig(): OptimizelyConfig | null { try { - const configObj = this.projectConfigManager.getConfig(); + const configObj = this.getProjectConfig(); if (!configObj) { return null; } - return this.projectConfigManager.getOptimizelyConfig(); - } catch (e: any) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); + return this.projectConfigManager.getOptimizelyConfig() || null; + } catch (e) { + this.errorReporter.report(e); return null; } } + flushImmediately(): Promise<unknown> { + const flushPromises = []; + + if (!this.isRunning()) { + return Promise.resolve(); + } + + if (this.eventProcessor) { + flushPromises.push(this.eventProcessor.flushImmediately()); + } + + if(this.odpManager) { + flushPromises.push(this.odpManager.flushImmediately()); + } + + return Promise.all(flushPromises); + } + /** * Stop background processes belonging to this instance, including: * @@ -1298,139 +1277,106 @@ export default class Optimizely { * above) are complete. If there are no in-flight event dispatcher requests and * no queued events waiting to be sent, returns an immediately-fulfilled Promise. * - * Returned Promises are fulfilled with result objects containing these - * properties: - * - success (boolean): true if the event dispatcher signaled completion of - * all in-flight and final requests, or if there were no - * queued events and no in-flight requests. false if an - * unexpected error was encountered during the close - * process. - * - reason (string=): If success is false, this is a string property with - * an explanatory message. * * NOTE: After close is called, this instance is no longer usable - any events * generated will no longer be sent to the event dispatcher. * * @return {Promise} */ - close(): Promise<{ success: boolean; reason?: string }> { - try { - const eventProcessorStoppedPromise = this.eventProcessor.stop(); - if (this.disposeOnUpdate) { - this.disposeOnUpdate(); - this.disposeOnUpdate = null; - } - if (this.projectConfigManager) { - this.projectConfigManager.stop(); - } - Object.keys(this.readyTimeouts).forEach( - (readyTimeoutId: string) => { - const readyTimeoutRecord = this.readyTimeouts[readyTimeoutId]; - clearTimeout(readyTimeoutRecord.readyTimeout); - readyTimeoutRecord.onClose(); - } - ); - this.readyTimeouts = {}; - return eventProcessorStoppedPromise.then( - function() { - return { - success: true, - }; - }, - function(err) { - return { - success: false, - reason: String(err), - }; - } - ); - } catch (err: any) { - this.logger.log(LOG_LEVEL.ERROR, err.message); - this.errorHandler.handleError(err); - return Promise.resolve({ - success: false, - reason: String(err), - }); + close(): Promise<unknown> { + this.stop(); + return this.onTerminated(); + } + + stop(): void { + if (this.isDone()) { + return; } + + if (!this.isRunning()) { + this.startPromise.reject(new Error( + sprintf(SERVICE_STOPPED_BEFORE_RUNNING, 'Client') + )); + } + + this.state = ServiceState.Stopping; + + this.projectConfigManager.stop(); + this.eventProcessor?.stop(); + this.odpManager?.stop(); + this.notificationCenter.clearAllNotificationListeners(); + + this.cleanupTasks.forEach((onClose) => onClose()); + + Promise.all([ + this.projectConfigManager.onTerminated(), + this.eventProcessor ? this.eventProcessor.onTerminated() : Promise.resolve(), + this.odpManager ? this.odpManager.onTerminated() : Promise.resolve(), + ]).then(() => { + this.state = ServiceState.Terminated; + this.stopPromise.resolve() + }).catch((err) => { + this.errorReporter.report(err); + this.state = ServiceState.Failed; + this.stopPromise.reject(err); + }); } /** * Returns a Promise that fulfills when this instance is ready to use (meaning - * it has a valid datafile), or has failed to become ready within a period of + * it has a valid datafile), or rejects when it has failed to become ready within a period of * time (configurable by the timeout property of the options argument), or when - * this instance is closed via the close method. + * this instance is closed via the close method before it became ready. * - * If a valid datafile was provided in the constructor, the returned Promise is - * immediately fulfilled. If an sdkKey was provided, a manager will be used to - * fetch a datafile, and the returned promise will fulfill if that fetch - * succeeds or fails before the timeout. The default timeout is 30 seconds, - * which will be used if no timeout is provided in the argument options object. + * If a static project config manager with a valid datafile was provided in the constructor, + * the returned Promise is immediately fulfilled. If a polling config manager was provided, + * it will be used to fetch a datafile, and the returned promise will fulfill if that fetch + * succeeds, or it will reject if the datafile fetch does not complete before the timeout. + * The default timeout is 30 seconds. * - * The returned Promise is fulfilled with a result object containing these - * properties: - * - success (boolean): True if this instance is ready to use with a valid - * datafile, or false if this instance failed to become - * ready or was closed prior to becoming ready. - * - reason (string=): If success is false, this is a string property with - * an explanatory message. Failure could be due to - * expiration of the timeout, network errors, - * unsuccessful responses, datafile parse errors, - * datafile validation errors, or the instance being - * closed + * The returned Promise is fulfilled with an unknown result which is not needed to + * be inspected to know that the instance is ready. If the promise is fulfilled, it + * is guaranteed that the instance is ready to use. If the promise is rejected, it + * means the instance is not ready to use, and the reason for the promise rejection + * will contain an error denoting the cause of failure. + * @param {Object=} options * @param {number|undefined} options.timeout * @return {Promise} */ - onReady(options?: { timeout?: number }): Promise<OnReadyResult> { + onReady(options?: { timeout?: number }): Promise<unknown> { let timeoutValue: number | undefined; if (typeof options === 'object' && options !== null) { if (options.timeout !== undefined) { timeoutValue = options.timeout; } } - if (!fns.isSafeInteger(timeoutValue)) { + if (!isSafeInteger(timeoutValue)) { timeoutValue = DEFAULT_ONREADY_TIMEOUT; } - let resolveTimeoutPromise: (value: OnReadyResult) => void; - const timeoutPromise = new Promise<OnReadyResult>( - (resolve) => { - resolveTimeoutPromise = resolve; - } - ); - - const timeoutId = this.nextReadyTimeoutId; - this.nextReadyTimeoutId++; + const timeoutPromise = resolvablePromise(); - const onReadyTimeout = (() => { - delete this.readyTimeouts[timeoutId]; - resolveTimeoutPromise({ - success: false, - reason: sprintf('onReady timeout expired after %s ms', timeoutValue), - }); - }); - const readyTimeout = setTimeout(onReadyTimeout, timeoutValue); - const onClose = function() { - resolveTimeoutPromise({ - success: false, - reason: 'Instance closed', - }); - }; + const cleanupTaskId = this.nextCleanupTaskId++; - this.readyTimeouts[timeoutId] = { - readyTimeout: readyTimeout, - onClose: onClose, + const onReadyTimeout = () => { + this.cleanupTasks.delete(cleanupTaskId); + timeoutPromise.reject(new Error( + sprintf(ONREADY_TIMEOUT, timeoutValue) + )); }; - - this.readyPromise.then(() => { + + const readyTimeout = setTimeout(onReadyTimeout, timeoutValue); + + this.cleanupTasks.set(cleanupTaskId, () => { clearTimeout(readyTimeout); - delete this.readyTimeouts[timeoutId]; - resolveTimeoutPromise({ - success: true, - }); + timeoutPromise.reject(new Error(INSTANCE_CLOSED)); }); - return Promise.race([this.readyPromise, timeoutPromise]); + return Promise.race([this.onRunning().then(() => { + clearTimeout(readyTimeout); + this.cleanupTasks.delete(cleanupTaskId); + }), timeoutPromise]); } //============ decide ============// @@ -1441,151 +1387,77 @@ export default class Optimizely { * A user context will be created successfully even when the SDK is not fully configured yet, so no * this.isValidInstance() check is performed here. * + * @param {string} userId (Optional) The user ID to be used for bucketing. + * @param {UserAttributes} attributes (Optional) user attributes. + * @return {OptimizelyUserContext|null} An OptimizelyUserContext associated with this OptimizelyClient or + * throws if provided inputs are invalid + */ + createUserContext(userId?: string, attributes?: UserAttributes): OptimizelyUserContext { + const userIdentifier = userId ?? this.vuidManager?.getVuid(); + + if (userIdentifier === undefined || !this.validateInputs({ user_id: userIdentifier })) { + throw new Error(INVALID_IDENTIFIER); + } + + if (!this.validateInputs({}, attributes)) { + throw new Error(INVALID_ATTRIBUTES); + } + + const userContext = new OptimizelyUserContext({ + optimizely: this, + userId: userIdentifier, + attributes, + }); + + this.onRunning().then(() => { + if (this.odpManager && this.isOdpIntegrated()) { + this.odpManager.identifyUser(userIdentifier); + } + }).catch(() => {}); + + return userContext; + } + + /** + * Creates an internal context of the user for which decision APIs will be called. + * + * A user context will be created successfully even when the SDK is not fully configured yet, so no + * this.isValidInstance() check is performed here. + * * @param {string} userId The user ID to be used for bucketing. * @param {UserAttributes} attributes Optional user attributes. * @return {OptimizelyUserContext|null} An OptimizelyUserContext associated with this OptimizelyClient or * null if provided inputs are invalid */ - createUserContext(userId: string, attributes?: UserAttributes): OptimizelyUserContext | null { - if (!this.validateInputs({ user_id: userId }, attributes)) { - return null; - } - + private createInternalUserContext(userId: string, attributes?: UserAttributes): OptimizelyUserContext | null { return new OptimizelyUserContext({ optimizely: this, userId, - attributes + attributes, }); } - decide( - user: OptimizelyUserContext, - key: string, - options: OptimizelyDecideOption[] = [] - ): OptimizelyDecision { - const userId = user.getUserId(); - const attributes = user.getAttributes(); - const configObj = this.projectConfigManager.getConfig(); - const reasons: (string | number)[][] = []; - let decisionObj: DecisionObj; - if (!this.isValidInstance() || !configObj) { - this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'decide'); - return newErrorDecision(key, user, [DECISION_MESSAGES.SDK_NOT_READY]); - } - - const feature = configObj.featureKeyMap[key]; - if (!feature) { - this.logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.FEATURE_NOT_IN_DATAFILE, MODULE_NAME, key); - return newErrorDecision(key, user, [sprintf(DECISION_MESSAGES.FLAG_KEY_INVALID, key)]); - } - - const allDecideOptions = this.getAllDecideOptions(options); - - const forcedDecisionResponse = this.decisionService.findValidatedForcedDecision(configObj, user, key); - reasons.push(...forcedDecisionResponse.reasons); - const variation = forcedDecisionResponse.result; - if (variation) { - decisionObj = { - experiment: null, - variation: variation, - decisionSource: DECISION_SOURCES.FEATURE_TEST - } - } else { - const decisionVariation = this.decisionService.getVariationForFeature( - configObj, - feature, - user, - allDecideOptions, - ); - reasons.push(...decisionVariation.reasons); - decisionObj = decisionVariation.result; - } - const decisionSource = decisionObj.decisionSource; - const experimentKey = decisionObj.experiment?.key ?? null; - const variationKey = decisionObj.variation?.key ?? null; - const flagEnabled: boolean = decision.getFeatureEnabledFromVariation(decisionObj); - if (flagEnabled === true) { - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.FEATURE_ENABLED_FOR_USER, - MODULE_NAME, - key, - userId, - ); - } else { - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.FEATURE_NOT_ENABLED_FOR_USER, - MODULE_NAME, - key, - userId, - ); - } + decide(user: OptimizelyUserContext, key: string, options: OptimizelyDecideOption[] = []): OptimizelyDecision { + const configObj = this.getProjectConfig(); - const variablesMap: { [key: string]: unknown } = {}; - let decisionEventDispatched = false; - - if (!allDecideOptions[OptimizelyDecideOption.EXCLUDE_VARIABLES]) { - feature.variables.forEach(variable => { - variablesMap[variable.key] = - this.getFeatureVariableValueFromVariation( - key, - flagEnabled, - decisionObj.variation, - variable, - userId - ); - }); + if (!configObj) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'decide'); + return newErrorDecision(key, user, [DECISION_MESSAGES.SDK_NOT_READY]); } - if ( - !allDecideOptions[OptimizelyDecideOption.DISABLE_DECISION_EVENT] && ( - decisionSource === DECISION_SOURCES.FEATURE_TEST || - decisionSource === DECISION_SOURCES.ROLLOUT && projectConfig.getSendFlagDecisionsValue(configObj)) - ) { - this.sendImpressionEvent( - decisionObj, - key, - userId, - flagEnabled, - attributes - ) - decisionEventDispatched = true; - } + return this.decideForKeys(user, [key], options, true)[key]; + } - const shouldIncludeReasons = allDecideOptions[OptimizelyDecideOption.INCLUDE_REASONS]; + async decideAsync(user: OptimizelyUserContext, key: string, options: OptimizelyDecideOption[] = []): Promise<OptimizelyDecision> { + const configObj = this.getProjectConfig(); - let reportedReasons: string[] = []; - if (shouldIncludeReasons) { - reportedReasons = reasons.map((reason) => sprintf(reason[0] as string, ...reason.slice(1))); + if (!configObj) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'decide'); + return newErrorDecision(key, user, [DECISION_MESSAGES.SDK_NOT_READY]); } - const featureInfo = { - flagKey: key, - enabled: flagEnabled, - variationKey: variationKey, - ruleKey: experimentKey, - variables: variablesMap, - reasons: reportedReasons, - decisionEventDispatched: decisionEventDispatched, - }; - - this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.DECISION, { - type: DECISION_NOTIFICATION_TYPES.FLAG, - userId: userId, - attributes: attributes, - decisionInfo: featureInfo, - }); - - return { - variationKey: variationKey, - enabled: flagEnabled, - variables: variablesMap, - ruleKey: experimentKey, - flagKey: key, - userContext: user, - reasons: reportedReasons, - }; + const result = await this.decideForKeysAsync(user, [key], options, true); + return result[key]; } /** @@ -1596,19 +1468,14 @@ export default class Optimizely { private getAllDecideOptions(options: OptimizelyDecideOption[]): { [key: string]: boolean } { const allDecideOptions = { ...this.defaultDecideOptions }; if (!Array.isArray(options)) { - this.logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.INVALID_DECIDE_OPTIONS, MODULE_NAME); + this.logger?.debug(INVALID_DECIDE_OPTIONS); } else { - options.forEach((option) => { + options.forEach(option => { // Filter out all provided decide options that are not in OptimizelyDecideOption[] if (OptimizelyDecideOption[option]) { allDecideOptions[option] = true; } else { - this.logger.log( - LOG_LEVEL.WARNING, - LOG_MESSAGES.UNRECOGNIZED_DECIDE_OPTION, - MODULE_NAME, - option, - ); + this.logger?.warn(UNRECOGNIZED_DECIDE_OPTION, option); } }); } @@ -1616,6 +1483,102 @@ export default class Optimizely { return allDecideOptions; } + /** + * Makes a decision for a given feature key. + * + * @param {OptimizelyUserContext} user - The user context associated with this Optimizely client. + * @param {string} key - The feature key for which a decision will be made. + * @param {DecisionObj} decisionObj - The decision object containing decision details. + * @param {DecisionReasons[]} reasons - An array of reasons for the decision. + * @param {Record<string, boolean>} options - A map of options for decision-making. + * @param {projectConfig.ProjectConfig} configObj - The project configuration object. + * @returns {OptimizelyDecision} - The decision object for the feature flag. + */ + private generateDecision( + user: OptimizelyUserContext, + key: string, + decisionObj: DecisionObj, + reasons: DecisionReasons[], + options: Record<string, boolean>, + configObj: projectConfig.ProjectConfig, + ): OptimizelyDecision { + const userId = user.getUserId() + const attributes = user.getAttributes() + const feature = configObj.featureKeyMap[key] + const decisionSource = decisionObj.decisionSource; + const experimentKey = decisionObj.experiment?.key ?? null; + const experimentId = decisionObj.experiment?.id ?? null; + const variationKey = decisionObj.variation?.key ?? null; + const variationId = decisionObj.variation?.id ?? null; + const flagEnabled: boolean = decision.getFeatureEnabledFromVariation(decisionObj); + const variablesMap: { [key: string]: unknown } = {}; + let decisionEventDispatched = false; + + if (flagEnabled) { + this.logger?.info(FEATURE_ENABLED_FOR_USER, key, userId); + } else { + this.logger?.info(FEATURE_NOT_ENABLED_FOR_USER, key, userId); + } + + if (!options[OptimizelyDecideOption.EXCLUDE_VARIABLES]) { + feature.variables.forEach(variable => { + variablesMap[variable.key] = this.getFeatureVariableValueFromVariation( + key, + flagEnabled, + decisionObj.variation, + variable, + userId + ); + }); + } + + if ( + !options[OptimizelyDecideOption.DISABLE_DECISION_EVENT] && + (decisionSource === DECISION_SOURCES.FEATURE_TEST || + decisionSource === DECISION_SOURCES.HOLDOUT || + (decisionSource === DECISION_SOURCES.ROLLOUT && projectConfig.getSendFlagDecisionsValue(configObj))) + ) { + this.sendImpressionEvent(decisionObj, key, userId, flagEnabled, attributes); + decisionEventDispatched = true; + } + + const shouldIncludeReasons = options[OptimizelyDecideOption.INCLUDE_REASONS]; + + let reportedReasons: string[] = []; + if (shouldIncludeReasons) { + reportedReasons = reasons.map(reason => sprintf(reason[0] as string, ...reason.slice(1))); + } + + const featureInfo = { + flagKey: key, + enabled: flagEnabled, + variationKey: variationKey, + ruleKey: experimentKey, + variables: variablesMap, + reasons: reportedReasons, + decisionEventDispatched: decisionEventDispatched, + experimentId: experimentId, + variationId: variationId, + }; + + this.notificationCenter.sendNotifications(NOTIFICATION_TYPES.DECISION, { + type: DECISION_NOTIFICATION_TYPES.FLAG, + userId: userId, + attributes: attributes, + decisionInfo: featureInfo, + }); + + return { + variationKey: variationKey, + enabled: flagEnabled, + variables: variablesMap, + ruleKey: experimentKey, + flagKey: key, + userContext: user, + reasons: reportedReasons, + }; + } + /** * Returns an object of decision results for multiple flag keys and a user context. * If the SDK finds an error for a key, the response will include a decision for the key showing reasons for the error. @@ -1628,26 +1591,95 @@ export default class Optimizely { decideForKeys( user: OptimizelyUserContext, keys: string[], - options: OptimizelyDecideOption[] = [] - ): { [key: string]: OptimizelyDecision } { - const decisionMap: { [key: string]: OptimizelyDecision } = {}; - if (!this.isValidInstance()) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'decideForKeys'); - return decisionMap; + options: OptimizelyDecideOption[] = [], + ignoreEnabledFlagOption?:boolean + ): Record<string, OptimizelyDecision> { + return this.getDecisionForKeys('sync', user, keys, options, ignoreEnabledFlagOption).get(); + } + + decideForKeysAsync( + user: OptimizelyUserContext, + keys: string[], + options: OptimizelyDecideOption[] = [], + ignoreEnabledFlagOption?:boolean + ): Promise<Record<string, OptimizelyDecision>> { + return this.getDecisionForKeys('async', user, keys, options, ignoreEnabledFlagOption).get(); + } + + private getDecisionForKeys<OP extends OpType>( + op: OP, + user: OptimizelyUserContext, + keys: string[], + options: OptimizelyDecideOption[] = [], + ignoreEnabledFlagOption?:boolean + ): Value<OP, Record<string, OptimizelyDecision>> { + const decisionMap: Record<string, OptimizelyDecision> = {}; + const flagDecisions: Record<string, DecisionObj> = {}; + const decisionReasonsMap: Record<string, DecisionReasons[]> = {}; + + const configObj = this.getProjectConfig() + + if (!configObj) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'decideForKeys'); + return Value.of(op, decisionMap); } + if (keys.length === 0) { - return decisionMap; + return Value.of(op, decisionMap); } const allDecideOptions = this.getAllDecideOptions(options); - keys.forEach(key => { - const optimizelyDecision: OptimizelyDecision = this.decide(user, key, options); - if (!allDecideOptions[OptimizelyDecideOption.ENABLED_FLAGS_ONLY] || optimizelyDecision.enabled) { - decisionMap[key] = optimizelyDecision; + + if (ignoreEnabledFlagOption) { + delete allDecideOptions[OptimizelyDecideOption.ENABLED_FLAGS_ONLY]; + } + + const validFlags: FeatureFlag[] = []; + + for(const key of keys) { + const feature = configObj.featureKeyMap[key]; + if (!feature) { + this.logger?.error(FEATURE_NOT_IN_DATAFILE, key); + decisionMap[key] = newErrorDecision(key, user, [sprintf(DECISION_MESSAGES.FLAG_KEY_INVALID, key)]); + continue; } - }); - return decisionMap; + validFlags.push(feature); + } + + return this.decisionService.resolveVariationsForFeatureList(op, configObj, validFlags, user, allDecideOptions) + .then((decisionList) => { + for(let i = 0; i < validFlags.length; i++) { + const key = validFlags[i].key; + const decision = decisionList[i]; + + if(decision.error) { + decisionMap[key] = newErrorDecision(key, user, decision.reasons.map(r => sprintf(r[0], ...r.slice(1)))); + } else { + flagDecisions[key] = decision.result; + decisionReasonsMap[key] = decision.reasons; + } + } + + for(const validFlag of validFlags) { + const validKey = validFlag.key; + + // if there is already a value for this flag, that must have come from + // the newErrorDecision above, so we skip it + if (decisionMap[validKey]) { + continue; + } + + const decision = this.generateDecision(user, validKey, flagDecisions[validKey], decisionReasonsMap[validKey], allDecideOptions, configObj); + + if(!allDecideOptions[OptimizelyDecideOption.ENABLED_FLAGS_ONLY] || decision.enabled) { + decisionMap[validKey] = decision; + } + } + + return Value.of(op, decisionMap); + }, + ); } /** @@ -1660,10 +1692,10 @@ export default class Optimizely { user: OptimizelyUserContext, options: OptimizelyDecideOption[] = [] ): { [key: string]: OptimizelyDecision } { - const configObj = this.projectConfigManager.getConfig(); const decisionMap: { [key: string]: OptimizelyDecision } = {}; - if (!this.isValidInstance() || !configObj) { - this.logger.log(LOG_LEVEL.ERROR, LOG_MESSAGES.INVALID_OBJECT, MODULE_NAME, 'decideAll'); + const configObj = this.getProjectConfig(); + if (!configObj) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'decideAll'); return decisionMap; } @@ -1672,4 +1704,98 @@ export default class Optimizely { return this.decideForKeys(user, allFlagKeys, options); } + async decideAllAsync( + user: OptimizelyUserContext, + options: OptimizelyDecideOption[] = [] + ): Promise<Record<string, OptimizelyDecision>> { + const decisionMap: { [key: string]: OptimizelyDecision } = {}; + const configObj = this.getProjectConfig(); + if (!configObj) { + this.errorReporter.report(NO_PROJECT_CONFIG_FAILURE, 'decideAll'); + return decisionMap; + } + + const allFlagKeys = Object.keys(configObj.featureKeyMap); + + return this.decideForKeysAsync(user, allFlagKeys, options); + } + + /** + * Updates ODP Config with most recent ODP key, host, pixelUrl, and segments from the project config + */ + private updateOdpSettings(): void { + const projectConfig = this.getProjectConfig(); + + if (!projectConfig) { + return; + } + + if (this.odpManager) { + this.odpManager.updateConfig(projectConfig.odpIntegrationConfig); + } + } + + /** + * Sends an action as an ODP Event with optional custom parameters including type, identifiers, and data + * Note: Since this depends on this.odpManager, it must await Optimizely client's onReady() promise resolution. + * @param {string} action Subcategory of the event type (i.e. "client_initialized", "identified", or a custom action) + * @param {string} type (Optional) Type of event (Defaults to "fullstack") + * @param {Map<string, string>} identifiers (Optional) Key-value map of user identifiers + * @param {Map<string, string>} data (Optional) Event data in a key-value map. + */ + public sendOdpEvent( + action: string, + type?: string, + identifiers?: Map<string, string>, + data?: Map<string, unknown> + ): void { + if (!this.odpManager) { + this.logger?.error(ODP_EVENT_FAILED_ODP_MANAGER_MISSING); + return; + } + + try { + const odpEvent = new OdpEvent(type || '', action, identifiers, data); + this.odpManager.sendEvent(odpEvent); + } catch (e) { + this.logger?.error(ODP_EVENT_FAILED, e); + } + } + /** + * Checks if ODP (Optimizely Data Platform) is integrated into the project. + * @returns { boolean } `true` if ODP settings were found in the datafile otherwise `false` + */ + public isOdpIntegrated(): boolean { + return this.getProjectConfig()?.odpIntegrationConfig?.integrated ?? false; + } + + /** + * Fetches list of qualified segments from ODP for a particular userId. + * @param {string} userId + * @param {Array<OptimizelySegmentOption>} options + * @returns {Promise<string[] | null>} + */ + public async fetchQualifiedSegments( + userId: string, + options?: Array<OptimizelySegmentOption> + ): Promise<string[] | null> { + if (!this.odpManager) { + return null; + } + + return await this.odpManager.fetchQualifiedSegments(userId, options); + } + + /** + * @returns {string|undefined} Currently provisioned VUID from local ODP Manager or undefined if + * ODP Manager has not been instantiated yet for any reason. + */ + public getVuid(): string | undefined { + if (!this.vuidManager) { + this.logger?.error(UNABLE_TO_GET_VUID_VUID_MANAGER_NOT_AVAILABLE); + return undefined; + } + + return this.vuidManager.getVuid(); + } } diff --git a/packages/optimizely-sdk/lib/optimizely_decision/index.ts b/lib/optimizely_decision/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/optimizely_decision/index.ts rename to lib/optimizely_decision/index.ts diff --git a/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js b/lib/optimizely_user_context/index.tests.js similarity index 69% rename from packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js rename to lib/optimizely_user_context/index.tests.js index 3e7f9a746..9f3597187 100644 --- a/packages/optimizely-sdk/lib/optimizely_user_context/index.tests.js +++ b/lib/optimizely_user_context/index.tests.js @@ -1,54 +1,96 @@ -/**************************************************************************** - * Copyright 2020-2022, Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ +/** + * Copyright 2020-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ import { assert } from 'chai'; import sinon from 'sinon'; - -import * as logging from '../modules/logging'; import { sprintf } from '../utils/fns'; -import { NOTIFICATION_TYPES } from '../utils/enums'; - +import { NOTIFICATION_TYPES } from '../notification_center/type'; import OptimizelyUserContext from './'; -import { createLogger } from '../plugins/logger'; -import { createEventProcessor } from '../plugins/event_processor'; -import { createNotificationCenter } from '../core/notification_center'; +import { createNotificationCenter } from '../notification_center'; import Optimizely from '../optimizely'; -import errorHandler from '../plugins/error_handler'; -import eventDispatcher from '../plugins/event_dispatcher/index.node'; -import { CONTROL_ATTRIBUTES, LOG_LEVEL, LOG_MESSAGES } from '../utils/enums'; +import { LOG_LEVEL } from '../utils/enums'; import testData from '../tests/test_data'; import { OptimizelyDecideOption } from '../shared_types'; +import { getMockProjectConfigManager } from '../tests/mock/mock_project_config_manager'; +import { createProjectConfig } from '../project_config/project_config'; +import { getForwardingEventProcessor } from '../event_processor/event_processor_factory'; +import { FORCED_DECISION_NULL_RULE_KEY } from './index' + +import { + USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, + USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, +} from '../core/decision_service'; + +const getMockEventDispatcher = () => { + const dispatcher = { + dispatchEvent: sinon.spy(() => Promise.resolve({ statusCode: 200 })), + } + return dispatcher; +} + +var createLogger = () => ({ + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + child: () => createLogger(), +}); + +const getOptlyInstance = ({ datafileObj, defaultDecideOptions }) => { + const createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); + const mockConfigManager = getMockProjectConfigManager({ + initConfig: createProjectConfig(datafileObj), + }); + const eventDispatcher = getMockEventDispatcher(); + const eventProcessor = getForwardingEventProcessor(eventDispatcher); + + const optlyInstance = new Optimizely({ + clientEngine: 'node-sdk', + projectConfigManager: mockConfigManager, + eventProcessor, + logger: createdLogger, + isValidInstance: true, + eventBatchSize: 1, + defaultDecideOptions: defaultDecideOptions || [], + }); + + + return { optlyInstance, eventProcessor, eventDispatcher, createdLogger } +} describe('lib/optimizely_user_context', function() { describe('APIs', function() { var fakeOptimizely; var userId = 'tester'; - var options = 'fakeOption'; + var options = ['fakeOption']; describe('#setAttribute', function() { fakeOptimizely = { - decide: sinon.stub().returns({}) - } + decide: sinon.stub().returns({}), + }; it('should set attributes when provided at instantiation of OptimizelyUserContext', function() { var userId = 'user1'; var attributes = { test_attribute: 'test_value' }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, - attributes + attributes, }); - user.setAttribute('k1', { 'hello': 'there' }); + user.setAttribute('k1', { hello: 'there' }); user.setAttribute('k2', true); user.setAttribute('k3', 100); user.setAttribute('k4', 3.5); @@ -57,7 +99,7 @@ describe('lib/optimizely_user_context', function() { var newAttributes = user.getAttributes(); assert.deepEqual(newAttributes['test_attribute'], 'test_value'); - assert.deepEqual(newAttributes['k1'], { 'hello': 'there' }); + assert.deepEqual(newAttributes['k1'], { hello: 'there' }); assert.deepEqual(newAttributes['k2'], true); assert.deepEqual(newAttributes['k3'], 100); assert.deepEqual(newAttributes['k4'], 3.5); @@ -66,10 +108,11 @@ describe('lib/optimizely_user_context', function() { it('should set attributes when none provided at instantiation of OptimizelyUserContext', function() { var userId = 'user2'; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, }); - user.setAttribute('k1', { 'hello': 'there' }); + user.setAttribute('k1', { hello: 'there' }); user.setAttribute('k2', true); user.setAttribute('k3', 100); user.setAttribute('k4', 3.5); @@ -77,7 +120,7 @@ describe('lib/optimizely_user_context', function() { assert.deepEqual(user.getUserId(), userId); var newAttributes = user.getAttributes(); - assert.deepEqual(newAttributes['k1'], { 'hello': 'there' }); + assert.deepEqual(newAttributes['k1'], { hello: 'there' }); assert.deepEqual(newAttributes['k2'], true); assert.deepEqual(newAttributes['k3'], 100); assert.deepEqual(newAttributes['k4'], 3.5); @@ -87,17 +130,18 @@ describe('lib/optimizely_user_context', function() { var userId = 'user3'; var attributes = { test_attribute: 'test_value' }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, attributes, }); - user.setAttribute('k1', { 'hello': 'there' }); + user.setAttribute('k1', { hello: 'there' }); user.setAttribute('test_attribute', 'overwritten_value'); assert.deepEqual(user.getOptimizely(), fakeOptimizely); assert.deepEqual(user.getUserId(), userId); var newAttributes = user.getAttributes(); - assert.deepEqual(newAttributes['k1'], { 'hello': 'there' }); + assert.deepEqual(newAttributes['k1'], { hello: 'there' }); assert.deepEqual(newAttributes['test_attribute'], 'overwritten_value'); assert.deepEqual(Object.keys(newAttributes).length, 2); }); @@ -105,6 +149,7 @@ describe('lib/optimizely_user_context', function() { it('should allow to set attributes with value of null', function() { var userId = 'user4'; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, }); @@ -120,31 +165,33 @@ describe('lib/optimizely_user_context', function() { var userId = 'user1'; var attributes = { initial_attribute: 'initial_value' }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, attributes, }); - attributes["attribute2"] = 100; + attributes['attribute2'] = 100; assert.deepEqual(user.getAttributes(), { initial_attribute: 'initial_value' }); - user.setAttribute("attribute3", "hello"); - assert.deepEqual(attributes, { initial_attribute: 'initial_value', 'attribute2': 100 }); + user.setAttribute('attribute3', 'hello'); + assert.deepEqual(attributes, { initial_attribute: 'initial_value', attribute2: 100 }); }); it('should not change user attributes if returned by getAttributes object is updated', function() { var userId = 'user1'; var attributes = { initial_attribute: 'initial_value' }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, attributes, }); var attributes2 = user.getAttributes(); - attributes2['new_attribute'] = { "value": 100 }; + attributes2['new_attribute'] = { value: 100 }; assert.deepEqual(user.getAttributes(), attributes); var expectedAttributes = { initial_attribute: 'initial_value', - new_attribute: { "value": 100 } - } + new_attribute: { value: 100 }, + }; assert.deepEqual(attributes2, expectedAttributes); }); }); @@ -162,30 +209,25 @@ describe('lib/optimizely_user_context', function() { reasons: [], }; fakeOptimizely = { - decide: sinon.stub().returns(fakeDecision) + decide: sinon.stub().returns(fakeDecision), }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, }); var decision = user.decide(flagKey, options); - sinon.assert.calledWithExactly( - fakeOptimizely.decide, - user, - flagKey, - options - ); + sinon.assert.calledWithExactly(fakeOptimizely.decide, user, flagKey, options); assert.deepEqual(decision, fakeDecision); }); - }); + }); describe('#decideForKeys', function() { it('should return an expected decision results object', function() { var flagKey1 = 'feature_1'; var flagKey2 = 'feature_2'; var fakeDecisionMap = { - flagKey1: - { + flagKey1: { variationKey: '18257766532', enabled: true, variables: {}, @@ -194,8 +236,7 @@ describe('lib/optimizely_user_context', function() { userContext: 'fakeUserContext', reasons: [], }, - flagKey2: - { + flagKey2: { variationKey: 'variation_with_traffic', enabled: true, variables: {}, @@ -206,19 +247,15 @@ describe('lib/optimizely_user_context', function() { }, }; fakeOptimizely = { - decideForKeys: sinon.stub().returns(fakeDecisionMap) + decideForKeys: sinon.stub().returns(fakeDecisionMap), }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, }); var decisionMap = user.decideForKeys([flagKey1, flagKey2], options); - sinon.assert.calledWithExactly( - fakeOptimizely.decideForKeys, - user, - [flagKey1, flagKey2], - options - ); + sinon.assert.calledWithExactly(fakeOptimizely.decideForKeys, user, [flagKey1, flagKey2], options); assert.deepEqual(decisionMap, fakeDecisionMap); }); }); @@ -229,8 +266,7 @@ describe('lib/optimizely_user_context', function() { var flagKey2 = 'feature_2'; var flagKey3 = 'feature_3'; var fakeDecisionMap = { - flagKey1: - { + flagKey1: { variationKey: '18257766532', enabled: true, variables: {}, @@ -239,8 +275,7 @@ describe('lib/optimizely_user_context', function() { userContext: 'fakeUserContext', reasons: [], }, - flagKey2: - { + flagKey2: { variationKey: 'variation_with_traffic', enabled: true, variables: {}, @@ -249,8 +284,7 @@ describe('lib/optimizely_user_context', function() { userContext: 'fakeUserContext', reasons: [], }, - flagKey3: - { + flagKey3: { variationKey: '', enabled: false, variables: {}, @@ -261,18 +295,15 @@ describe('lib/optimizely_user_context', function() { }, }; fakeOptimizely = { - decideAll: sinon.stub().returns(fakeDecisionMap) + decideAll: sinon.stub().returns(fakeDecisionMap), }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, }); var decisionMap = user.decideAll(options); - sinon.assert.calledWithExactly( - fakeOptimizely.decideAll, - user, - options - ); + sinon.assert.calledWithExactly(fakeOptimizely.decideAll, user, options); assert.deepEqual(decisionMap, fakeDecisionMap); }); }); @@ -280,11 +311,12 @@ describe('lib/optimizely_user_context', function() { describe('#trackEvent', function() { it('should call track from optimizely client', function() { fakeOptimizely = { - track: sinon.stub() + track: sinon.stub(), }; var eventName = 'myEvent'; - var eventTags = { 'eventTag1': 1000 } + var eventTags = { eventTag1: 1000 }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, }); @@ -294,93 +326,94 @@ describe('lib/optimizely_user_context', function() { eventName, user.getUserId(), user.getAttributes(), - eventTags, + eventTags ); }); }); describe('#setForcedDecision', function() { - var createdLogger = createLogger({ + let createdLogger = createLogger({ logLevel: LOG_LEVEL.DEBUG, logToConsole: false, }); - var stubLogHandler; + + let optlyInstance, eventDispatcher; + beforeEach(function() { - stubLogHandler = { - log: sinon.stub(), - }; - logging.setLogLevel('notset'); - logging.setLogHandler(stubLogHandler); + ({ optlyInstance, createdLogger, eventDispatcher} = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + })); }); + afterEach(function() { - logging.resetLogger(); + eventDispatcher.dispatchEvent.reset(); }); + it('should return true when client is not ready', function() { fakeOptimizely = { - isValidInstance: sinon.stub().returns(false) + isValidInstance: sinon.stub().returns(false), }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, }); var result = user.setForcedDecision({ flagKey: 'feature_1' }, '3324490562'); assert.strictEqual(result, true); - sinon.assert.notCalled(stubLogHandler.log); }); it('should return true when provided empty string flagKey', function() { fakeOptimizely = { - isValidInstance: sinon.stub().returns(true) + isValidInstance: sinon.stub().returns(true), }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId: 'user123', }); var result = user.setForcedDecision({ flagKey: '' }, '3324490562'); assert.strictEqual(result, true); - sinon.assert.notCalled(stubLogHandler.log); }); it('should return true when provided flagKey and variationKey', function() { fakeOptimizely = { - isValidInstance: sinon.stub().returns(true) + isValidInstance: sinon.stub().returns(true), }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId: 'user123', }); var result = user.setForcedDecision({ flagKey: 'feature_1' }, '3324490562'); assert.strictEqual(result, true); - sinon.assert.notCalled(stubLogHandler.log); }); describe('when valid forced decision is set', function() { var optlyInstance; - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); - var eventProcessor = createEventProcessor({ - dispatcher: eventDispatcher, - batchSize: 1, - notificationCenter: notificationCenter, - }); + var notificationCenter = createNotificationCenter({ logger: createdLogger }); + var eventDispatcher = getMockEventDispatcher(); + var eventProcessor = getForwardingEventProcessor( + eventDispatcher, + ); beforeEach(function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), - errorHandler: errorHandler, + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()) + }), eventProcessor, isValidInstance: true, logger: createdLogger, notificationCenter, }); - sinon.stub(optlyInstance.decisionService.logger, 'log') - sinon.stub(eventDispatcher, 'dispatchEvent'); + sinon.stub(optlyInstance.decisionService.logger, 'log'); sinon.stub(optlyInstance.notificationCenter, 'sendNotifications'); }); afterEach(function() { - optlyInstance.decisionService.logger.log.restore(); - eventDispatcher.dispatchEvent.restore(); + // optlyInstance.decisionService.logger.log.restore(); + eventDispatcher.dispatchEvent.reset(); optlyInstance.notificationCenter.sendNotifications.restore(); }); @@ -388,11 +421,11 @@ describe('lib/optimizely_user_context', function() { var flagKey = 'feature_1'; var ruleKey = 'exp_with_audience'; var variationKey = '3324490633'; - + var user = optlyInstance.createUserContext(userId); user.setForcedDecision({ flagKey: flagKey, ruleKey }, { variationKey }); var decision = user.decide(flagKey, options); - + assert.equal(decision.variationKey, variationKey); assert.equal(decision.ruleKey, ruleKey); assert.equal(decision.enabled, true); @@ -406,29 +439,50 @@ describe('lib/optimizely_user_context', function() { var featureKey = 'feature_1'; var variationKey = '3324490562'; user.setForcedDecision({ flagKey: featureKey }, { variationKey }); - var decision = user.decide( - featureKey, - [ - OptimizelyDecideOption.INCLUDE_REASONS, - OptimizelyDecideOption.DISABLE_DECISION_EVENT - ] + var decision = user.decide(featureKey, [ + OptimizelyDecideOption.INCLUDE_REASONS, + OptimizelyDecideOption.DISABLE_DECISION_EVENT, + ]); + assert.equal(decision.variationKey, variationKey); + assert.equal(decision.ruleKey, null); + assert.equal(decision.enabled, true); + assert.equal(decision.userContext.getUserId(), userId); + assert.deepEqual(decision.userContext.getAttributes(), {}); + assert.deepEqual(Object.keys(decision.userContext.forcedDecisionsMap).length, 1); + assert.deepEqual( + decision.userContext.forcedDecisionsMap[featureKey][FORCED_DECISION_NULL_RULE_KEY], + { variationKey } + ); + assert.equal( + true, + decision.reasons.includes( + sprintf(USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, featureKey, userId) + ) ); + + sinon.assert.notCalled(eventDispatcher.dispatchEvent); + }); + + it('should return forced decision object when forced decision is set for a flag and do NOT dispatch an event with DISABLE_DECISION_EVENT string passed in decide options', function() { + var user = optlyInstance.createUserContext(userId); + var featureKey = 'feature_1'; + var variationKey = '3324490562'; + user.setForcedDecision({ flagKey: featureKey }, { variationKey }); + var decision = user.decide(featureKey, ['INCLUDE_REASONS', 'DISABLE_DECISION_EVENT']); assert.equal(decision.variationKey, variationKey); assert.equal(decision.ruleKey, null); assert.equal(decision.enabled, true); assert.equal(decision.userContext.getUserId(), userId); assert.deepEqual(decision.userContext.getAttributes(), {}); assert.deepEqual(Object.keys(decision.userContext.forcedDecisionsMap).length, 1); - assert.deepEqual(decision.userContext.forcedDecisionsMap[featureKey][CONTROL_ATTRIBUTES.FORCED_DECISION_NULL_RULE_KEY], { variationKey }); + assert.deepEqual( + decision.userContext.forcedDecisionsMap[featureKey][FORCED_DECISION_NULL_RULE_KEY], + { variationKey } + ); assert.equal( true, decision.reasons.includes( - sprintf( - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, - variationKey, - featureKey, - userId, - ) + sprintf(USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, featureKey, userId) ) ); @@ -436,6 +490,13 @@ describe('lib/optimizely_user_context', function() { }); it('should return forced decision object when forced decision is set for a flag and dispatch an event', function() { + const { optlyInstance, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + + const notificationCenter = optlyInstance.notificationCenter; + sinon.stub(notificationCenter, 'sendNotifications'); + var user = optlyInstance.createUserContext(userId); var featureKey = 'feature_1'; var variationKey = '3324490562'; @@ -448,16 +509,14 @@ describe('lib/optimizely_user_context', function() { assert.equal(decision.userContext.getUserId(), userId); assert.deepEqual(decision.userContext.getAttributes(), {}); assert.deepEqual(Object.values(decision.userContext.forcedDecisionsMap).length, 1); - assert.deepEqual(decision.userContext.forcedDecisionsMap[featureKey][CONTROL_ATTRIBUTES.FORCED_DECISION_NULL_RULE_KEY], { variationKey }); + assert.deepEqual( + decision.userContext.forcedDecisionsMap[featureKey][FORCED_DECISION_NULL_RULE_KEY], + { variationKey } + ); assert.equal( true, decision.reasons.includes( - sprintf( - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, - variationKey, - featureKey, - userId, - ) + sprintf(USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, featureKey, userId) ) ); @@ -476,8 +535,8 @@ describe('lib/optimizely_user_context', function() { assert.equal(metadata.variation_key, variationKey); assert.equal(metadata.enabled, true); - sinon.assert.callCount(optlyInstance.notificationCenter.sendNotifications, 3); - var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(2).args; + sinon.assert.callCount(notificationCenter.sendNotifications, 3); + var notificationCallArgs = notificationCenter.sendNotifications.getCall(2).args; var expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -500,19 +559,28 @@ describe('lib/optimizely_user_context', function() { decisionEventDispatched: true, reasons: [ sprintf( - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, + USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, variationKey, featureKey, - userId, - ) + userId + ), ], + experimentId: null, + variationId: '3324490562' }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); it('should return forced decision object when forced decision is set for an experiment rule and dispatch an event', function() { + const { optlyInstance, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + + const notificationCenter = optlyInstance.notificationCenter; + sinon.stub(notificationCenter, 'sendNotifications'); + var attributes = { country: 'US' }; var user = optlyInstance.createUserContext(userId, attributes); var featureKey = 'feature_1'; @@ -533,11 +601,11 @@ describe('lib/optimizely_user_context', function() { true, decision.reasons.includes( sprintf( - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, variationKey, featureKey, ruleKey, - userId, + userId ) ) ); @@ -557,8 +625,8 @@ describe('lib/optimizely_user_context', function() { assert.equal(metadata.variation_key, 'b'); assert.equal(metadata.enabled, false); - sinon.assert.callCount(optlyInstance.notificationCenter.sendNotifications, 3); - var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(2).args; + sinon.assert.callCount(notificationCenter.sendNotifications, 3); + var notificationCallArgs = notificationCenter.sendNotifications.getCall(2).args; var expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -580,22 +648,30 @@ describe('lib/optimizely_user_context', function() { }, decisionEventDispatched: true, reasons: [ - sprintf( - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, variationKey, featureKey, ruleKey, - userId, - ) + userId + ), ], + experimentId: '10390977673', + variationId: '10416523121', }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); it('should return forced decision object when forced decision is set for a delivery rule and dispatch an event', function() { + const { optlyInstance, eventDispatcher } = getOptlyInstance({ + datafileObj: testData.getTestDecideProjectConfig(), + }); + + const notificationCenter = optlyInstance.notificationCenter; + sinon.stub(notificationCenter, 'sendNotifications'); + var user = optlyInstance.createUserContext(userId); var featureKey = 'feature_1'; var variationKey = '3324490633'; @@ -612,14 +688,17 @@ describe('lib/optimizely_user_context', function() { assert.deepEqual(Object.keys(decision.userContext.forcedDecisionsMap[featureKey]).length, 1); assert.deepEqual(decision.userContext.forcedDecisionsMap[featureKey][ruleKey], { variationKey }); - sinon.assert.called(stubLogHandler.log); - var logMessage = optlyInstance.decisionService.logger.log.args[4]; - assert.strictEqual(logMessage[0], 2); - assert.strictEqual(logMessage[1], 'Variation (%s) is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.'); - assert.strictEqual(logMessage[2], variationKey); - assert.strictEqual(logMessage[3], featureKey); - assert.strictEqual(logMessage[4], ruleKey); - assert.strictEqual(logMessage[5], userId); + // sinon.assert.called(stubLogHandler.log); + // var logMessage = optlyInstance.decisionService.logger.log.args[4]; + // assert.strictEqual(logMessage[0], 2); + // assert.strictEqual( + // logMessage[1], + // 'Variation (%s) is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.' + // ); + // assert.strictEqual(logMessage[2], variationKey); + // assert.strictEqual(logMessage[3], featureKey); + // assert.strictEqual(logMessage[4], ruleKey); + // assert.strictEqual(logMessage[5], userId); sinon.assert.calledOnce(eventDispatcher.dispatchEvent); var callArgs = eventDispatcher.dispatchEvent.getCalls()[0].args; @@ -636,8 +715,8 @@ describe('lib/optimizely_user_context', function() { assert.equal(metadata.variation_key, '3324490633'); assert.equal(metadata.enabled, true); - sinon.assert.callCount(optlyInstance.notificationCenter.sendNotifications, 3); - var notificationCallArgs = optlyInstance.notificationCenter.sendNotifications.getCall(2).args; + sinon.assert.callCount(notificationCenter.sendNotifications, 3); + var notificationCallArgs = notificationCenter.sendNotifications.getCall(2).args; var expectedNotificationCallArgs = [ NOTIFICATION_TYPES.DECISION, { @@ -659,26 +738,28 @@ describe('lib/optimizely_user_context', function() { }, decisionEventDispatched: true, reasons: [], + experimentId: '3332020515', + variationId: '3324490633', }, - } - ] + }, + ]; assert.deepEqual(notificationCallArgs, expectedNotificationCallArgs); }); }); describe('when invalid forced decision is set', function() { var optlyInstance; - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); - var eventProcessor = createEventProcessor({ - dispatcher: eventDispatcher, - batchSize: 1, - notificationCenter: notificationCenter, - }); + var notificationCenter = createNotificationCenter({ logger: createdLogger }); + var eventDispatcher = getMockEventDispatcher(); + var eventProcessor = getForwardingEventProcessor( + eventDispatcher, + ); beforeEach(function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), - errorHandler: errorHandler, + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()) + }), eventProcessor, isValidInstance: true, logger: createdLogger, @@ -686,6 +767,10 @@ describe('lib/optimizely_user_context', function() { }); }); + afterEach(() => { + eventDispatcher.dispatchEvent.reset(); + }) + it('should NOT return forced decision object when forced decision is set for a flag', function() { var user = optlyInstance.createUserContext(userId); var featureKey = 'feature_1'; @@ -697,15 +782,14 @@ describe('lib/optimizely_user_context', function() { assert.equal(decision.variationKey, '18257766532'); assert.equal(decision.ruleKey, '18322080788'); assert.deepEqual(Object.keys(decision.userContext.forcedDecisionsMap).length, 1); - assert.deepEqual(decision.userContext.forcedDecisionsMap[featureKey][CONTROL_ATTRIBUTES.FORCED_DECISION_NULL_RULE_KEY], { variationKey }); + assert.deepEqual( + decision.userContext.forcedDecisionsMap[featureKey][FORCED_DECISION_NULL_RULE_KEY], + { variationKey } + ); assert.equal( true, decision.reasons.includes( - sprintf( - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, - featureKey, - userId, - ) + sprintf(USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, featureKey, userId) ) ); }); @@ -728,10 +812,10 @@ describe('lib/optimizely_user_context', function() { true, decision.reasons.includes( sprintf( - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, featureKey, ruleKey, - userId, + userId ) ) ); @@ -755,10 +839,10 @@ describe('lib/optimizely_user_context', function() { true, decision.reasons.includes( sprintf( - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, + USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, featureKey, ruleKey, - userId, + userId ) ) ); @@ -767,21 +851,21 @@ describe('lib/optimizely_user_context', function() { describe('when forced decision is set for a flag and an experiment rule', function() { var optlyInstance; - var createdLogger = createLogger({ + const createdLogger = createLogger({ logLevel: LOG_LEVEL.DEBUG, logToConsole: false, }); - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); - var eventProcessor = createEventProcessor({ - dispatcher: eventDispatcher, - batchSize: 1, - notificationCenter: notificationCenter, - }); + var notificationCenter = createNotificationCenter({ logger: createdLogger }); + var eventDispatcher = getMockEventDispatcher(); + var eventProcessor = getForwardingEventProcessor( + eventDispatcher, + ); beforeEach(function() { optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), - errorHandler: errorHandler, + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()) + }), eventProcessor, isValidInstance: true, logger: createdLogger, @@ -789,6 +873,10 @@ describe('lib/optimizely_user_context', function() { }); }); + afterEach(() => { + eventDispatcher.dispatchEvent.reset(); + }); + it('should prioritize flag forced decision over experiment rule', function() { var user = optlyInstance.createUserContext(userId); var featureKey = 'feature_1'; @@ -810,20 +898,20 @@ describe('lib/optimizely_user_context', function() { describe('#getForcedDecision', function() { it('should return correct forced variation', function() { - var createdLogger = createLogger({ + const createdLogger = createLogger({ logLevel: LOG_LEVEL.DEBUG, logToConsole: false, }); - var notificationCenter = createNotificationCenter({ logger: createdLogger, errorHandler: errorHandler }); - var eventProcessor = createEventProcessor({ - dispatcher: eventDispatcher, - batchSize: 1, - notificationCenter: notificationCenter, - }); + var notificationCenter = createNotificationCenter({ logger: createdLogger }); + var eventDispatcher = getMockEventDispatcher(); + var eventProcessor = getForwardingEventProcessor( + eventDispatcher, + ); var optlyInstance = new Optimizely({ clientEngine: 'node-sdk', - datafile: testData.getTestDecideProjectConfig(), - errorHandler: errorHandler, + projectConfigManager: getMockProjectConfigManager({ + initConfig: createProjectConfig(testData.getTestDecideProjectConfig()) + }), eventProcessor, isValidInstance: true, logger: createdLogger, @@ -851,23 +939,12 @@ describe('lib/optimizely_user_context', function() { }); describe('#removeForcedDecision', function() { - var stubLogHandler; - beforeEach(function() { - stubLogHandler = { - log: sinon.stub(), - }; - logging.setLogLevel('notset'); - logging.setLogHandler(stubLogHandler); - }); - afterEach(function() { - logging.resetLogger(); - }); - it('should return true when client is not ready and the forced decision has been removed successfully', function() { fakeOptimizely = { - isValidInstance: sinon.stub().returns(false) + isValidInstance: sinon.stub().returns(false), }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId: 'user123', }); @@ -878,9 +955,10 @@ describe('lib/optimizely_user_context', function() { it('should return true when the forced decision has been removed successfully and false otherwise', function() { fakeOptimizely = { - isValidInstance: sinon.stub().returns(true) + isValidInstance: sinon.stub().returns(true), }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId: 'user123', }); @@ -890,15 +968,14 @@ describe('lib/optimizely_user_context', function() { var result2 = user.removeForcedDecision('non-existent_feature'); assert.strictEqual(result2, false); - - sinon.assert.notCalled(stubLogHandler.log); }); it('should successfully remove forced decision when multiple forced decisions set with same feature key', function() { fakeOptimizely = { - isValidInstance: sinon.stub().returns(true) + isValidInstance: sinon.stub().returns(true), }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId: 'user123', }); @@ -925,36 +1002,25 @@ describe('lib/optimizely_user_context', function() { }); describe('#removeAllForcedDecisions', function() { - var stubLogHandler; - beforeEach(function() { - stubLogHandler = { - log: sinon.stub(), - }; - logging.setLogLevel('notset'); - logging.setLogHandler(stubLogHandler); - }); - afterEach(function() { - logging.resetLogger(); - }); - it('should return true when client is not ready', function() { fakeOptimizely = { - isValidInstance: sinon.stub().returns(false) + isValidInstance: sinon.stub().returns(false), }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, }); var result = user.removeAllForcedDecisions(); assert.strictEqual(result, true); - sinon.assert.notCalled(stubLogHandler.log); }); it('should return true when all forced decisions have been removed successfully', function() { fakeOptimizely = { - isValidInstance: sinon.stub().returns(true) + isValidInstance: sinon.stub().returns(true), }; var user = new OptimizelyUserContext({ + shouldIdentifyUser: false, optimizely: fakeOptimizely, userId, }); @@ -964,7 +1030,9 @@ describe('lib/optimizely_user_context', function() { assert.deepEqual(Object.keys(user.forcedDecisionsMap['feature_1']).length, 2); assert.deepEqual(user.getForcedDecision({ flagKey: 'feature_1' }), { variationKey: '3324490562' }); - assert.deepEqual(user.getForcedDecision({ flagKey: 'feature_1', ruleKey: 'exp_with_audience' }), { variationKey: 'b' }); + assert.deepEqual(user.getForcedDecision({ flagKey: 'feature_1', ruleKey: 'exp_with_audience' }), { + variationKey: 'b', + }); var result1 = user.removeAllForcedDecisions(); assert.strictEqual(result1, true); @@ -972,8 +1040,70 @@ describe('lib/optimizely_user_context', function() { assert.strictEqual(user.getForcedDecision({ flagKey: 'feature_1' }), null); assert.strictEqual(user.getForcedDecision({ flagKey: 'feature_1', ruleKey: 'exp_with_audience' }), null); + }); + }); + + describe('fetchQualifiedSegments', () => { + it('should successfully get segments', async () => { + fakeOptimizely = { + fetchQualifiedSegments: sinon.stub().returns(['a']), + }; + const user = new OptimizelyUserContext({ + shouldIdentifyUser: false, + optimizely: fakeOptimizely, + userId, + }); + + const successfullyFetched = await user.fetchQualifiedSegments(); + assert.deepEqual(successfullyFetched, true); + + sinon.assert.calledWithExactly(fakeOptimizely.fetchQualifiedSegments, userId, undefined); + + assert.deepEqual(user.qualifiedSegments, ['a']); + }); + + it('should return true empty returned segements', async () => { + fakeOptimizely = { + fetchQualifiedSegments: sinon.stub().returns([]), + }; + const user = new OptimizelyUserContext({ + shouldIdentifyUser: false, + optimizely: fakeOptimizely, + userId, + }); + + const successfullyFetched = await user.fetchQualifiedSegments(); + assert.deepEqual(successfullyFetched, true); + }); + + it('should return false in other cases', async () => { + fakeOptimizely = { + fetchQualifiedSegments: sinon.stub().returns(null), + }; + const user = new OptimizelyUserContext({ + shouldIdentifyUser: false, + optimizely: fakeOptimizely, + userId, + }); + + const successfullyFetched = await user.fetchQualifiedSegments(); + assert.deepEqual(successfullyFetched, false); + }); + }); + + describe('isQualifiedFor', () => { + it('should successfully return the expected result for if a user is qualified for a particular segment or not', () => { + const user = new OptimizelyUserContext({ + shouldIdentifyUser: false, + optimizely: fakeOptimizely, + userId, + }); + + user.qualifiedSegments = ['a', 'b']; - sinon.assert.notCalled(stubLogHandler.log); + assert.deepEqual(user.isQualifiedFor('a'), true); + assert.deepEqual(user.isQualifiedFor('b'), true); + assert.deepEqual(user.isQualifiedFor('c'), false); }); }); }); diff --git a/packages/optimizely-sdk/lib/optimizely_user_context/index.ts b/lib/optimizely_user_context/index.ts similarity index 54% rename from packages/optimizely-sdk/lib/optimizely_user_context/index.ts rename to lib/optimizely_user_context/index.ts index 83bca5f97..7b2af6488 100644 --- a/packages/optimizely-sdk/lib/optimizely_user_context/index.ts +++ b/lib/optimizely_user_context/index.ts @@ -1,48 +1,69 @@ -/**************************************************************************** - * Copyright 2020-2022, Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ -import Optimizely from '../../lib/optimizely'; +/** + * Copyright 2020-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import Optimizely from '../optimizely'; import { EventTags, OptimizelyDecideOption, OptimizelyDecision, OptimizelyDecisionContext, OptimizelyForcedDecision, + UserAttributeValue, UserAttributes, -} from '../../lib/shared_types'; -import { CONTROL_ATTRIBUTES } from '../utils/enums'; +} from '../shared_types'; +import { OptimizelySegmentOption } from '../odp/segment_manager/optimizely_segment_option'; -export default class OptimizelyUserContext { +export const FORCED_DECISION_NULL_RULE_KEY = '$opt_null_rule_key'; + +interface OptimizelyUserContextConfig { + optimizely: Optimizely; + userId: string; + attributes?: UserAttributes; +} + +export interface IOptimizelyUserContext { + qualifiedSegments: string[] | null; + getUserId(): string; + getAttributes(): UserAttributes; + setAttribute(key: string, value: unknown): void; + decide(key: string, options?: OptimizelyDecideOption[]): OptimizelyDecision; + decideAsync(key: string, options?: OptimizelyDecideOption[]): Promise<OptimizelyDecision>; + decideForKeys(keys: string[], options?: OptimizelyDecideOption[]): { [key: string]: OptimizelyDecision }; + decideForKeysAsync(keys: string[], options?: OptimizelyDecideOption[]): Promise<Record<string, OptimizelyDecision>>; + decideAll(options?: OptimizelyDecideOption[]): { [key: string]: OptimizelyDecision }; + decideAllAsync(options?: OptimizelyDecideOption[]): Promise<Record<string, OptimizelyDecision>>; + trackEvent(eventName: string, eventTags?: EventTags): void; + setForcedDecision(context: OptimizelyDecisionContext, decision: OptimizelyForcedDecision): boolean; + getForcedDecision(context: OptimizelyDecisionContext): OptimizelyForcedDecision | null; + removeForcedDecision(context: OptimizelyDecisionContext): boolean; + removeAllForcedDecisions(): boolean; + fetchQualifiedSegments(options?: OptimizelySegmentOption[]): Promise<boolean>; + isQualifiedFor(segment: string): boolean; +} + +export default class OptimizelyUserContext implements IOptimizelyUserContext { private optimizely: Optimizely; private userId: string; private attributes: UserAttributes; private forcedDecisionsMap: { [key: string]: { [key: string]: OptimizelyForcedDecision } }; - private _qualifiedSegments: string[] = []; - - constructor({ - optimizely, - userId, - attributes, - }: { - optimizely: Optimizely, - userId: string, - attributes?: UserAttributes, - }) { + private _qualifiedSegments: string[] | null = null; + + constructor({ optimizely, userId, attributes }: OptimizelyUserContextConfig) { this.optimizely = optimizely; this.userId = userId; - this.attributes = { ...attributes } ?? {}; + this.attributes = { ...attributes }; this.forcedDecisionsMap = {}; } @@ -51,7 +72,7 @@ export default class OptimizelyUserContext { * @param {string} key An attribute key * @param {any} value An attribute value */ - setAttribute(key: string, value: unknown): void { + setAttribute(key: string, value: UserAttributeValue): void { this.attributes[key] = value; } @@ -67,12 +88,12 @@ export default class OptimizelyUserContext { return this.optimizely; } - public get qualifiedSegments(): string[] { + public get qualifiedSegments(): string[] | null { return this._qualifiedSegments; } - public set qualifiedSegments(qualifiedSegments: string[]) { - this._qualifiedSegments = [...qualifiedSegments]; + public set qualifiedSegments(qualifiedSegments: string[] | null) { + this._qualifiedSegments = qualifiedSegments; } /** @@ -82,14 +103,21 @@ export default class OptimizelyUserContext { * @param {OptimizelyDecideOption} options An array of options for decision-making. * @return {OptimizelyDecision} A decision result. */ - decide( - key: string, - options: OptimizelyDecideOption[] = [] - ): OptimizelyDecision { - + decide(key: string, options: OptimizelyDecideOption[] = []): OptimizelyDecision { return this.optimizely.decide(this.cloneUserContext(), key, options); } + /** + * Returns a promise that resolves in decision result for a given flag key and a user context, which contains all data required to deliver the flag. + * If the SDK finds an error, it will return a decision with null for variationKey. The decision will include an error message in reasons. + * @param {string} key A flag key for which a decision will be made. + * @param {OptimizelyDecideOption} options An array of options for decision-making. + * @return {Promise<OptimizelyDecision>} A Promise that resolves decision result. + */ + decideAsync(key: string, options?: OptimizelyDecideOption[]): Promise<OptimizelyDecision> { + return this.optimizely.decideAsync(this.cloneUserContext(), key, options); + } + /** * Returns an object of decision results for multiple flag keys and a user context. * If the SDK finds an error for a key, the response will include a decision for the key showing reasons for the error. @@ -98,26 +126,39 @@ export default class OptimizelyUserContext { * @param {OptimizelyDecideOption[]} options An array of options for decision-making. * @return {[key: string]: OptimizelyDecision} An object of decision results mapped by flag keys. */ - decideForKeys( - keys: string[], - options: OptimizelyDecideOption[] = [], - ): { [key: string]: OptimizelyDecision } { - + decideForKeys(keys: string[], options: OptimizelyDecideOption[] = []): { [key: string]: OptimizelyDecision } { return this.optimizely.decideForKeys(this.cloneUserContext(), keys, options); } + /** + * Returns a promise that resolves in an object of decision results for multiple flag keys and a user context. + * If the SDK finds an error for a key, the response will include a decision for the key showing reasons for the error. + * The SDK will always return key-mapped decisions. When it cannot process requests, it will return an empty map after logging the errors. + * @param {string[]} keys An array of flag keys for which decisions will be made. + * @param {OptimizelyDecideOption[]} options An array of options for decision-making. + * @return {Promise<Record<string, OptimizelyDecision>>} A promise that resolves in an object of decision results mapped by flag keys. + */ + decideForKeysAsync(keys: string[], options?: OptimizelyDecideOption[]): Promise<Record<string, OptimizelyDecision>> { + return this.optimizely.decideForKeysAsync(this.cloneUserContext(), keys, options); + } /** * Returns an object of decision results for all active flag keys. * @param {OptimizelyDecideOption[]} options An array of options for decision-making. * @return {[key: string]: OptimizelyDecision} An object of all decision results mapped by flag keys. */ - decideAll( - options: OptimizelyDecideOption[] = [] - ): { [key: string]: OptimizelyDecision } { - + decideAll(options: OptimizelyDecideOption[] = []): { [key: string]: OptimizelyDecision } { return this.optimizely.decideAll(this.cloneUserContext(), options); } + /** + * Returns a promise that resolves in an object of decision results for all active flag keys. + * @param {OptimizelyDecideOption[]} options An array of options for decision-making. + * @return {Promise<Record<string ,OptimizelyDecision>>} A promise that resolves in an object of all decision results mapped by flag keys. + */ + decideAllAsync(options: OptimizelyDecideOption[] = []): Promise<Record<string, OptimizelyDecision>> { + return this.optimizely.decideAllAsync(this.cloneUserContext(), options); + } + /** * Tracks an event. * @param {string} eventName The event name. @@ -136,7 +177,7 @@ export default class OptimizelyUserContext { setForcedDecision(context: OptimizelyDecisionContext, decision: OptimizelyForcedDecision): boolean { const flagKey = context.flagKey; - const ruleKey = context.ruleKey ?? CONTROL_ATTRIBUTES.FORCED_DECISION_NULL_RULE_KEY; + const ruleKey = context.ruleKey ?? FORCED_DECISION_NULL_RULE_KEY; const variationKey = decision.variationKey; const forcedDecision = { variationKey }; @@ -163,7 +204,7 @@ export default class OptimizelyUserContext { * @return {boolean} true if the forced decision has been removed successfully */ removeForcedDecision(context: OptimizelyDecisionContext): boolean { - const ruleKey = context.ruleKey ?? CONTROL_ATTRIBUTES.FORCED_DECISION_NULL_RULE_KEY; + const ruleKey = context.ruleKey ?? FORCED_DECISION_NULL_RULE_KEY; const flagKey = context.flagKey; let isForcedDecisionRemoved = false; @@ -198,7 +239,7 @@ export default class OptimizelyUserContext { */ private findForcedDecision(context: OptimizelyDecisionContext): OptimizelyForcedDecision | null { let variationKey; - const validRuleKey = context.ruleKey ?? CONTROL_ATTRIBUTES.FORCED_DECISION_NULL_RULE_KEY; + const validRuleKey = context.ruleKey ?? FORCED_DECISION_NULL_RULE_KEY; const flagKey = context.flagKey; if (this.forcedDecisionsMap.hasOwnProperty(context.flagKey)) { @@ -212,10 +253,6 @@ export default class OptimizelyUserContext { return null; } - public isQualifiedFor(segment: string): boolean { - return this._qualifiedSegments.indexOf(segment) > -1; - } - private cloneUserContext(): OptimizelyUserContext { const userContext = new OptimizelyUserContext({ optimizely: this.getOptimizely(), @@ -227,10 +264,34 @@ export default class OptimizelyUserContext { userContext.forcedDecisionsMap = { ...this.forcedDecisionsMap }; } - if (this._qualifiedSegments) { - userContext._qualifiedSegments = [...this._qualifiedSegments]; - } + userContext._qualifiedSegments = this._qualifiedSegments; return userContext; } + + /** + * Fetches a target user's list of qualified segments filtered by any given segment options and stores in qualifiedSegments. + * @param {OptimizelySegmentOption[]} options (Optional) List of segment options used to filter qualified segment results. + * @returns Boolean representing if segments were populated. + */ + async fetchQualifiedSegments(options?: OptimizelySegmentOption[]): Promise<boolean> { + const segments = await this.optimizely.fetchQualifiedSegments(this.userId, options); + + this.qualifiedSegments = segments; + + return segments !== null; + } + + /** + * Returns a boolean representing if a user is qualified for a particular segment. + * @param {string} segment Target segment to be evaluated for user qualification. + * @returns {boolean} Boolean representing if a user qualified for the passed in segment. + */ + isQualifiedFor(segment: string): boolean { + if (!this._qualifiedSegments) { + return false; + } + + return this._qualifiedSegments.indexOf(segment) > -1; + } } diff --git a/lib/project_config/config_manager_factory.browser.spec.ts b/lib/project_config/config_manager_factory.browser.spec.ts new file mode 100644 index 000000000..9dfa7bced --- /dev/null +++ b/lib/project_config/config_manager_factory.browser.spec.ts @@ -0,0 +1,87 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, beforeEach, vi } from 'vitest'; + +vi.mock('./config_manager_factory', () => { + return { + getPollingConfigManager: vi.fn().mockReturnValueOnce({ foo: 'bar' }), + getOpaquePollingConfigManager: vi.fn().mockReturnValueOnce({ foo: 'bar' }), + }; +}); + +vi.mock('../utils/http_request_handler/request_handler.browser', () => { + const BrowserRequestHandler = vi.fn(); + return { BrowserRequestHandler }; +}); + +import { getOpaquePollingConfigManager, PollingConfigManagerConfig, PollingConfigManagerFactoryOptions } from './config_manager_factory'; +import { createPollingProjectConfigManager } from './config_manager_factory.browser'; +import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser'; +import { getMockSyncCache } from '../tests/mock/mock_cache'; + +describe('createPollingConfigManager', () => { + const mockGetOpaquePollingConfigManager = vi.mocked(getOpaquePollingConfigManager); + const MockBrowserRequestHandler = vi.mocked(BrowserRequestHandler); + + beforeEach(() => { + mockGetOpaquePollingConfigManager.mockClear(); + MockBrowserRequestHandler.mockClear(); + }); + + it('creates and returns the instance by calling getPollingConfigManager', () => { + const config = { + sdkKey: 'sdkKey', + }; + + const projectConfigManager = createPollingProjectConfigManager(config); + expect(Object.is(projectConfigManager, mockGetOpaquePollingConfigManager.mock.results[0].value)).toBe(true); + }); + + it('uses an instance of BrowserRequestHandler as requestHandler', () => { + const config = { + sdkKey: 'sdkKey', + }; + + const projectConfigManager = createPollingProjectConfigManager(config); + expect(Object.is(mockGetOpaquePollingConfigManager.mock.calls[0][0].requestHandler, MockBrowserRequestHandler.mock.instances[0])).toBe(true); + }); + + it('uses uses autoUpdate = false by default', () => { + const config = { + sdkKey: 'sdkKey', + }; + + const projectConfigManager = createPollingProjectConfigManager(config); + expect(mockGetOpaquePollingConfigManager.mock.calls[0][0].autoUpdate).toBe(false); + }); + + it('uses the provided options', () => { + const config: PollingConfigManagerConfig = { + datafile: '{}', + jsonSchemaValidator: vi.fn(), + sdkKey: 'sdkKey', + updateInterval: 50000, + autoUpdate: true, + urlTemplate: 'urlTemplate', + datafileAccessToken: 'datafileAccessToken', + cache: getMockSyncCache<string>(), + }; + + const projectConfigManager = createPollingProjectConfigManager(config); + expect(mockGetOpaquePollingConfigManager).toHaveBeenNthCalledWith(1, expect.objectContaining(config)); + }); +}); diff --git a/lib/project_config/config_manager_factory.browser.ts b/lib/project_config/config_manager_factory.browser.ts new file mode 100644 index 000000000..0a96affd5 --- /dev/null +++ b/lib/project_config/config_manager_factory.browser.ts @@ -0,0 +1,27 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getOpaquePollingConfigManager, OpaqueConfigManager, PollingConfigManagerConfig } from './config_manager_factory'; +import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser'; +import { ProjectConfigManager } from './project_config_manager'; + +export const createPollingProjectConfigManager = (config: PollingConfigManagerConfig): OpaqueConfigManager => { + const defaultConfig = { + autoUpdate: false, + requestHandler: new BrowserRequestHandler(), + }; + return getOpaquePollingConfigManager({ ...defaultConfig, ...config }); +}; diff --git a/lib/project_config/config_manager_factory.node.spec.ts b/lib/project_config/config_manager_factory.node.spec.ts new file mode 100644 index 000000000..c0631a63b --- /dev/null +++ b/lib/project_config/config_manager_factory.node.spec.ts @@ -0,0 +1,87 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, beforeEach, vi } from 'vitest'; + +vi.mock('./config_manager_factory', () => { + return { + getPollingConfigManager: vi.fn().mockReturnValueOnce({ foo: 'bar' }), + getOpaquePollingConfigManager: vi.fn().mockReturnValueOnce({ foo: 'bar' }), + }; +}); + +vi.mock('../utils/http_request_handler/request_handler.node', () => { + const NodeRequestHandler = vi.fn(); + return { NodeRequestHandler }; +}); + +import { getOpaquePollingConfigManager, PollingConfigManagerConfig } from './config_manager_factory'; +import { createPollingProjectConfigManager } from './config_manager_factory.node'; +import { NodeRequestHandler } from '../utils/http_request_handler/request_handler.node'; +import { getMockSyncCache } from '../tests/mock/mock_cache'; + +describe('createPollingConfigManager', () => { + const mockGetOpaquePollingConfigManager = vi.mocked(getOpaquePollingConfigManager); + const MockNodeRequestHandler = vi.mocked(NodeRequestHandler); + + beforeEach(() => { + mockGetOpaquePollingConfigManager.mockClear(); + MockNodeRequestHandler.mockClear(); + }); + + it('creates and returns the instance by calling getPollingConfigManager', () => { + const config = { + sdkKey: 'sdkKey', + }; + + const projectConfigManager = createPollingProjectConfigManager(config); + expect(Object.is(projectConfigManager, mockGetOpaquePollingConfigManager.mock.results[0].value)).toBe(true); + }); + + it('uses an instance of NodeRequestHandler as requestHandler', () => { + const config = { + sdkKey: 'sdkKey', + }; + + const projectConfigManager = createPollingProjectConfigManager(config); + expect(Object.is(mockGetOpaquePollingConfigManager.mock.calls[0][0].requestHandler, MockNodeRequestHandler.mock.instances[0])).toBe(true); + }); + + it('uses uses autoUpdate = true by default', () => { + const config = { + sdkKey: 'sdkKey', + }; + + const projectConfigManager = createPollingProjectConfigManager(config); + expect(mockGetOpaquePollingConfigManager.mock.calls[0][0].autoUpdate).toBe(true); + }); + + it('uses the provided options', () => { + const config: PollingConfigManagerConfig = { + datafile: '{}', + jsonSchemaValidator: vi.fn(), + sdkKey: 'sdkKey', + updateInterval: 50000, + autoUpdate: false, + urlTemplate: 'urlTemplate', + datafileAccessToken: 'datafileAccessToken', + cache: getMockSyncCache(), + }; + + const projectConfigManager = createPollingProjectConfigManager(config); + expect(mockGetOpaquePollingConfigManager).toHaveBeenNthCalledWith(1, expect.objectContaining(config)); + }); +}); diff --git a/lib/project_config/config_manager_factory.node.ts b/lib/project_config/config_manager_factory.node.ts new file mode 100644 index 000000000..58ac126bc --- /dev/null +++ b/lib/project_config/config_manager_factory.node.ts @@ -0,0 +1,28 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getOpaquePollingConfigManager, OpaqueConfigManager, PollingConfigManagerConfig } from "./config_manager_factory"; +import { NodeRequestHandler } from "../utils/http_request_handler/request_handler.node"; +import { ProjectConfigManager } from "./project_config_manager"; +import { DEFAULT_URL_TEMPLATE, DEFAULT_AUTHENTICATED_URL_TEMPLATE } from './constant'; + +export const createPollingProjectConfigManager = (config: PollingConfigManagerConfig): OpaqueConfigManager => { + const defaultConfig = { + autoUpdate: true, + requestHandler: new NodeRequestHandler(), + }; + return getOpaquePollingConfigManager({ ...defaultConfig, ...config }); +}; diff --git a/lib/project_config/config_manager_factory.react_native.spec.ts b/lib/project_config/config_manager_factory.react_native.spec.ts new file mode 100644 index 000000000..52411861d --- /dev/null +++ b/lib/project_config/config_manager_factory.react_native.spec.ts @@ -0,0 +1,165 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, beforeEach, vi } from 'vitest'; + +await vi.hoisted(async () => { + await mockRequireAsyncStorage(); +}); + +let isAsyncStorageAvailable = true; + +async function mockRequireAsyncStorage() { + const { Module } = await import('module'); + const M: any = Module; + + M._load_original = M._load; + M._load = (uri: string, parent: string) => { + if (uri === '@react-native-async-storage/async-storage') { + if (isAsyncStorageAvailable) return { default: {} }; + throw new Error("Module not found: @react-native-async-storage/async-storage"); + } + return M._load_original(uri, parent); + }; +} + +vi.mock('./config_manager_factory', () => { + return { + getPollingConfigManager: vi.fn().mockReturnValueOnce({ foo: 'bar' }), + getOpaquePollingConfigManager: vi.fn().mockRejectedValueOnce({ foo: 'bar' }), + }; +}); + +vi.mock('../utils/http_request_handler/request_handler.browser', () => { + const BrowserRequestHandler = vi.fn(); + return { BrowserRequestHandler }; +}); + +vi.mock('../utils/cache/async_storage_cache.react_native', async (importOriginal) => { + const original: any = await importOriginal(); + const OriginalAsyncStorageCache = original.AsyncStorageCache; + const MockAsyncStorageCache = vi.fn().mockImplementation(function (this: any, ...args) { + Object.setPrototypeOf(this, new OriginalAsyncStorageCache(...args)); + }); + return { AsyncStorageCache: MockAsyncStorageCache }; +}); + +import { getOpaquePollingConfigManager, getPollingConfigManager, PollingConfigManagerConfig } from './config_manager_factory'; +import { createPollingProjectConfigManager } from './config_manager_factory.react_native'; +import { BrowserRequestHandler } from '../utils/http_request_handler/request_handler.browser'; +import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; +import { getMockSyncCache } from '../tests/mock/mock_cache'; + +describe('createPollingConfigManager', () => { + const mockGetOpaquePollingConfigManager = vi.mocked(getOpaquePollingConfigManager); + const MockBrowserRequestHandler = vi.mocked(BrowserRequestHandler); + const MockAsyncStorageCache = vi.mocked(AsyncStorageCache); + + beforeEach(() => { + mockGetOpaquePollingConfigManager.mockClear(); + MockBrowserRequestHandler.mockClear(); + MockAsyncStorageCache.mockClear(); + }); + + it('creates and returns the instance by calling getPollingConfigManager', () => { + const config = { + sdkKey: 'sdkKey', + }; + + const projectConfigManager = createPollingProjectConfigManager(config); + expect(Object.is(projectConfigManager, mockGetOpaquePollingConfigManager.mock.results[0].value)).toBe(true); + }); + + it('uses an instance of BrowserRequestHandler as requestHandler', () => { + const config = { + sdkKey: 'sdkKey', + }; + + createPollingProjectConfigManager(config); + + expect( + Object.is( + mockGetOpaquePollingConfigManager.mock.calls[0][0].requestHandler, + MockBrowserRequestHandler.mock.instances[0] + ) + ).toBe(true); + }); + + it('uses uses autoUpdate = true by default', () => { + const config = { + sdkKey: 'sdkKey', + }; + + createPollingProjectConfigManager(config); + + expect(mockGetOpaquePollingConfigManager.mock.calls[0][0].autoUpdate).toBe(true); + }); + + it('uses an instance of ReactNativeAsyncStorageCache for caching by default', () => { + const config = { + sdkKey: 'sdkKey', + }; + + createPollingProjectConfigManager(config); + + expect( + Object.is(mockGetOpaquePollingConfigManager.mock.calls[0][0].cache, MockAsyncStorageCache.mock.instances[0]) + ).toBe(true); + }); + + it('uses the provided options', () => { + const config: PollingConfigManagerConfig = { + datafile: '{}', + jsonSchemaValidator: vi.fn(), + sdkKey: 'sdkKey', + updateInterval: 50000, + autoUpdate: false, + urlTemplate: 'urlTemplate', + datafileAccessToken: 'datafileAccessToken', + cache: getMockSyncCache(), + }; + + createPollingProjectConfigManager(config); + + expect(mockGetOpaquePollingConfigManager).toHaveBeenNthCalledWith(1, expect.objectContaining(config)); + }); + + it('Should not throw error if a cache is present in the config, and async storage is not available', async () => { + isAsyncStorageAvailable = false; + const config = { + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + cache: getMockSyncCache<string>(), + }; + + expect(() => createPollingProjectConfigManager(config)).not.toThrow(); + isAsyncStorageAvailable = true; + }); + + it('should throw an error if cache is not present in the config, and async storage is not available', async () => { + isAsyncStorageAvailable = false; + + const config = { + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + }; + + expect(() => createPollingProjectConfigManager(config)).toThrowError( + "Module not found: @react-native-async-storage/async-storage" + ); + isAsyncStorageAvailable = true; + }); +}); diff --git a/lib/project_config/config_manager_factory.react_native.ts b/lib/project_config/config_manager_factory.react_native.ts new file mode 100644 index 000000000..8ea480595 --- /dev/null +++ b/lib/project_config/config_manager_factory.react_native.ts @@ -0,0 +1,32 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getOpaquePollingConfigManager, PollingConfigManagerConfig } from "./config_manager_factory"; +import { BrowserRequestHandler } from "../utils/http_request_handler/request_handler.browser"; +import { ProjectConfigManager } from "./project_config_manager"; +import { AsyncStorageCache } from "../utils/cache/async_storage_cache.react_native"; + +import { OpaqueConfigManager } from "./config_manager_factory"; + +export const createPollingProjectConfigManager = (config: PollingConfigManagerConfig): OpaqueConfigManager => { + const defaultConfig = { + autoUpdate: true, + requestHandler: new BrowserRequestHandler(), + cache: config.cache || new AsyncStorageCache(), + }; + + return getOpaquePollingConfigManager({ ...defaultConfig, ...config }); +}; diff --git a/lib/project_config/config_manager_factory.spec.ts b/lib/project_config/config_manager_factory.spec.ts new file mode 100644 index 000000000..7def4f9a8 --- /dev/null +++ b/lib/project_config/config_manager_factory.spec.ts @@ -0,0 +1,184 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, beforeEach, vi } from 'vitest'; + +vi.mock('./project_config_manager', () => { + const MockConfigManager = vi.fn(); + return { ProjectConfigManagerImpl: MockConfigManager }; +}); + +vi.mock('./polling_datafile_manager', () => { + const MockDatafileManager = vi.fn(); + return { PollingDatafileManager: MockDatafileManager }; +}); + +vi.mock('../utils/repeater/repeater', () => { + const MockIntervalRepeater = vi.fn(); + const MockExponentialBackoff = vi.fn(); + return { IntervalRepeater: MockIntervalRepeater, ExponentialBackoff: MockExponentialBackoff }; +}); + +import { ProjectConfigManagerImpl } from './project_config_manager'; +import { PollingDatafileManager } from './polling_datafile_manager'; +import { ExponentialBackoff, IntervalRepeater } from '../utils/repeater/repeater'; +import { getPollingConfigManager } from './config_manager_factory'; +import { DEFAULT_UPDATE_INTERVAL, UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE } from './constant'; +import { getMockSyncCache } from '../tests/mock/mock_cache'; +import { LogLevel } from '../logging/logger'; + +describe('getPollingConfigManager', () => { + const MockProjectConfigManagerImpl = vi.mocked(ProjectConfigManagerImpl); + const MockPollingDatafileManager = vi.mocked(PollingDatafileManager); + const MockIntervalRepeater = vi.mocked(IntervalRepeater); + const MockExponentialBackoff = vi.mocked(ExponentialBackoff); + + beforeEach(() => { + MockProjectConfigManagerImpl.mockClear(); + MockPollingDatafileManager.mockClear(); + MockIntervalRepeater.mockClear(); + MockExponentialBackoff.mockClear(); + }); + + it('should throw an error if the passed cache is not valid', () => { + expect(() => getPollingConfigManager({ + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + cache: 1 as any, + })).toThrow('Invalid store'); + + expect(() => getPollingConfigManager({ + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + cache: 'abc' as any, + })).toThrow('Invalid store'); + + expect(() => getPollingConfigManager({ + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + cache: {} as any, + })).toThrow('Invalid store method set, Invalid store method get, Invalid store method remove, Invalid store method getKeys'); + + expect(() => getPollingConfigManager({ + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + cache: { set: 'abc', get: 'abc', remove: 'abc', getKeys: 'abc' } as any, + })).toThrow('Invalid store method set, Invalid store method get, Invalid store method remove, Invalid store method getKeys'); + + const noop = () => {}; + + expect(() => getPollingConfigManager({ + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + cache: { set: noop, get: 'abc', remove: 'abc', getKeys: 'abc' } as any, + })).toThrow('Invalid store method get, Invalid store method remove, Invalid store method getKeys'); + + expect(() => getPollingConfigManager({ + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + cache: { set: noop, get: noop, remove: 'abc', getKeys: 'abc' } as any, + })).toThrow('Invalid store method remove, Invalid store method getKeys'); + + expect(() => getPollingConfigManager({ + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + cache: { set: noop, get: noop, remove: noop, getKeys: 'abc' } as any, + })).toThrow('Invalid store method getKeys'); + }); + + it('uses a repeater with exponential backoff for the datafileManager', () => { + const config = { + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + }; + const projectConfigManager = getPollingConfigManager(config); + expect(Object.is(projectConfigManager, MockProjectConfigManagerImpl.mock.instances[0])).toBe(true); + const usedDatafileManager = MockProjectConfigManagerImpl.mock.calls[0][0].datafileManager; + expect(Object.is(usedDatafileManager, MockPollingDatafileManager.mock.instances[0])).toBe(true); + const usedRepeater = MockPollingDatafileManager.mock.calls[0][0].repeater; + expect(Object.is(usedRepeater, MockIntervalRepeater.mock.instances[0])).toBe(true); + const usedBackoff = MockIntervalRepeater.mock.calls[0][1]; + expect(Object.is(usedBackoff, MockExponentialBackoff.mock.instances[0])).toBe(true); + }); + + it('uses the default update interval if not provided', () => { + const config = { + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + }; + getPollingConfigManager(config); + expect(MockIntervalRepeater.mock.calls[0][0]).toBe(DEFAULT_UPDATE_INTERVAL); + }); + + it('adds a startup log if the update interval is below the minimum', () => { + const config = { + sdkKey: 'abcd', + requestHandler: { makeRequest: vi.fn() }, + updateInterval: 10000, + }; + getPollingConfigManager(config); + const startupLogs = MockPollingDatafileManager.mock.calls[0][0].startupLogs; + expect(startupLogs).toEqual(expect.arrayContaining([{ + level: LogLevel.Warn, + message: UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE, + params: [], + }])); + }); + + it('does not add any startup log if the update interval above the minimum', () => { + const config = { + sdkKey: 'abcd', + requestHandler: { makeRequest: vi.fn() }, + updateInterval: 40000, + }; + getPollingConfigManager(config); + const startupLogs = MockPollingDatafileManager.mock.calls[0][0].startupLogs; + expect(startupLogs).toEqual([]); + }); + + it('uses the provided options', () => { + const config = { + datafile: '{}', + jsonSchemaValidator: vi.fn(), + sdkKey: 'sdkKey', + requestHandler: { makeRequest: vi.fn() }, + updateInterval: 50000, + autoUpdate: true, + urlTemplate: 'urlTemplate', + datafileAccessToken: 'datafileAccessToken', + cache: getMockSyncCache<string>(), + }; + + getPollingConfigManager(config); + expect(MockIntervalRepeater.mock.calls[0][0]).toBe(config.updateInterval); + expect(MockExponentialBackoff).toHaveBeenNthCalledWith(1, 1000, config.updateInterval, 500); + + expect(MockPollingDatafileManager).toHaveBeenNthCalledWith(1, expect.objectContaining({ + sdkKey: config.sdkKey, + autoUpdate: config.autoUpdate, + urlTemplate: config.urlTemplate, + datafileAccessToken: config.datafileAccessToken, + requestHandler: config.requestHandler, + repeater: MockIntervalRepeater.mock.instances[0], + cache: config.cache, + })); + + expect(MockProjectConfigManagerImpl).toHaveBeenNthCalledWith(1, expect.objectContaining({ + datafile: config.datafile, + jsonSchemaValidator: config.jsonSchemaValidator, + })); + }); +}); diff --git a/lib/project_config/config_manager_factory.ts b/lib/project_config/config_manager_factory.ts new file mode 100644 index 000000000..e7d21aeea --- /dev/null +++ b/lib/project_config/config_manager_factory.ts @@ -0,0 +1,129 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RequestHandler } from "../utils/http_request_handler/http"; +import { Maybe, Transformer } from "../utils/type"; +import { DatafileManagerConfig } from "./datafile_manager"; +import { ProjectConfigManagerImpl, ProjectConfigManager } from "./project_config_manager"; +import { PollingDatafileManager } from "./polling_datafile_manager"; +import { DEFAULT_UPDATE_INTERVAL } from './constant'; +import { ExponentialBackoff, IntervalRepeater } from "../utils/repeater/repeater"; +import { StartupLog } from "../service"; +import { MIN_UPDATE_INTERVAL, UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE } from './constant'; +import { LogLevel } from '../logging/logger' +import { Store } from "../utils/cache/store"; +import { validateStore } from "../utils/cache/store_validator"; + +export const INVALID_CONFIG_MANAGER = "Invalid config manager"; + +const configManagerSymbol: unique symbol = Symbol(); + +export type OpaqueConfigManager = { + [configManagerSymbol]: unknown; +}; + +export type StaticConfigManagerConfig = { + datafile: string, + jsonSchemaValidator?: Transformer<unknown, boolean>, +}; + +export const createStaticProjectConfigManager = ( + config: StaticConfigManagerConfig +): OpaqueConfigManager => { + return { + [configManagerSymbol]: new ProjectConfigManagerImpl(config), + } +}; + +export type PollingConfigManagerConfig = { + datafile?: string, + sdkKey: string, + jsonSchemaValidator?: Transformer<unknown, boolean>, + autoUpdate?: boolean; + updateInterval?: number; + urlTemplate?: string; + datafileAccessToken?: string; + cache?: Store<string>; +}; + +export type PollingConfigManagerFactoryOptions = PollingConfigManagerConfig & { requestHandler: RequestHandler }; + +export const getPollingConfigManager = ( + opt: PollingConfigManagerFactoryOptions +): ProjectConfigManager => { + if (opt.cache) { + validateStore(opt.cache); + } + + const updateInterval = opt.updateInterval ?? DEFAULT_UPDATE_INTERVAL; + + const backoff = new ExponentialBackoff(1000, updateInterval, 500); + const repeater = new IntervalRepeater(updateInterval, backoff); + + const startupLogs: StartupLog[] = [] + + if (updateInterval < MIN_UPDATE_INTERVAL) { + startupLogs.push({ + level: LogLevel.Warn, + message: UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE, + params: [], + }); + } + + const datafileManagerConfig: DatafileManagerConfig = { + sdkKey: opt.sdkKey, + autoUpdate: opt.autoUpdate, + urlTemplate: opt.urlTemplate, + datafileAccessToken: opt.datafileAccessToken, + requestHandler: opt.requestHandler, + cache: opt.cache, + repeater, + startupLogs, + }; + + const datafileManager = new PollingDatafileManager(datafileManagerConfig); + + return new ProjectConfigManagerImpl({ + datafile: opt.datafile, + datafileManager, + jsonSchemaValidator: opt.jsonSchemaValidator, + }); +}; + +export const getOpaquePollingConfigManager = (opt: PollingConfigManagerFactoryOptions): OpaqueConfigManager => { + return { + [configManagerSymbol]: getPollingConfigManager(opt), + }; +}; + +export const wrapConfigManager = (configManager: ProjectConfigManager): OpaqueConfigManager => { + return { + [configManagerSymbol]: configManager, + }; +}; + +export const extractConfigManager = (opaqueConfigManager: OpaqueConfigManager): ProjectConfigManager => { + if (!opaqueConfigManager || typeof opaqueConfigManager !== 'object') { + throw new Error(INVALID_CONFIG_MANAGER); + } + + const configManager = opaqueConfigManager[configManagerSymbol]; + if (!configManager) { + throw new Error(INVALID_CONFIG_MANAGER); + } + + return opaqueConfigManager[configManagerSymbol] as ProjectConfigManager; +}; diff --git a/lib/project_config/config_manager_factory.universal.ts b/lib/project_config/config_manager_factory.universal.ts new file mode 100644 index 000000000..bcc664082 --- /dev/null +++ b/lib/project_config/config_manager_factory.universal.ts @@ -0,0 +1,31 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getOpaquePollingConfigManager, OpaqueConfigManager, PollingConfigManagerConfig } from "./config_manager_factory"; +import { RequestHandler } from "../utils/http_request_handler/http"; +import { validateRequestHandler } from "../utils/http_request_handler/request_handler_validator"; + +export type UniversalPollingConfigManagerConfig = PollingConfigManagerConfig & { + requestHandler: RequestHandler; +} + +export const createPollingProjectConfigManager = (config: UniversalPollingConfigManagerConfig): OpaqueConfigManager => { + validateRequestHandler(config.requestHandler); + const defaultConfig = { + autoUpdate: true, + }; + return getOpaquePollingConfigManager({ ...defaultConfig, ...config }); +}; diff --git a/packages/datafile-manager/src/config.ts b/lib/project_config/constant.ts similarity index 57% rename from packages/datafile-manager/src/config.ts rename to lib/project_config/constant.ts index 7c31d8bd0..55e69a33e 100644 --- a/packages/datafile-manager/src/config.ts +++ b/lib/project_config/constant.ts @@ -1,11 +1,11 @@ /** - * Copyright 2019-2020, Optimizely + * Copyright 2022-2023, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,9 +14,15 @@ * limitations under the License. */ -export const DEFAULT_UPDATE_INTERVAL = 5 * 60 * 1000; // 5 minutes +const DEFAULT_UPDATE_INTERVAL_MINUTES = 5; +/** Standard interval (5 minutes in milliseconds) for polling datafile updates.; */ +export const DEFAULT_UPDATE_INTERVAL = DEFAULT_UPDATE_INTERVAL_MINUTES * 60 * 1000; -export const MIN_UPDATE_INTERVAL = 1000; +const MIN_UPDATE_INTERVAL_SECONDS = 30; +/** Minimum allowed interval (30 seconds in milliseconds) for polling datafile updates. */ +export const MIN_UPDATE_INTERVAL = MIN_UPDATE_INTERVAL_SECONDS * 1000; + +export const UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE = `Polling intervals below ${MIN_UPDATE_INTERVAL_SECONDS} seconds are not recommended.`; export const DEFAULT_URL_TEMPLATE = `https://cdn.optimizely.com/datafiles/%s.json`; diff --git a/lib/project_config/datafile_manager.ts b/lib/project_config/datafile_manager.ts new file mode 100644 index 000000000..c5765a539 --- /dev/null +++ b/lib/project_config/datafile_manager.ts @@ -0,0 +1,40 @@ +/** + * Copyright 2022-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Service, StartupLog } from '../service'; +import { Store } from '../utils/cache/store'; +import { RequestHandler } from '../utils/http_request_handler/http'; +import { Fn, Consumer } from '../utils/type'; +import { Repeater } from '../utils/repeater/repeater'; +import { LoggerFacade } from '../logging/logger'; + +export interface DatafileManager extends Service { + get(): string | undefined; + onUpdate(listener: Consumer<string>): Fn; + setLogger(logger: LoggerFacade): void; +} + +export type DatafileManagerConfig = { + requestHandler: RequestHandler; + autoUpdate?: boolean; + sdkKey: string; + urlTemplate?: string; + cache?: Store<string>; + datafileAccessToken?: string; + initRetry?: number; + repeater: Repeater; + logger?: LoggerFacade; + startupLogs?: StartupLog[]; +} diff --git a/lib/project_config/optimizely_config.spec.ts b/lib/project_config/optimizely_config.spec.ts new file mode 100644 index 000000000..6e9e6747b --- /dev/null +++ b/lib/project_config/optimizely_config.spec.ts @@ -0,0 +1,937 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, beforeEach, vi, assert } from 'vitest'; +import { createOptimizelyConfig, OptimizelyConfig } from './optimizely_config'; +import { createProjectConfig, ProjectConfig } from './project_config'; +import { + getTestProjectConfigWithFeatures, + getTypedAudiencesConfig, + getSimilarRuleKeyConfig, + getSimilarExperimentKeyConfig, + getDuplicateExperimentKeyConfig, +} from '../tests/test_data'; +import { Experiment } from '../shared_types'; +import { LoggerFacade } from '../logging/logger'; +import { getMockLogger } from '../tests/mock/mock_logger'; + +const datafile: ProjectConfig = getTestProjectConfigWithFeatures(); +const typedAudienceDatafile = getTypedAudiencesConfig(); +const similarRuleKeyDatafile = getSimilarRuleKeyConfig(); +const similarExperimentKeyDatafile = getSimilarExperimentKeyConfig(); +const cloneDeep = (obj: any) => JSON.parse(JSON.stringify(obj)); +const getAllExperimentsFromDatafile = (datafile: ProjectConfig) => { + const allExperiments: Experiment[] = []; + datafile.groups.forEach(group => { + group.experiments.forEach(experiment => { + allExperiments.push(experiment); + }); + }); + datafile.experiments.forEach(experiment => { + allExperiments.push(experiment); + }); + return allExperiments; +}; + +describe('Optimizely Config', () => { + let optimizelyConfigObject: OptimizelyConfig; + let projectConfigObject: ProjectConfig; + let projectTypedAudienceConfigObject: ProjectConfig; + let optimizelySimilarRuleKeyConfigObject: OptimizelyConfig; + let projectSimilarRuleKeyConfigObject: ProjectConfig; + let optimizelySimilarExperimentkeyConfigObject: OptimizelyConfig; + let projectSimilarExperimentKeyConfigObject: ProjectConfig; + + const logger = getMockLogger(); + + beforeEach(() => { + projectConfigObject = createProjectConfig(cloneDeep(datafile as any)); + optimizelyConfigObject = createOptimizelyConfig(projectConfigObject, JSON.stringify(datafile)); + projectTypedAudienceConfigObject = createProjectConfig(cloneDeep(typedAudienceDatafile)); + projectSimilarRuleKeyConfigObject = createProjectConfig(cloneDeep(similarRuleKeyDatafile)); + optimizelySimilarRuleKeyConfigObject = createOptimizelyConfig( + projectSimilarRuleKeyConfigObject, + JSON.stringify(similarRuleKeyDatafile) + ); + projectSimilarExperimentKeyConfigObject = createProjectConfig(cloneDeep(similarExperimentKeyDatafile)); + optimizelySimilarExperimentkeyConfigObject = createOptimizelyConfig( + projectSimilarExperimentKeyConfigObject, + JSON.stringify(similarExperimentKeyDatafile) + ); + }); + + it('should return all experiments except rollouts', () => { + const experimentsMap = optimizelyConfigObject.experimentsMap; + const experimentsCount = Object.keys(experimentsMap).length; + + expect(experimentsCount).toBe(12); + + const allExperiments: Experiment[] = getAllExperimentsFromDatafile(datafile); + + allExperiments.forEach(experiment => { + expect(experimentsMap[experiment.key]).toMatchObject({ + id: experiment.id, + key: experiment.key, + }); + + const variationsMap = experimentsMap[experiment.key].variationsMap; + + experiment.variations.forEach(variation => { + expect(variationsMap[variation.key]).toMatchObject({ + id: variation.id, + key: variation.key, + }); + }); + }); + }); + + it('should keep the last experiment in case of duplicate key and log a warning', () => { + const datafile = getDuplicateExperimentKeyConfig(); + const configObj = createProjectConfig(datafile, JSON.stringify(datafile)); + const optimizelyConfig = createOptimizelyConfig(configObj, JSON.stringify(datafile), logger); + const experimentsMap = optimizelyConfig.experimentsMap; + const duplicateKey = 'experiment_rule'; + const lastExperiment = datafile.experiments[datafile.experiments.length - 1]; + + expect(experimentsMap['experiment_rule'].id).toBe(lastExperiment.id); + expect(logger.warn).toHaveBeenCalledWith(`Duplicate experiment keys found in datafile: ${duplicateKey}`); + }); + + it('should return all the feature flags', function() { + const featureFlagsCount = Object.keys(optimizelyConfigObject.featuresMap).length; + assert.equal(featureFlagsCount, 9); + + const featuresMap = optimizelyConfigObject.featuresMap; + const expectedDeliveryRules = [ + [ + { + id: '594031', + key: '594031', + audiences: '', + variationsMap: { + '594032': { + id: '594032', + key: '594032', + featureEnabled: true, + variablesMap: { + new_content: { + id: '4919852825313280', + key: 'new_content', + type: 'boolean', + value: 'true', + }, + lasers: { + id: '5482802778734592', + key: 'lasers', + type: 'integer', + value: '395', + }, + price: { + id: '6045752732155904', + key: 'price', + type: 'double', + value: '4.99', + }, + message: { + id: '6327227708866560', + key: 'message', + type: 'string', + value: 'Hello audience', + }, + message_info: { + id: '8765345281230956', + key: 'message_info', + type: 'json', + value: '{ "count": 2, "message": "Hello audience" }', + }, + }, + }, + }, + }, + { + id: '594037', + key: '594037', + audiences: '', + variationsMap: { + '594038': { + id: '594038', + key: '594038', + featureEnabled: false, + variablesMap: { + new_content: { + id: '4919852825313280', + key: 'new_content', + type: 'boolean', + value: 'false', + }, + lasers: { + id: '5482802778734592', + key: 'lasers', + type: 'integer', + value: '400', + }, + price: { + id: '6045752732155904', + key: 'price', + type: 'double', + value: '14.99', + }, + message: { + id: '6327227708866560', + key: 'message', + type: 'string', + value: 'Hello', + }, + message_info: { + id: '8765345281230956', + key: 'message_info', + type: 'json', + value: '{ "count": 1, "message": "Hello" }', + }, + }, + }, + }, + }, + ], + [ + { + id: '594060', + key: '594060', + audiences: '', + variationsMap: { + '594061': { + id: '594061', + key: '594061', + featureEnabled: true, + variablesMap: { + miles_to_the_wall: { + id: '5060590313668608', + key: 'miles_to_the_wall', + type: 'double', + value: '27.34', + }, + motto: { + id: '5342065290379264', + key: 'motto', + type: 'string', + value: 'Winter is NOT coming', + }, + soldiers_available: { + id: '6186490220511232', + key: 'soldiers_available', + type: 'integer', + value: '10003', + }, + is_winter_coming: { + id: '6467965197221888', + key: 'is_winter_coming', + type: 'boolean', + value: 'false', + }, + }, + }, + }, + }, + { + id: '594066', + key: '594066', + audiences: '', + variationsMap: { + '594067': { + id: '594067', + key: '594067', + featureEnabled: true, + variablesMap: { + miles_to_the_wall: { + id: '5060590313668608', + key: 'miles_to_the_wall', + type: 'double', + value: '30.34', + }, + motto: { + id: '5342065290379264', + key: 'motto', + type: 'string', + value: 'Winter is coming definitely', + }, + soldiers_available: { + id: '6186490220511232', + key: 'soldiers_available', + type: 'integer', + value: '500', + }, + is_winter_coming: { + id: '6467965197221888', + key: 'is_winter_coming', + type: 'boolean', + value: 'true', + }, + }, + }, + }, + }, + ], + [], + [], + [ + { + id: '599056', + key: '599056', + audiences: '', + variationsMap: { + '599057': { + id: '599057', + key: '599057', + featureEnabled: true, + variablesMap: { + lasers: { + id: '4937719889264640', + key: 'lasers', + type: 'integer', + value: '200', + }, + message: { + id: '6345094772817920', + key: 'message', + type: 'string', + value: "i'm a rollout", + }, + }, + }, + }, + }, + ], + [], + [], + [ + { + id: '594060', + key: '594060', + audiences: '', + variationsMap: { + '594061': { + id: '594061', + key: '594061', + featureEnabled: true, + variablesMap: { + miles_to_the_wall: { + id: '5060590313668608', + key: 'miles_to_the_wall', + type: 'double', + value: '27.34', + }, + motto: { + id: '5342065290379264', + key: 'motto', + type: 'string', + value: 'Winter is NOT coming', + }, + soldiers_available: { + id: '6186490220511232', + key: 'soldiers_available', + type: 'integer', + value: '10003', + }, + is_winter_coming: { + id: '6467965197221888', + key: 'is_winter_coming', + type: 'boolean', + value: 'false', + }, + }, + }, + }, + }, + { + id: '594066', + key: '594066', + audiences: '', + variationsMap: { + '594067': { + id: '594067', + key: '594067', + featureEnabled: true, + variablesMap: { + miles_to_the_wall: { + id: '5060590313668608', + key: 'miles_to_the_wall', + type: 'double', + value: '30.34', + }, + motto: { + id: '5342065290379264', + key: 'motto', + type: 'string', + value: 'Winter is coming definitely', + }, + soldiers_available: { + id: '6186490220511232', + key: 'soldiers_available', + type: 'integer', + value: '500', + }, + is_winter_coming: { + id: '6467965197221888', + key: 'is_winter_coming', + type: 'boolean', + value: 'true', + }, + }, + }, + }, + }, + ], + [ + { + id: '594060', + key: '594060', + audiences: '', + variationsMap: { + '594061': { + id: '594061', + key: '594061', + featureEnabled: true, + variablesMap: { + miles_to_the_wall: { + id: '5060590313668608', + key: 'miles_to_the_wall', + type: 'double', + value: '27.34', + }, + motto: { + id: '5342065290379264', + key: 'motto', + type: 'string', + value: 'Winter is NOT coming', + }, + soldiers_available: { + id: '6186490220511232', + key: 'soldiers_available', + type: 'integer', + value: '10003', + }, + is_winter_coming: { + id: '6467965197221888', + key: 'is_winter_coming', + type: 'boolean', + value: 'false', + }, + }, + }, + }, + }, + { + id: '594066', + key: '594066', + audiences: '', + variationsMap: { + '594067': { + id: '594067', + key: '594067', + featureEnabled: true, + variablesMap: { + miles_to_the_wall: { + id: '5060590313668608', + key: 'miles_to_the_wall', + type: 'double', + value: '30.34', + }, + motto: { + id: '5342065290379264', + key: 'motto', + type: 'string', + value: 'Winter is coming definitely', + }, + soldiers_available: { + id: '6186490220511232', + key: 'soldiers_available', + type: 'integer', + value: '500', + }, + is_winter_coming: { + id: '6467965197221888', + key: 'is_winter_coming', + type: 'boolean', + value: 'true', + }, + }, + }, + }, + }, + ], + ]; + const expectedExperimentRules = [ + [], + [], + [ + { + id: '594098', + key: 'testing_my_feature', + audiences: '', + variationsMap: { + variation: { + id: '594096', + key: 'variation', + featureEnabled: true, + variablesMap: { + num_buttons: { + id: '4792309476491264', + key: 'num_buttons', + type: 'integer', + value: '2', + }, + is_button_animated: { + id: '5073784453201920', + key: 'is_button_animated', + type: 'boolean', + value: 'true', + }, + button_txt: { + id: '5636734406623232', + key: 'button_txt', + type: 'string', + value: 'Buy me NOW', + }, + button_width: { + id: '6199684360044544', + key: 'button_width', + type: 'double', + value: '20.25', + }, + button_info: { + id: '1547854156498475', + key: 'button_info', + type: 'json', + value: '{ "num_buttons": 1, "text": "first variation"}', + }, + }, + }, + control: { + id: '594097', + key: 'control', + featureEnabled: true, + variablesMap: { + num_buttons: { + id: '4792309476491264', + key: 'num_buttons', + type: 'integer', + value: '10', + }, + is_button_animated: { + id: '5073784453201920', + key: 'is_button_animated', + type: 'boolean', + value: 'false', + }, + button_txt: { + id: '5636734406623232', + key: 'button_txt', + type: 'string', + value: 'Buy me', + }, + button_width: { + id: '6199684360044544', + key: 'button_width', + type: 'double', + value: '50.55', + }, + button_info: { + id: '1547854156498475', + key: 'button_info', + type: 'json', + value: '{ "num_buttons": 2, "text": "second variation"}', + }, + }, + }, + variation2: { + id: '594099', + key: 'variation2', + featureEnabled: false, + variablesMap: { + num_buttons: { + id: '4792309476491264', + key: 'num_buttons', + type: 'integer', + value: '10', + }, + is_button_animated: { + id: '5073784453201920', + key: 'is_button_animated', + type: 'boolean', + value: 'false', + }, + button_txt: { + id: '5636734406623232', + key: 'button_txt', + type: 'string', + value: 'Buy me', + }, + button_width: { + id: '6199684360044544', + key: 'button_width', + type: 'double', + value: '50.55', + }, + button_info: { + id: '1547854156498475', + key: 'button_info', + type: 'json', + value: '{ "num_buttons": 0, "text": "default value"}', + }, + }, + }, + }, + }, + ], + [ + { + id: '595010', + key: 'exp_with_group', + audiences: '', + variationsMap: { + var: { + featureEnabled: undefined, + id: '595008', + key: 'var', + variablesMap: {}, + }, + con: { + featureEnabled: undefined, + id: '595009', + key: 'con', + variablesMap: {}, + }, + }, + }, + ], + [ + { + id: '599028', + key: 'test_shared_feature', + audiences: '', + variationsMap: { + treatment: { + id: '599026', + key: 'treatment', + featureEnabled: true, + variablesMap: { + lasers: { + id: '4937719889264640', + key: 'lasers', + type: 'integer', + value: '100', + }, + message: { + id: '6345094772817920', + key: 'message', + type: 'string', + value: 'shared', + }, + }, + }, + control: { + id: '599027', + key: 'control', + featureEnabled: false, + variablesMap: { + lasers: { + id: '4937719889264640', + key: 'lasers', + type: 'integer', + value: '100', + }, + message: { + id: '6345094772817920', + key: 'message', + type: 'string', + value: 'shared', + }, + }, + }, + }, + }, + ], + [], + [ + { + id: '12115595439', + key: 'no_traffic_experiment', + audiences: '', + variationsMap: { + variation_5000: { + featureEnabled: undefined, + id: '12098126629', + key: 'variation_5000', + variablesMap: {}, + }, + variation_10000: { + featureEnabled: undefined, + id: '12098126630', + key: 'variation_10000', + variablesMap: {}, + }, + }, + }, + ], + [ + { + id: '42222', + key: 'group_2_exp_1', + audiences: '"Test attribute users 3"', + variationsMap: { + var_1: { + id: '38901', + key: 'var_1', + featureEnabled: false, + variablesMap: {}, + }, + }, + }, + { + id: '42223', + key: 'group_2_exp_2', + audiences: '"Test attribute users 3"', + variationsMap: { + var_1: { + id: '38905', + key: 'var_1', + featureEnabled: false, + variablesMap: {}, + }, + }, + }, + { + id: '42224', + key: 'group_2_exp_3', + audiences: '"Test attribute users 3"', + variationsMap: { + var_1: { + id: '38906', + key: 'var_1', + featureEnabled: false, + variablesMap: {}, + }, + }, + }, + ], + [ + { + id: '111134', + key: 'test_experiment3', + audiences: '"Test attribute users 3"', + variationsMap: { + control: { + id: '222239', + key: 'control', + featureEnabled: false, + variablesMap: {}, + }, + }, + }, + { + id: '111135', + key: 'test_experiment4', + audiences: '"Test attribute users 3"', + variationsMap: { + control: { + id: '222240', + key: 'control', + featureEnabled: false, + variablesMap: {}, + }, + }, + }, + { + id: '111136', + key: 'test_experiment5', + audiences: '"Test attribute users 3"', + variationsMap: { + control: { + id: '222241', + key: 'control', + featureEnabled: false, + variablesMap: {}, + }, + }, + }, + ], + ]; + + datafile.featureFlags.forEach((featureFlag, index) => { + expect(featuresMap[featureFlag.key]).toMatchObject({ + id: featureFlag.id, + key: featureFlag.key, + }); + + featureFlag.experimentIds.forEach(experimentId => { + const experimentKey = projectConfigObject.experimentIdMap[experimentId].key; + + expect(!!featuresMap[featureFlag.key].experimentsMap[experimentKey]).toBe(true); + }); + + const variablesMap = featuresMap[featureFlag.key].variablesMap; + const deliveryRules = featuresMap[featureFlag.key].deliveryRules; + const experimentRules = featuresMap[featureFlag.key].experimentRules; + + expect(deliveryRules).toEqual(expectedDeliveryRules[index]); + expect(experimentRules).toEqual(expectedExperimentRules[index]); + + featureFlag.variables.forEach(variable => { + // json is represented as sub type of string to support backwards compatibility in datafile. + // project config treats it as a first-class type. + const expectedVariableType = variable.type === 'string' && variable.subType === 'json' ? 'json' : variable.type; + + expect(variablesMap[variable.key]).toMatchObject({ + id: variable.id, + key: variable.key, + type: expectedVariableType, + value: variable.defaultValue, + }); + }); + }); + }); + + it('should correctly merge all feature variables', () => { + const featureFlags = datafile.featureFlags; + const datafileExperimentsMap: Record<string, Experiment> = getAllExperimentsFromDatafile(datafile).reduce( + (experiments, experiment) => { + experiments[experiment.key] = experiment; + return experiments; + }, + {} as Record<string, Experiment> + ); + + featureFlags.forEach(featureFlag => { + const experimentIds = featureFlag.experimentIds; + experimentIds.forEach(experimentId => { + const experimentKey = projectConfigObject.experimentIdMap[experimentId].key; + const experiment = optimizelyConfigObject.experimentsMap[experimentKey]; + const variations = datafileExperimentsMap[experimentKey].variations; + const variationsMap = experiment.variationsMap; + variations.forEach(variation => { + featureFlag.variables.forEach(variable => { + const variableToAssert = variationsMap[variation.key].variablesMap[variable.key]; + // json is represented as sub type of string to support backwards compatibility in datafile. + // project config treats it as a first-class type. + const expectedVariableType = + variable.type === 'string' && variable.subType === 'json' ? 'json' : variable.type; + + expect({ + id: variable.id, + key: variable.key, + type: expectedVariableType, + }).toMatchObject({ + id: variableToAssert.id, + key: variableToAssert.key, + type: variableToAssert.type, + }); + + if (!variation.featureEnabled) { + expect(variable.defaultValue).toBe(variableToAssert.value); + } + }); + }); + }); + }); + }); + + it('should return correct config revision', () => { + expect(optimizelyConfigObject.revision).toBe(datafile.revision); + }); + + it('should return correct config sdkKey ', () => { + expect(optimizelyConfigObject.sdkKey).toBe(datafile.sdkKey); + }); + + it('should return correct config environmentKey ', () => { + expect(optimizelyConfigObject.environmentKey).toBe(datafile.environmentKey); + }); + + it('should return serialized audiences', () => { + const audiencesById = projectTypedAudienceConfigObject.audiencesById; + const audienceConditions = [ + ['or', '3468206642', '3988293898'], + ['or', '3468206642', '3988293898', '3468206646'], + ['not', '3468206642'], + ['or', '3468206642'], + ['and', '3468206642'], + ['3468206642'], + ['3468206642', '3988293898'], + ['and', ['or', '3468206642', '3988293898'], '3468206646'], + [ + 'and', + ['or', '3468206642', ['and', '3988293898', '3468206646']], + ['and', '3988293899', ['or', '3468206647', '3468206643']], + ], + ['and', 'and'], + ['not', ['and', '3468206642', '3988293898']], + [], + ['or', '3468206642', '999999999'], + ]; + + const expectedAudienceOutputs = [ + '"exactString" OR "substringString"', + '"exactString" OR "substringString" OR "exactNumber"', + 'NOT "exactString"', + '"exactString"', + '"exactString"', + '"exactString"', + '"exactString" OR "substringString"', + '("exactString" OR "substringString") AND "exactNumber"', + '("exactString" OR ("substringString" AND "exactNumber")) AND ("exists" AND ("gtNumber" OR "exactBoolean"))', + '', + 'NOT ("exactString" AND "substringString")', + '', + '"exactString" OR "999999999"', + ]; + + for (let testNo = 0; testNo < audienceConditions.length; testNo++) { + const serializedAudiences = OptimizelyConfig.getSerializedAudiences( + audienceConditions[testNo] as string[], + audiencesById + ); + + expect(serializedAudiences).toBe(expectedAudienceOutputs[testNo]); + } + }); + + it('should return correct rollouts', () => { + const rolloutFlag1 = optimizelySimilarRuleKeyConfigObject.featuresMap['flag_1'].deliveryRules[0]; + const rolloutFlag2 = optimizelySimilarRuleKeyConfigObject.featuresMap['flag_2'].deliveryRules[0]; + const rolloutFlag3 = optimizelySimilarRuleKeyConfigObject.featuresMap['flag_3'].deliveryRules[0]; + + expect(rolloutFlag1.id).toBe('9300000004977'); + expect(rolloutFlag1.key).toBe('targeted_delivery'); + expect(rolloutFlag2.id).toBe('9300000004979'); + expect(rolloutFlag2.key).toBe('targeted_delivery'); + expect(rolloutFlag3.id).toBe('9300000004981'); + expect(rolloutFlag3.key).toBe('targeted_delivery'); + }); + + it('should return default SDK and environment key', () => { + expect(optimizelySimilarRuleKeyConfigObject.sdkKey).toBe(''); + expect(optimizelySimilarRuleKeyConfigObject.environmentKey).toBe(''); + }); + + it('should return correct experiments with similar keys', function() { + expect(Object.keys(optimizelySimilarExperimentkeyConfigObject.experimentsMap).length).toBe(1); + + const experimentMapFlag1 = optimizelySimilarExperimentkeyConfigObject.featuresMap['flag1'].experimentsMap; + const experimentMapFlag2 = optimizelySimilarExperimentkeyConfigObject.featuresMap['flag2'].experimentsMap; + + expect(experimentMapFlag1['targeted_delivery'].id).toBe('9300000007569'); + expect(experimentMapFlag2['targeted_delivery'].id).toBe('9300000007573'); + }); +}); diff --git a/packages/optimizely-sdk/lib/core/optimizely_config/index.tests.js b/lib/project_config/optimizely_config.tests.js similarity index 96% rename from packages/optimizely-sdk/lib/core/optimizely_config/index.tests.js rename to lib/project_config/optimizely_config.tests.js index 25ce515fe..22d2b95f3 100644 --- a/packages/optimizely-sdk/lib/core/optimizely_config/index.tests.js +++ b/lib/project_config/optimizely_config.tests.js @@ -1,5 +1,5 @@ /** - * Copyright 2019-2021, Optimizely + * Copyright 2019-2021, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,15 +15,17 @@ */ import { assert } from 'chai'; import { cloneDeep } from 'lodash'; +import sinon from 'sinon'; -import { createOptimizelyConfig, OptimizelyConfig } from './'; -import { createProjectConfig } from '../project_config'; +import { createOptimizelyConfig, OptimizelyConfig } from './optimizely_config'; +import { createProjectConfig } from './project_config'; import { getTestProjectConfigWithFeatures, getTypedAudiencesConfig, getSimilarRuleKeyConfig, - getSimilarExperimentKeyConfig -} from '../../tests/test_data'; + getSimilarExperimentKeyConfig, + getDuplicateExperimentKeyConfig, +} from '../tests/test_data'; var datafile = getTestProjectConfigWithFeatures(); var typedAudienceDatafile = getTypedAudiencesConfig(); @@ -53,6 +55,7 @@ describe('lib/core/optimizely_config', function() { var projectSimilarRuleKeyConfigObject; var optimizelySimilarExperimentkeyConfigObject; var projectSimilarExperimentKeyConfigObject; + beforeEach(function() { projectConfigObject = createProjectConfig(cloneDeep(datafile)); optimizelyConfigObject = createOptimizelyConfig(projectConfigObject, JSON.stringify(datafile)); @@ -85,6 +88,24 @@ describe('lib/core/optimizely_config', function() { }); }); + it('should keep the last experiment in case of duplicate key and log a warning', function() { + const datafile = getDuplicateExperimentKeyConfig(); + const configObj = createProjectConfig(datafile, JSON.stringify(datafile)); + + const logger = { + warn: sinon.spy(), + } + + const optimizelyConfig = createOptimizelyConfig(configObj, JSON.stringify(datafile), logger); + const experimentsMap = optimizelyConfig.experimentsMap; + + const duplicateKey = 'experiment_rule'; + const lastExperiment = datafile.experiments[datafile.experiments.length - 1]; + + assert.equal(experimentsMap['experiment_rule'].id, lastExperiment.id); + assert.isTrue(logger.warn.calledWithExactly(`Duplicate experiment keys found in datafile: ${duplicateKey}`)); + }); + it('should return all the feature flags', function() { var featureFlagsCount = Object.keys(optimizelyConfigObject.featuresMap).length; assert.equal(featureFlagsCount, 9); diff --git a/packages/optimizely-sdk/lib/core/optimizely_config/index.ts b/lib/project_config/optimizely_config.ts similarity index 79% rename from packages/optimizely-sdk/lib/core/optimizely_config/index.ts rename to lib/project_config/optimizely_config.ts index 968806094..b01255c43 100644 --- a/packages/optimizely-sdk/lib/core/optimizely_config/index.ts +++ b/lib/project_config/optimizely_config.ts @@ -1,5 +1,5 @@ /** - * Copyright 2020-2021, Optimizely + * Copyright 2020-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ProjectConfig } from '../project_config'; -import { DEFAULT_OPERATOR_TYPES } from '../condition_tree_evaluator'; +import { LoggerFacade } from '../logging/logger' +import { ProjectConfig } from '../project_config/project_config'; +import { DEFAULT_OPERATOR_TYPES } from '../core/condition_tree_evaluator'; import { Audience, Experiment, @@ -31,7 +32,7 @@ import { Rollout, Variation, VariationVariable, -} from '../../shared_types'; +} from '../shared_types'; interface FeatureVariablesMap { [key: string]: FeatureVariable[]; @@ -61,7 +62,8 @@ export class OptimizelyConfig { public events: OptimizelyEvent[]; private datafile: string; - constructor(configObj: ProjectConfig, datafile: string) { + + constructor(configObj: ProjectConfig, datafile: string, logger?: LoggerFacade) { this.sdkKey = configObj.sdkKey ?? ''; this.environmentKey = configObj.environmentKey ?? ''; this.attributes = configObj.attributes; @@ -74,9 +76,17 @@ export class OptimizelyConfig { return resultMap; }, {}); - const experimentsMapById = OptimizelyConfig.getExperimentsMapById(configObj, featureIdVariablesMap); - this.experimentsMap = OptimizelyConfig.getExperimentsKeyMap(experimentsMapById); - this.featuresMap = OptimizelyConfig.getFeaturesMap(configObj, featureIdVariablesMap, experimentsMapById); + const variableIdMap = OptimizelyConfig.getVariableIdMap(configObj); + + const { experimentsMapById, experimentsMapByKey } = OptimizelyConfig.getExperimentsMap( + configObj, featureIdVariablesMap, variableIdMap, logger, + ); + + this.experimentsMap = experimentsMapByKey; + + this.featuresMap = OptimizelyConfig.getFeaturesMap( + configObj, featureIdVariablesMap, experimentsMapById, variableIdMap + ); this.datafile = datafile; } @@ -242,7 +252,7 @@ export class OptimizelyConfig { * Gets Map of all experiment variations and variables including rollouts * @param {Variation[]} variations * @param {FeatureVariablesMap} featureIdVariableMap - * @param {[id: string]: FeatureVariable} variableIdMap + * @param {{[id: string]: FeatureVariable}} variableIdMap * @param {string} featureId * @returns {[key: string]: Variation} Variations mapped by key */ @@ -292,19 +302,20 @@ export class OptimizelyConfig { /** * Gets list of rollout experiments - * @param {ProjectConfig} configObj - * @param {FeatureVariablesMap} featureVariableIdMap - * @param {string} featureId - * @param {Experiment[]} experiments - * @returns {OptimizelyExperiment[]} List of Optimizely rollout experiments + * @param {ProjectConfig} configObj + * @param {FeatureVariablesMap} featureVariableIdMap + * @param {string} featureId + * @param {Experiment[]} experiments + * @param {{[id: string]: FeatureVariable}} variableIdMap + * @returns {OptimizelyExperiment[]} List of Optimizely rollout experiments */ static getDeliveryRules( configObj: ProjectConfig, featureVariableIdMap: FeatureVariablesMap, featureId: string, - experiments: Experiment[] + experiments: Experiment[], + variableIdMap: {[id: string]: FeatureVariable} ): OptimizelyExperiment[] { - const variableIdMap = OptimizelyConfig.getVariableIdMap(configObj); return experiments.map((experiment) => { return { id: experiment.id, @@ -339,39 +350,53 @@ export class OptimizelyConfig { * Get experiments mapped by their id's which are not part of a rollout * @param {ProjectConfig} configObj * @param {FeatureVariablesMap} featureIdVariableMap - * @returns {[id: string]: OptimizelyExperiment} Experiments mapped by id + * @param {{[id: string]: FeatureVariable}} variableIdMap + * @returns { experimentsMapById: { [id: string]: OptimizelyExperiment }, experimentsMapByKey: OptimizelyExperimentsMap } Experiments mapped by id and key */ - static getExperimentsMapById( + static getExperimentsMap( configObj: ProjectConfig, - featureIdVariableMap: FeatureVariablesMap - ): { [id: string]: OptimizelyExperiment } { - const variableIdMap = OptimizelyConfig.getVariableIdMap(configObj); + featureIdVariableMap: FeatureVariablesMap, + variableIdMap: {[id: string]: FeatureVariable}, + logger?: LoggerFacade, + ) : { experimentsMapById: { [id: string]: OptimizelyExperiment }, experimentsMapByKey: OptimizelyExperimentsMap } { const rolloutExperimentIds = this.getRolloutExperimentIds(configObj.rollouts); - const experiments = configObj.experiments; + const experimentsMapById: { [id : string]: OptimizelyExperiment } = {}; + const experimentsMapByKey: OptimizelyExperimentsMap = {}; - return (experiments || []).reduce((experimentsMap: { [id: string]: OptimizelyExperiment }, experiment) => { - if (rolloutExperimentIds.indexOf(experiment.id) === -1) { - const featureIds = configObj.experimentFeatureMap[experiment.id]; - let featureId = ''; - if (featureIds && featureIds.length > 0) { - featureId = featureIds[0]; - } - const variationsMap = OptimizelyConfig.getVariationsMap( - experiment.variations, - featureIdVariableMap, - variableIdMap, - featureId.toString() - ); - experimentsMap[experiment.id] = { - id: experiment.id, - key: experiment.key, - audiences: OptimizelyConfig.getExperimentAudiences(experiment, configObj), - variationsMap: variationsMap, - }; + const experiments = configObj.experiments || []; + experiments.forEach((experiment) => { + if (rolloutExperimentIds.indexOf(experiment.id) !== -1) { + return; } - return experimentsMap; - }, {}); + + const featureIds = configObj.experimentFeatureMap[experiment.id]; + let featureId = ''; + if (featureIds && featureIds.length > 0) { + featureId = featureIds[0]; + } + const variationsMap = OptimizelyConfig.getVariationsMap( + experiment.variations, + featureIdVariableMap, + variableIdMap, + featureId.toString() + ); + + const optimizelyExperiment: OptimizelyExperiment = { + id: experiment.id, + key: experiment.key, + audiences: OptimizelyConfig.getExperimentAudiences(experiment, configObj), + variationsMap: variationsMap, + }; + + experimentsMapById[experiment.id] = optimizelyExperiment; + if (experimentsMapByKey[experiment.key] && logger) { + logger.warn(`Duplicate experiment keys found in datafile: ${experiment.key}`); + } + experimentsMapByKey[experiment.key] = optimizelyExperiment; + }); + + return { experimentsMapById, experimentsMapByKey }; } /** @@ -391,15 +416,17 @@ export class OptimizelyConfig { /** * Gets Map of all FeatureFlags and associated experiment map inside it - * @param {ProjectConfig} configObj - * @param {FeatureVariablesMap} featureVariableIdMap - * @param {OptimizelyExperimentsMap} experimentsMapById - * @returns {OptimizelyFeaturesMap} OptimizelyFeature mapped by key + * @param {ProjectConfig} configObj + * @param {FeatureVariablesMap} featureVariableIdMap + * @param {OptimizelyExperimentsMap} experimentsMapById + * @param {{[id: string]: FeatureVariable}} variableIdMap + * @returns {OptimizelyFeaturesMap} OptimizelyFeature mapped by key */ static getFeaturesMap( configObj: ProjectConfig, featureVariableIdMap: FeatureVariablesMap, - experimentsMapById: OptimizelyExperimentsMap + experimentsMapById: OptimizelyExperimentsMap, + variableIdMap: {[id: string]: FeatureVariable} ): OptimizelyFeaturesMap { const featuresMap: OptimizelyFeaturesMap = {}; configObj.featureFlags.forEach((featureFlag) => { @@ -428,7 +455,8 @@ export class OptimizelyConfig { configObj, featureVariableIdMap, featureFlag.id, - rollout.experiments + rollout.experiments, + variableIdMap, ); } featuresMap[featureFlag.key] = { @@ -450,6 +478,6 @@ export class OptimizelyConfig { * @param {string} datafile * @returns {OptimizelyConfig} An instance of OptimizelyConfig */ -export function createOptimizelyConfig(configObj: ProjectConfig, datafile: string): OptimizelyConfig { - return new OptimizelyConfig(configObj, datafile); +export function createOptimizelyConfig(configObj: ProjectConfig, datafile: string, logger?: LoggerFacade): OptimizelyConfig { + return new OptimizelyConfig(configObj, datafile, logger); } diff --git a/lib/project_config/polling_datafile_manager.spec.ts b/lib/project_config/polling_datafile_manager.spec.ts new file mode 100644 index 000000000..921ab2a93 --- /dev/null +++ b/lib/project_config/polling_datafile_manager.spec.ts @@ -0,0 +1,1013 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, vi } from 'vitest'; + +import { LOGGER_NAME, PollingDatafileManager} from './polling_datafile_manager'; +import { getMockRepeater } from '../tests/mock/mock_repeater'; +import { getMockAbortableRequest, getMockRequestHandler } from '../tests/mock/mock_request_handler'; +import { getMockLogger } from '../tests/mock/mock_logger'; +import { DEFAULT_AUTHENTICATED_URL_TEMPLATE, DEFAULT_URL_TEMPLATE, MIN_UPDATE_INTERVAL, UPDATE_INTERVAL_BELOW_MINIMUM_MESSAGE } from './constant'; +import { resolvablePromise } from '../utils/promise/resolvablePromise'; +import { ServiceState, StartupLog } from '../service'; +import { getMockSyncCache, getMockAsyncCache } from '../tests/mock/mock_cache'; +import { LogLevel } from '../logging/logger'; + + +describe('PollingDatafileManager', () => { + it('should set name on the logger passed into the constructor', () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const logger = getMockLogger(); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: '123', + logger, + }); + + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME) + }); + + it('should set name on the logger set by setLogger', () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const logger = getMockLogger(); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: '123', + }); + + manager.setLogger(logger); + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME) + }); + + it('should log polling interval below MIN_UPDATE_INTERVAL', () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const logger = getMockLogger(); + + const startupLogs: StartupLog[] = [ + { + level: LogLevel.Warn, + message: 'warn message', + params: [1, 2] + }, + { + level: LogLevel.Error, + message: 'error message', + params: [3, 4] + }, + ]; + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: '123', + logger, + startupLogs, + }); + + manager.start(); + expect(logger.warn).toHaveBeenNthCalledWith(1, 'warn message', 1, 2); + expect(logger.error).toHaveBeenNthCalledWith(1, 'error message', 3, 4); + }); + + + it('starts the repeater with immediateExecution on start', () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: '123', + }); + manager.start(); + expect(repeater.start).toHaveBeenCalledWith(true); + }); + + describe('when cached datafile is available', () => { + it('uses cached version of datafile, resolves onRunning() and calls onUpdate handlers while datafile fetch request waits', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); // response promise is pending + const cache = getMockAsyncCache<string>(); + await cache.set('opt-datafile-keyThatExists', JSON.stringify({ name: 'keyThatExists' })); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + cache, + }); + + manager.start(); + repeater.execute(0); + const listener = vi.fn(); + + manager.onUpdate(listener); + await expect(manager.onRunning()).resolves.toBeUndefined(); + expect(listener).toHaveBeenCalledWith(JSON.stringify({ name: 'keyThatExists' })); + }); + + it('uses cached version of datafile, resolves onRunning() and calls onUpdate handlers even if fetch request fails', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.reject('test error')); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const cache = getMockAsyncCache<string>(); + await cache.set('opt-datafile-keyThatExists', JSON.stringify({ name: 'keyThatExists' })); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + cache, + }); + + manager.start(); + repeater.execute(0); + + const listener = vi.fn(); + + manager.onUpdate(listener); + await expect(manager.onRunning()).resolves.toBeUndefined(); + expect(requestHandler.makeRequest).toHaveBeenCalledOnce(); + expect(listener).toHaveBeenCalledWith(JSON.stringify({ name: 'keyThatExists' })); + }); + + it('uses cached version of datafile, then calls onUpdate when fetch request succeeds after the cache read', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const cache = getMockAsyncCache<string>(); + await cache.set('opt-datafile-keyThatExists', JSON.stringify({ name: 'keyThatExists' })); + const mockResponse = getMockAbortableRequest(); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + cache, + }); + + manager.start(); + repeater.execute(0); + + const listener = vi.fn(); + + manager.onUpdate(listener); + await expect(manager.onRunning()).resolves.toBeUndefined(); + expect(requestHandler.makeRequest).toHaveBeenCalledOnce(); + expect(listener).toHaveBeenNthCalledWith(1, JSON.stringify({ name: 'keyThatExists' })); + + mockResponse.mockResponse.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} }); + await mockResponse.mockResponse; + expect(listener).toHaveBeenNthCalledWith(2, '{"foo": "bar"}'); + }); + + it('ignores cached datafile if fetch request succeeds before cache read completes', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const cache = getMockAsyncCache<string>(); + // this will be resolved after the requestHandler response is resolved + const cachePromise = resolvablePromise<string | undefined>(); + const getSpy = vi.spyOn(cache, 'get'); + getSpy.mockReturnValueOnce(cachePromise.promise); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + cache, + }); + + manager.start(); + repeater.execute(0); + + const listener = vi.fn(); + + manager.onUpdate(listener); + await expect(manager.onRunning()).resolves.toBeUndefined(); + expect(requestHandler.makeRequest).toHaveBeenCalledOnce(); + expect(listener).toHaveBeenCalledWith('{"foo": "bar"}'); + + cachePromise.resolve(JSON.stringify({ name: 'keyThatExists '})); + await cachePromise.promise; + expect(listener).toHaveBeenCalledTimes(1); + expect(listener).not.toHaveBeenCalledWith(JSON.stringify({ name: 'keyThatExists' })); + }); + }); + + it('returns a failing promise to repeater if requestHandler.makeRequest return non-success status code', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 500, body: '', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + }); + + manager.start(); + const ret = repeater.execute(0); + await expect(ret).rejects.toThrow(); + }); + + it('returns a failing promise to repeater if requestHandler.makeRequest promise fails', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.reject('test error')); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + }); + + manager.start(); + const ret = repeater.execute(0); + await expect(ret).rejects.toThrow(); + }); + + it('returns a promise that resolves to repeater if requestHandler.makeRequest succeedes', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + }); + + manager.start(); + const ret = repeater.execute(0); + await expect(ret).resolves.not.toThrow(); + }); + + describe('start', () => { + it('retries specified number of times before rejecting onRunning() and onTerminated() when autoupdate is true', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.reject('test error')); + requestHandler.makeRequest.mockReturnValue(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + initRetry: 5, + autoUpdate: true, + }); + + manager.start(); + + for(let i = 0; i < 6; i++) { + const ret = repeater.execute(0); + await expect(ret).rejects.toThrow(); + } + + expect(requestHandler.makeRequest).toHaveBeenCalledTimes(6); + await expect(manager.onRunning()).rejects.toThrow(); + await expect(manager.onTerminated()).rejects.toThrow(); + }); + + it('retries min(initRetry, 5) amount of times if datafile manager is disposable', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.reject('test error')); + requestHandler.makeRequest.mockReturnValue(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + initRetry: 10, + }); + manager.makeDisposable(); + manager.start(); + + for(let i = 0; i < 6; i++) { + const ret = repeater.execute(i); + await expect(ret).rejects.toThrow(); + } + + expect(repeater.isRunning()).toBe(false); + expect(() => repeater.execute(6)).toThrow(); + }) + + it('retries specified number of times before rejecting onRunning() and onTerminated() when autoupdate is false', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.reject('test error')); + requestHandler.makeRequest.mockReturnValue(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + initRetry: 5, + autoUpdate: false, + }); + + manager.start(); + + for(let i = 0; i < 6; i++) { + const ret = repeater.execute(0); + await expect(ret).rejects.toThrow(); + } + + expect(requestHandler.makeRequest).toHaveBeenCalledTimes(6); + await expect(manager.onRunning()).rejects.toThrow(); + await expect(manager.onTerminated()).rejects.toThrow(); + }); + + it('stops the repeater when initalization fails', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.reject('test error')); + requestHandler.makeRequest.mockReturnValue(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + initRetry: 0, + autoUpdate: false, + }); + + manager.start(); + repeater.execute(0); + + expect(requestHandler.makeRequest).toHaveBeenCalledTimes(1); + await expect(manager.onRunning()).rejects.toThrow(); + await expect(manager.onTerminated()).rejects.toThrow(); + expect(repeater.stop).toHaveBeenCalled(); + }); + + it('retries specified number of times before rejecting onRunning() and onTerminated() when provided cache does not contain datafile', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.reject('test error')); + requestHandler.makeRequest.mockReturnValue(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatDoesNotExists', + initRetry: 5, + cache: getMockAsyncCache(), + }); + + manager.start(); + + for(let i = 0; i < 6; i++) { + const ret = repeater.execute(0); + await expect(ret).rejects.toThrow(); + } + + expect(requestHandler.makeRequest).toHaveBeenCalledTimes(6); + await expect(manager.onRunning()).rejects.toThrow(); + await expect(manager.onTerminated()).rejects.toThrow(); + }); + + it('retries init indefinitely if initRetry is not provided when autoupdate is true', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.reject('test error')); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + }); + + const promiseCallback = vi.fn(); + manager.onRunning().finally(promiseCallback); + + manager.start(); + const testTry = 10_000; + + for(let i = 0; i < testTry; i++) { + const ret = repeater.execute(0); + await expect(ret).rejects.toThrow(); + } + + expect(requestHandler.makeRequest).toHaveBeenCalledTimes(testTry); + expect(promiseCallback).not.toHaveBeenCalled(); + }); + + it('retries init indefinitely if initRetry is not provided when autoupdate is false', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.reject('test error')); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + autoUpdate: false, + }); + + const promiseCallback = vi.fn(); + manager.onRunning().finally(promiseCallback); + + manager.start(); + const testTry = 10_000; + + for(let i = 0; i < testTry; i++) { + const ret = repeater.execute(0); + await expect(ret).rejects.toThrow(); + } + + expect(requestHandler.makeRequest).toHaveBeenCalledTimes(testTry); + expect(promiseCallback).not.toHaveBeenCalled(); + }); + + it('successfully resolves onRunning() and calls onUpdate handlers when fetch request succeeds', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + }); + + const listener = vi.fn(); + + manager.onUpdate(listener); + + manager.start(); + repeater.execute(0); + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(requestHandler.makeRequest).toHaveBeenCalledOnce(); + expect(listener).toHaveBeenCalledWith('{"foo": "bar"}'); + }); + + it('successfully resolves onRunning() and calls onUpdate handlers when fetch request succeeds after retries', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockFailure = getMockAbortableRequest(Promise.reject('test error')); + const mockSuccess = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockFailure) + .mockReturnValueOnce(mockFailure).mockReturnValueOnce(mockSuccess); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + initRetry: 5, + }); + + const listener = vi.fn(); + + manager.onUpdate(listener); + + manager.start(); + for(let i = 0; i < 2; i++) { + const ret = repeater.execute(0); + await expect(ret).rejects.toThrow(); + } + + repeater.execute(0); + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(requestHandler.makeRequest).toHaveBeenCalledTimes(3); + expect(listener).toHaveBeenCalledWith('{"foo": "bar"}'); + }); + + it('stops repeater after successful initialization if autoupdate is false', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + autoUpdate: false, + }); + + manager.start(); + repeater.execute(0); + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(repeater.stop).toHaveBeenCalled(); + }); + + it('stops repeater after successful initialization if disposable is true', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + }); + manager.makeDisposable(); + manager.start(); + repeater.execute(0); + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(repeater.stop).toHaveBeenCalled(); + }); + + it('saves the datafile in cache', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const cache = getMockAsyncCache<string>(); + const spy = vi.spyOn(cache, 'set'); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatDoesNotExists', + cache, + }); + + manager.start(); + repeater.execute(0); + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(spy).toHaveBeenCalledWith('opt-datafile-keyThatDoesNotExists', '{"foo": "bar"}'); + }); + }); + + describe('autoupdate', () => { + it('fetches datafile on each tick and calls onUpdate handlers when fetch request succeeds', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse1 = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + const mockResponse2 = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo2": "bar2"}', headers: {} })); + const mockResponse3 = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo3": "bar3"}', headers: {} })); + + requestHandler.makeRequest.mockReturnValueOnce(mockResponse1) + .mockReturnValueOnce(mockResponse2).mockReturnValueOnce(mockResponse3); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + autoUpdate: true, + }); + + const listener = vi.fn(); + manager.onUpdate(listener); + + manager.start(); + + for(let i = 0; i <3; i++) { + const ret = repeater.execute(0); + await expect(ret).resolves.not.toThrow(); + } + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(requestHandler.makeRequest).toHaveBeenCalledTimes(3); + expect(listener).toHaveBeenNthCalledWith(1, '{"foo": "bar"}'); + expect(listener).toHaveBeenNthCalledWith(2, '{"foo2": "bar2"}'); + expect(listener).toHaveBeenNthCalledWith(3, '{"foo3": "bar3"}'); + }); + + it('saves the datafile each time in cache', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse1 = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + const mockResponse2 = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo2": "bar2"}', headers: {} })); + const mockResponse3 = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo3": "bar3"}', headers: {} })); + + requestHandler.makeRequest.mockReturnValueOnce(mockResponse1) + .mockReturnValueOnce(mockResponse2).mockReturnValueOnce(mockResponse3); + + const cache = getMockAsyncCache<string>(); + const spy = vi.spyOn(cache, 'set'); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatDoesNotExists', + autoUpdate: true, + cache, + }); + + const listener = vi.fn(); + manager.onUpdate(listener); + + manager.start(); + + for(let i = 0; i <3; i++) { + const ret = repeater.execute(0); + await expect(ret).resolves.not.toThrow(); + } + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(spy).toHaveBeenNthCalledWith(1, 'opt-datafile-keyThatDoesNotExists', '{"foo": "bar"}'); + expect(spy).toHaveBeenNthCalledWith(2, 'opt-datafile-keyThatDoesNotExists', '{"foo2": "bar2"}'); + expect(spy).toHaveBeenNthCalledWith(3, 'opt-datafile-keyThatDoesNotExists', '{"foo3": "bar3"}'); + }); + + it('logs an error if fetch request fails and does not call onUpdate handler', async () => { + const logger = getMockLogger(); + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse1 = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + const mockResponse2= getMockAbortableRequest(Promise.reject('test error')); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse1) + .mockReturnValueOnce(mockResponse2).mockReturnValueOnce(mockResponse2); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + autoUpdate: true, + logger, + }); + + const listener = vi.fn(); + manager.onUpdate(listener); + + manager.start(); + for(let i = 0; i < 3; i++) { + await repeater.execute(0).catch(() => {}); + } + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(logger.error).toHaveBeenCalledTimes(2); + expect(listener).toHaveBeenCalledTimes(1); + }); + + it('logs an error if fetch returns non success response and does not call onUpdate handler', async () => { + const logger = getMockLogger(); + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse1 = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + const mockResponse2= getMockAbortableRequest(Promise.resolve({ statusCode: 500, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse1) + .mockReturnValueOnce(mockResponse2).mockReturnValueOnce(mockResponse2); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + autoUpdate: true, + logger, + }); + + const listener = vi.fn(); + manager.onUpdate(listener); + + manager.start(); + for(let i = 0; i < 3; i++) { + await repeater.execute(0).catch(() => {}); + } + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(logger.error).toHaveBeenCalledTimes(2); + expect(listener).toHaveBeenCalledTimes(1); + }); + + it('saves and uses last-modified header', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse1 = getMockAbortableRequest( + Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: { 'last-modified': 'Fri, 08 Mar 2019 18:57:17 GMT' } })); + const mockResponse2 = getMockAbortableRequest( + Promise.resolve({ statusCode: 304, body: '', headers: {} })); + const mockResponse3 = getMockAbortableRequest( + Promise.resolve({ statusCode: 200, body: '{"foo2": "bar2"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse1) + .mockReturnValueOnce(mockResponse2).mockReturnValueOnce(mockResponse3); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatDoesNotExists', + autoUpdate: true, + }); + + manager.start(); + + for(let i = 0; i <3; i++) { + await repeater.execute(0); + } + + await expect(manager.onRunning()).resolves.not.toThrow(); + const secondCallHeaders = requestHandler.makeRequest.mock.calls[1][1]; + expect(secondCallHeaders['if-modified-since']).toBe('Fri, 08 Mar 2019 18:57:17 GMT'); + const thirdCallHeaders = requestHandler.makeRequest.mock.calls[1][1]; + expect(thirdCallHeaders['if-modified-since']).toBe('Fri, 08 Mar 2019 18:57:17 GMT'); + }); + + it('does not call onUpdate handler if status is 304', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse1 = getMockAbortableRequest( + Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: { 'last-modified': 'Fri, 08 Mar 2019 18:57:17 GMT' } })); + const mockResponse2 = getMockAbortableRequest( + Promise.resolve({ statusCode: 304, body: '{"foo2": "bar2"}', headers: {} })); + const mockResponse3 = getMockAbortableRequest( + Promise.resolve({ statusCode: 200, body: '{"foo3": "bar3"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse1) + .mockReturnValueOnce(mockResponse2).mockReturnValueOnce(mockResponse3); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatDoesNotExists', + autoUpdate: true, + }); + + manager.start(); + + const listener = vi.fn(); + manager.onUpdate(listener); + + for(let i = 0; i <3; i++) { + await repeater.execute(0); + } + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(listener).toHaveBeenCalledTimes(2); + expect(listener).not.toHaveBeenCalledWith('{"foo2": "bar2"}'); + expect(listener).toHaveBeenNthCalledWith(1, '{"foo": "bar"}'); + expect(listener).toHaveBeenNthCalledWith(2, '{"foo3": "bar3"}'); + }); + }); + + it('sends the access token in the request Authorization header', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + datafileAccessToken: 'token123', + }); + + manager.start(); + repeater.execute(0); + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(requestHandler.makeRequest).toHaveBeenCalledOnce(); + expect(requestHandler.makeRequest.mock.calls[0][1].Authorization).toBe('Bearer token123'); + }); + + it('uses the provided urlTemplate', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + urlTemplate: 'https://example.com/datafile?key=%s', + }); + + manager.start(); + repeater.execute(0); + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(requestHandler.makeRequest).toHaveBeenCalledOnce(); + expect(requestHandler.makeRequest.mock.calls[0][0]).toBe('https://example.com/datafile?key=keyThatExists'); + }); + + it('uses the default urlTemplate if none is provided and datafileAccessToken is also not provided', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + }); + + manager.start(); + repeater.execute(0); + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(requestHandler.makeRequest).toHaveBeenCalledOnce(); + expect(requestHandler.makeRequest.mock.calls[0][0]).toBe(DEFAULT_URL_TEMPLATE.replace('%s', 'keyThatExists')); + }); + + it('uses the default authenticated urlTemplate if none is provided and datafileAccessToken is provided', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + datafileAccessToken: 'token123', + }); + + manager.start(); + repeater.execute(0); + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(requestHandler.makeRequest).toHaveBeenCalledOnce(); + expect(requestHandler.makeRequest.mock.calls[0][0]).toBe(DEFAULT_AUTHENTICATED_URL_TEMPLATE.replace('%s', 'keyThatExists')); + }); + + it('returns the datafile from get', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + }); + + manager.start(); + repeater.execute(0); + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(manager.get()).toBe('{"foo": "bar"}'); + }); + + it('returns undefined from get before becoming ready', () => { + const repeater = getMockRepeater(); + const mockResponse = getMockAbortableRequest(); + const requestHandler = getMockRequestHandler(); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + }); + manager.start(); + expect(manager.get()).toBeUndefined(); + }); + + it('removes the onUpdate handler when the retuned function is called', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValue(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + autoUpdate: true, + }); + + const listener = vi.fn(); + const removeListener = manager.onUpdate(listener); + + manager.start(); + repeater.execute(0); + + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(listener).toHaveBeenCalledTimes(1); + removeListener(); + + await repeater.execute(0); + expect(listener).toHaveBeenCalledTimes(1); + }); + + describe('stop', () => { + it('rejects onRunning when stop is called if manager state is New', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + autoUpdate: true, + }); + + expect(manager.getState()).toBe(ServiceState.New); + manager.stop(); + await expect(manager.onRunning()).rejects.toThrow(); + }); + + it('rejects onRunning when stop is called if manager state is Starting', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + autoUpdate: true, + }); + + manager.start(); + expect(manager.getState()).toBe(ServiceState.Starting); + manager.stop(); + await expect(manager.onRunning()).rejects.toThrow(); + }); + + it('stops the repeater, set state to Termimated, and resolve onTerminated when stop is called', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + autoUpdate: true, + }); + + manager.start(); + await repeater.execute(0); + await expect(manager.onRunning()).resolves.not.toThrow(); + + manager.stop(); + await expect(manager.onTerminated()).resolves.not.toThrow(); + expect(repeater.stop).toHaveBeenCalled(); + expect(manager.getState()).toBe(ServiceState.Terminated); + }); + + it('aborts the current request if stop is called', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + autoUpdate: true, + }); + + manager.start(); + repeater.execute(0); + + expect(requestHandler.makeRequest).toHaveBeenCalledOnce(); + manager.stop(); + expect(mockResponse.abort).toHaveBeenCalled(); + }); + + it('does not call onUpdate handler after stop is called', async () => { + const repeater = getMockRepeater(); + const requestHandler = getMockRequestHandler(); + const mockResponse = getMockAbortableRequest(Promise.resolve({ statusCode: 200, body: '{"foo": "bar"}', headers: {} })); + requestHandler.makeRequest.mockReturnValueOnce(mockResponse); + + const manager = new PollingDatafileManager({ + repeater, + requestHandler, + sdkKey: 'keyThatExists', + autoUpdate: true, + }); + + const listener = vi.fn(); + manager.onUpdate(listener); + + manager.start(); + repeater.execute(0); + manager.stop(); + + expect(listener).not.toHaveBeenCalled(); + }); + }) +}); diff --git a/lib/project_config/polling_datafile_manager.ts b/lib/project_config/polling_datafile_manager.ts new file mode 100644 index 000000000..ba8e70139 --- /dev/null +++ b/lib/project_config/polling_datafile_manager.ts @@ -0,0 +1,262 @@ +/** + * Copyright 2022-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { sprintf } from '../utils/fns'; +import { DatafileManager, DatafileManagerConfig } from './datafile_manager'; +import { EventEmitter } from '../utils/event_emitter/event_emitter'; +import { DEFAULT_AUTHENTICATED_URL_TEMPLATE, DEFAULT_URL_TEMPLATE } from './constant'; +import { Store } from '../utils/cache/store'; +import { BaseService, ServiceState } from '../service'; +import { RequestHandler, AbortableRequest, Headers, Response } from '../utils/http_request_handler/http'; +import { Repeater } from '../utils/repeater/repeater'; +import { Consumer, Fn } from '../utils/type'; +import { isSuccessStatusCode } from '../utils/http_request_handler/http_util'; +import { + DATAFILE_FETCH_REQUEST_FAILED, + ERROR_FETCHING_DATAFILE, +} from 'error_message'; +import { + ADDING_AUTHORIZATION_HEADER_WITH_BEARER_TOKEN, + MAKING_DATAFILE_REQ_TO_URL_WITH_HEADERS, + RESPONSE_STATUS_CODE, + SAVED_LAST_MODIFIED_HEADER_VALUE_FROM_RESPONSE, +} from 'log_message'; +import { OptimizelyError } from '../error/optimizly_error'; +import { LoggerFacade } from '../logging/logger'; + +export const LOGGER_NAME = 'PollingDatafileManager'; + +import { SERVICE_STOPPED_BEFORE_RUNNING } from '../service'; + +export const FAILED_TO_FETCH_DATAFILE = 'Failed to fetch datafile'; + +export class PollingDatafileManager extends BaseService implements DatafileManager { + private requestHandler: RequestHandler; + private currentDatafile?: string; + private emitter: EventEmitter<{ update: string }>; + private autoUpdate: boolean; + private initRetryRemaining?: number; + private repeater: Repeater; + private lastResponseLastModified?: string; + private datafileUrl: string; + private currentRequest?: AbortableRequest; + private cacheKey: string; + private cache?: Store<string>; + private sdkKey: string; + private datafileAccessToken?: string; + + constructor(config: DatafileManagerConfig) { + super(config.startupLogs); + const { + autoUpdate = false, + sdkKey, + datafileAccessToken, + urlTemplate, + cache, + initRetry, + repeater, + requestHandler, + logger, + } = config; + this.cache = cache; + this.cacheKey = 'opt-datafile-' + sdkKey; + this.sdkKey = sdkKey; + this.datafileAccessToken = datafileAccessToken; + this.requestHandler = requestHandler; + this.emitter = new EventEmitter(); + this.autoUpdate = autoUpdate; + this.initRetryRemaining = initRetry; + this.repeater = repeater; + + if (logger) { + this.setLogger(logger); + } + + const urlTemplateToUse = urlTemplate || + (datafileAccessToken ? DEFAULT_AUTHENTICATED_URL_TEMPLATE : DEFAULT_URL_TEMPLATE); + this.datafileUrl = sprintf(urlTemplateToUse, this.sdkKey); + } + + setLogger(logger: LoggerFacade): void { + this.logger = logger; + this.logger.setName(LOGGER_NAME); + } + + onUpdate(listener: Consumer<string>): Fn { + return this.emitter.on('update', listener); + } + + get(): string | undefined { + return this.currentDatafile; + } + + start(): void { + if (!this.isNew()) { + return; + } + + super.start(); + this.state = ServiceState.Starting; + this.setDatafileFromCacheIfAvailable(); + this.repeater.setTask(this.syncDatafile.bind(this)); + this.repeater.start(true); + } + + makeDisposable(): void { + super.makeDisposable(); + this.initRetryRemaining = Math.min(this.initRetryRemaining ?? 5, 5); + } + + stop(): void { + if (this.isDone()) { + return; + } + + if (this.isNew() || this.isStarting()) { + this.startPromise.reject(new Error( + sprintf(SERVICE_STOPPED_BEFORE_RUNNING, 'PollingDatafileManager') + )); + } + + this.state = ServiceState.Terminated; + this.repeater.stop(); + this.currentRequest?.abort(); + this.emitter.removeAllListeners(); + this.stopPromise.resolve(); + } + + private handleInitFailure(): void { + this.state = ServiceState.Failed; + this.repeater.stop(); + const error = new Error(FAILED_TO_FETCH_DATAFILE); + this.startPromise.reject(error); + this.stopPromise.reject(error); + } + + private handleError(errorOrStatus: Error | number): void { + if (this.isDone()) { + return; + } + + if (errorOrStatus instanceof Error) { + this.logger?.error(ERROR_FETCHING_DATAFILE, errorOrStatus.message, errorOrStatus); + } else { + this.logger?.error(DATAFILE_FETCH_REQUEST_FAILED, errorOrStatus); + } + + if(this.isStarting() && this.initRetryRemaining !== undefined) { + if (this.initRetryRemaining === 0) { + this.handleInitFailure(); + } else { + this.initRetryRemaining--; + } + } + } + + private async onRequestRejected(err: any): Promise<void> { + this.handleError(err); + return Promise.reject(err); + } + + private async onRequestResolved(response: Response): Promise<void> { + if (this.isDone()) { + return; + } + + this.saveLastModified(response.headers); + + if (!isSuccessStatusCode(response.statusCode)) { + this.handleError(response.statusCode); + return Promise.reject(new Error()); + } + + const datafile = this.getDatafileFromResponse(response); + if (datafile) { + this.handleDatafile(datafile); + // if autoUpdate is off, don't need to sync datafile any more + // if disposable, stop the repeater after the first successful fetch + if (!this.autoUpdate || this.disposable) { + this.repeater.stop(); + } + } + } + + private makeDatafileRequest(): AbortableRequest { + const headers: Headers = {}; + if (this.lastResponseLastModified) { + headers['if-modified-since'] = this.lastResponseLastModified; + } + + if (this.datafileAccessToken) { + this.logger?.debug(ADDING_AUTHORIZATION_HEADER_WITH_BEARER_TOKEN); + headers['Authorization'] = `Bearer ${this.datafileAccessToken}`; + } + + this.logger?.debug(MAKING_DATAFILE_REQ_TO_URL_WITH_HEADERS, this.datafileUrl, () => JSON.stringify(headers)); + return this.requestHandler.makeRequest(this.datafileUrl, headers, 'GET'); + } + + private async syncDatafile(): Promise<void> { + this.currentRequest = this.makeDatafileRequest(); + return this.currentRequest.responsePromise + .then(this.onRequestResolved.bind(this), this.onRequestRejected.bind(this)) + .finally(() => this.currentRequest = undefined); + } + + private handleDatafile(datafile: string): void { + if (this.isDone()) { + return; + } + + this.currentDatafile = datafile; + this.cache?.set(this.cacheKey, datafile); + + if (this.isStarting()) { + this.startPromise.resolve(); + this.state = ServiceState.Running; + } + this.emitter.emit('update', datafile); + } + + private getDatafileFromResponse(response: Response): string | undefined{ + this.logger?.debug(RESPONSE_STATUS_CODE, response.statusCode); + if (response.statusCode === 304) { + return undefined; + } + return response.body; + } + + private saveLastModified(headers: Headers): void { + const lastModifiedHeader = headers['last-modified'] || headers['Last-Modified']; + if (lastModifiedHeader !== undefined) { + this.lastResponseLastModified = lastModifiedHeader; + this.logger?.debug(SAVED_LAST_MODIFIED_HEADER_VALUE_FROM_RESPONSE, this.lastResponseLastModified); + } + } + + private async setDatafileFromCacheIfAvailable(): Promise<void> { + if (!this.cache) { + return; + } + try { + const datafile = await this.cache.get(this.cacheKey); + if (datafile && this.isStarting()) { + this.handleDatafile(datafile); + } + } catch { + // ignore error + } + } +} diff --git a/lib/project_config/project_config.spec.ts b/lib/project_config/project_config.spec.ts new file mode 100644 index 000000000..21d18940c --- /dev/null +++ b/lib/project_config/project_config.spec.ts @@ -0,0 +1,1354 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, beforeEach, afterEach, vi, assert, Mock, beforeAll, afterAll } from 'vitest'; +import { sprintf } from '../utils/fns'; +import { keyBy } from '../utils/fns'; +import projectConfig, { ProjectConfig, getHoldoutsForFlag } from './project_config'; +import { FEATURE_VARIABLE_TYPES, LOG_LEVEL } from '../utils/enums'; +import testDatafile from '../tests/test_data'; +import configValidator from '../utils/config_validator'; +import { + INVALID_EXPERIMENT_ID, + INVALID_EXPERIMENT_KEY, + UNEXPECTED_RESERVED_ATTRIBUTE_PREFIX, + UNRECOGNIZED_ATTRIBUTE, + VARIABLE_KEY_NOT_IN_DATAFILE, + FEATURE_NOT_IN_DATAFILE, + UNABLE_TO_CAST_VALUE, +} from 'error_message'; +import { getMockLogger } from '../tests/mock/mock_logger'; +import { VariableType } from '../shared_types'; +import { OptimizelyError } from '../error/optimizly_error'; +import { mock } from 'node:test'; + +const buildLogMessageFromArgs = (args: any[]) => sprintf(args[1], ...args.splice(2)); +const cloneDeep = (obj: any) => JSON.parse(JSON.stringify(obj)); +const logger = getMockLogger(); + +const mockHoldoutToggle = vi.hoisted(() => vi.fn()); + +vi.mock('../feature_toggle', () => { + return { + holdout: mockHoldoutToggle, + }; +}); + +describe('createProjectConfig', () => { + let configObj: ProjectConfig; + + it('should use US region when no region is specified in datafile', () => { + const datafile = testDatafile.getTestProjectConfig(); + const config = projectConfig.createProjectConfig(datafile); + + expect(config.region).toBe('US'); + }); + + it('should parse region specified in datafile correctly', () => { + const datafileUs = testDatafile.getTestProjectConfig(); + datafileUs.region = 'US'; + + const configUs = projectConfig.createProjectConfig(datafileUs); + expect(configUs.region).toBe('US'); + + const datafileEu = testDatafile.getTestProjectConfig(); + datafileEu.region = 'EU'; + const configEu = projectConfig.createProjectConfig(datafileEu); + + expect(configEu.region).toBe('EU'); + }); + + it('should set properties correctly when createProjectConfig is called', () => { + const testData: Record<string, any> = testDatafile.getTestProjectConfig(); + configObj = projectConfig.createProjectConfig(testData as JSON); + + testData.audiences.forEach((audience: any) => { + audience.conditions = JSON.parse(audience.conditions); + }); + + expect(configObj.accountId).toBe(testData.accountId); + expect(configObj.projectId).toBe(testData.projectId); + expect(configObj.revision).toBe(testData.revision); + expect(configObj.events).toEqual(testData.events); + expect(configObj.audiences).toEqual(testData.audiences); + + testData.groups.forEach((group: any) => { + group.experiments.forEach((experiment: any) => { + experiment.groupId = group.id; + experiment.variationKeyMap = keyBy(experiment.variations, 'key'); + }); + }); + + expect(configObj.groups).toEqual(testData.groups); + + const expectedGroupIdMap = { + 666: testData.groups[0], + 667: testData.groups[1], + }; + + expect(configObj.groupIdMap).toEqual(expectedGroupIdMap); + + const expectedExperiments = testData.experiments.slice(); + + Object.entries(configObj.groupIdMap).forEach(([groupId, group]) => { + group.experiments.forEach((experiment: any) => { + experiment.groupId = groupId; + expectedExperiments.push(experiment); + }); + }) + expectedExperiments.forEach((experiment: any) => { + experiment.variationKeyMap = keyBy(experiment.variations, 'key'); + }) + + expect(configObj.experiments).toEqual(expectedExperiments); + + const expectedAttributeKeyMap = { + browser_type: testData.attributes[0], + boolean_key: testData.attributes[1], + integer_key: testData.attributes[2], + double_key: testData.attributes[3], + valid_positive_number: testData.attributes[4], + valid_negative_number: testData.attributes[5], + invalid_number: testData.attributes[6], + array: testData.attributes[7], + }; + + expect(configObj.attributeKeyMap).toEqual(expectedAttributeKeyMap); + + const expectedExperimentKeyMap = { + testExperiment: configObj.experiments[0], + testExperimentWithAudiences: configObj.experiments[1], + testExperimentNotRunning: configObj.experiments[2], + testExperimentLaunched: configObj.experiments[3], + groupExperiment1: configObj.experiments[4], + groupExperiment2: configObj.experiments[5], + overlappingGroupExperiment1: configObj.experiments[6], + }; + + expect(configObj.experimentKeyMap).toEqual(expectedExperimentKeyMap); + + const expectedEventKeyMap = { + testEvent: testData.events[0], + 'Total Revenue': testData.events[1], + testEventWithAudiences: testData.events[2], + testEventWithoutExperiments: testData.events[3], + testEventWithExperimentNotRunning: testData.events[4], + testEventWithMultipleExperiments: testData.events[5], + testEventLaunched: testData.events[6], + }; + + expect(configObj.eventKeyMap).toEqual(expectedEventKeyMap); + + const expectedExperimentIdMap = { + '111127': configObj.experiments[0], + '122227': configObj.experiments[1], + '133337': configObj.experiments[2], + '144447': configObj.experiments[3], + '442': configObj.experiments[4], + '443': configObj.experiments[5], + '444': configObj.experiments[6], + }; + + expect(configObj.experimentIdMap).toEqual(expectedExperimentIdMap); + }); + + it('should not mutate the datafile', () => { + const datafile = testDatafile.getTypedAudiencesConfig(); + const datafileClone = cloneDeep(datafile); + projectConfig.createProjectConfig(datafile as any); + + expect(datafile).toEqual(datafileClone); + }); +}); + +describe('createProjectConfig - feature management', () => { + let configObj: ProjectConfig; + + beforeEach(() => { + configObj = projectConfig.createProjectConfig(testDatafile.getTestProjectConfigWithFeatures()); + }); + + it('should create a rolloutIdMap from rollouts in the datafile', () => { + expect(configObj.rolloutIdMap).toEqual(testDatafile.datafileWithFeaturesExpectedData.rolloutIdMap); + }); + + it('should create a variationVariableUsageMap from rollouts and experiments with features in the datafile', () => { + expect(configObj.variationVariableUsageMap).toEqual( + testDatafile.datafileWithFeaturesExpectedData.variationVariableUsageMap + ); + }); + + it('should create a featureKeyMap from features in the datafile', () => { + expect(configObj.featureKeyMap).toEqual(testDatafile.datafileWithFeaturesExpectedData.featureKeyMap); + }); + + it('should add variations from rollout experiements to the variationKeyMap', () => { + expect(configObj.variationIdMap['594032']).toEqual({ + variables: [ + { value: 'true', id: '4919852825313280' }, + { value: '395', id: '5482802778734592' }, + { value: '4.99', id: '6045752732155904' }, + { value: 'Hello audience', id: '6327227708866560' }, + { value: '{ "count": 2, "message": "Hello audience" }', id: '8765345281230956' }, + ], + featureEnabled: true, + key: '594032', + id: '594032', + }); + + expect(configObj.variationIdMap['594038']).toEqual({ + variables: [ + { value: 'false', id: '4919852825313280' }, + { value: '400', id: '5482802778734592' }, + { value: '14.99', id: '6045752732155904' }, + { value: 'Hello', id: '6327227708866560' }, + { value: '{ "count": 1, "message": "Hello" }', id: '8765345281230956' }, + ], + featureEnabled: false, + key: '594038', + id: '594038', + }); + + expect(configObj.variationIdMap['594061']).toEqual({ + variables: [ + { value: '27.34', id: '5060590313668608' }, + { value: 'Winter is NOT coming', id: '5342065290379264' }, + { value: '10003', id: '6186490220511232' }, + { value: 'false', id: '6467965197221888' }, + ], + featureEnabled: true, + key: '594061', + id: '594061', + }); + + expect(configObj.variationIdMap['594067']).toEqual({ + variables: [ + { value: '30.34', id: '5060590313668608' }, + { value: 'Winter is coming definitely', id: '5342065290379264' }, + { value: '500', id: '6186490220511232' }, + { value: 'true', id: '6467965197221888' }, + ], + featureEnabled: true, + key: '594067', + id: '594067', + }); + }); +}); + +describe('createProjectConfig - flag variations', () => { + let configObj: ProjectConfig; + + beforeEach(() => { + configObj = projectConfig.createProjectConfig(testDatafile.getTestDecideProjectConfig()); + }); + + it('should populate flagVariationsMap correctly', function() { + const allVariationsForFlag = configObj.flagVariationsMap; + const feature1Variations = allVariationsForFlag.feature_1; + const feature2Variations = allVariationsForFlag.feature_2; + const feature3Variations = allVariationsForFlag.feature_3; + const feature1VariationsKeys = feature1Variations.map(variation => { + return variation.key; + }, {}); + const feature2VariationsKeys = feature2Variations.map(variation => { + return variation.key; + }, {}); + const feature3VariationsKeys = feature3Variations.map(variation => { + return variation.key; + }, {}); + + expect(feature1VariationsKeys).toEqual(['a', 'b', '3324490633', '3324490562', '18257766532']); + expect(feature2VariationsKeys).toEqual(['variation_with_traffic', 'variation_no_traffic']); + expect(feature3VariationsKeys).toEqual([]); + }); +}); + +describe('createProjectConfig - cmab experiments', () => { + it('should populate cmab field correctly', function() { + const datafile = testDatafile.getTestProjectConfig(); + datafile.experiments[0].cmab = { + attributeIds: ['808797688', '808797689'], + trafficAllocation: 3141, + }; + + datafile.experiments[2].cmab = { + attributeIds: ['808797689'], + trafficAllocation: 1414, + }; + + const configObj = projectConfig.createProjectConfig(datafile); + + const experiment0 = configObj.experiments[0]; + expect(experiment0.cmab).toEqual({ + trafficAllocation: 3141, + attributeIds: ['808797688', '808797689'], + }); + + const experiment1 = configObj.experiments[1]; + expect(experiment1.cmab).toBeUndefined(); + + const experiment2 = configObj.experiments[2]; + expect(experiment2.cmab).toEqual({ + trafficAllocation: 1414, + attributeIds: ['808797689'], + }); + }); +}); + +const getHoldoutDatafile = () => { + const datafile = testDatafile.getTestDecideProjectConfig(); + + // Add holdouts to the datafile + datafile.holdouts = [ + { + id: 'holdout_id_1', + key: 'holdout_1', + status: 'Running', + includedFlags: [], + excludedFlags: [], + audienceIds: ['13389130056'], + audienceConditions: ['or', '13389130056'], + variations: [ + { + id: 'var_id_1', + key: 'holdout_variation_1', + variables: [] + } + ], + trafficAllocation: [ + { + entityId: 'var_id_1', + endOfRange: 5000 + } + ] + }, + { + id: 'holdout_id_2', + key: 'holdout_2', + status: 'Running', + includedFlags: [], + excludedFlags: ['44829230000'], + audienceIds: [], + audienceConditions: [], + variations: [ + { + id: 'var_id_2', + key: 'holdout_variation_2', + variables: [] + } + ], + trafficAllocation: [ + { + entityId: 'var_id_2', + endOfRange: 1000 + } + ] + }, + { + id: 'holdout_id_3', + key: 'holdout_3', + status: 'Draft', + includedFlags: ['4482920077'], + excludedFlags: [], + audienceIds: [], + audienceConditions: [], + variations: [ + { + id: 'var_id_2', + key: 'holdout_variation_2', + variables: [] + } + ], + trafficAllocation: [ + { + entityId: 'var_id_2', + endOfRange: 1000 + } + ] + } + ]; + + return datafile; +} + +describe('createProjectConfig - holdouts, feature toggle is on', () => { + beforeAll(() => { + mockHoldoutToggle.mockReturnValue(true); + }); + + afterAll(() => { + mockHoldoutToggle.mockReset(); + }); + + it('should populate holdouts fields correctly', function() { + const datafile = getHoldoutDatafile(); + + mockHoldoutToggle.mockReturnValue(true); + + const configObj = projectConfig.createProjectConfig(JSON.parse(JSON.stringify(datafile))); + + expect(configObj.holdouts).toHaveLength(3); + configObj.holdouts.forEach((holdout, i) => { + expect(holdout).toEqual(expect.objectContaining(datafile.holdouts[i])); + expect(holdout.variationKeyMap).toEqual( + keyBy(datafile.holdouts[i].variations, 'key') + ); + }); + + expect(configObj.holdoutIdMap).toEqual({ + holdout_id_1: configObj.holdouts[0], + holdout_id_2: configObj.holdouts[1], + holdout_id_3: configObj.holdouts[2], + }); + + expect(configObj.globalHoldouts).toHaveLength(2); + expect(configObj.globalHoldouts).toEqual([ + configObj.holdouts[0], // holdout_1 has empty includedFlags + configObj.holdouts[1] // holdout_2 has empty includedFlags + ]); + + expect(configObj.includedHoldouts).toEqual({ + feature_1: [configObj.holdouts[2]], // holdout_3 includes feature_1 (ID: 4482920077) + }); + + expect(configObj.excludedHoldouts).toEqual({ + feature_3: [configObj.holdouts[1]] // holdout_2 excludes feature_3 (ID: 44829230000) + }); + + expect(configObj.flagHoldoutsMap).toEqual({}); + }); + + it('should handle empty holdouts array', function() { + const datafile = testDatafile.getTestProjectConfig(); + + const configObj = projectConfig.createProjectConfig(datafile); + + expect(configObj.holdouts).toEqual([]); + expect(configObj.holdoutIdMap).toEqual({}); + expect(configObj.globalHoldouts).toEqual([]); + expect(configObj.includedHoldouts).toEqual({}); + expect(configObj.excludedHoldouts).toEqual({}); + expect(configObj.flagHoldoutsMap).toEqual({}); + }); + + it('should handle undefined includedFlags and excludedFlags in holdout', function() { + const datafile = getHoldoutDatafile(); + datafile.holdouts[0].includedFlags = undefined; + datafile.holdouts[0].excludedFlags = undefined; + + const configObj = projectConfig.createProjectConfig(JSON.parse(JSON.stringify(datafile))); + + expect(configObj.holdouts).toHaveLength(3); + expect(configObj.holdouts[0].includedFlags).toEqual([]); + expect(configObj.holdouts[0].excludedFlags).toEqual([]); + }); +}); + +describe('getHoldoutsForFlag: feature toggle is on', () => { + beforeAll(() => { + mockHoldoutToggle.mockReturnValue(true); + }); + + afterAll(() => { + mockHoldoutToggle.mockReset(); + }); + + it('should return all applicable holdouts for a flag', () => { + const datafile = getHoldoutDatafile(); + const configObj = projectConfig.createProjectConfig(JSON.parse(JSON.stringify(datafile))); + + const feature1Holdouts = getHoldoutsForFlag(configObj, 'feature_1'); + expect(feature1Holdouts).toHaveLength(3); + expect(feature1Holdouts).toEqual([ + configObj.holdouts[0], + configObj.holdouts[1], + configObj.holdouts[2], + ]); + + const feature2Holdouts = getHoldoutsForFlag(configObj, 'feature_2'); + expect(feature2Holdouts).toHaveLength(2); + expect(feature2Holdouts).toEqual([ + configObj.holdouts[0], + configObj.holdouts[1], + ]); + + const feature3Holdouts = getHoldoutsForFlag(configObj, 'feature_3'); + expect(feature3Holdouts).toHaveLength(1); + expect(feature3Holdouts).toEqual([ + configObj.holdouts[0], + ]); + }); +}); + +describe('getExperimentId', () => { + let testData: Record<string, any>; + let configObj: ProjectConfig; + let createdLogger: ReturnType<typeof getMockLogger>; + + beforeEach(function() { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + createdLogger = getMockLogger(); + }); + + it('should retrieve experiment ID for valid experiment key in getExperimentId', function() { + expect(projectConfig.getExperimentId(configObj, testData.experiments[0].key)).toBe(testData.experiments[0].id); + }); + + it('should throw error for invalid experiment key in getExperimentId', function() { + expect(() => { + projectConfig.getExperimentId(configObj, 'invalidExperimentId'); + }).toThrowError( + expect.objectContaining({ + baseMessage: INVALID_EXPERIMENT_KEY, + params: ['invalidExperimentId'], + }) + ); + }); +}); + +describe('getLayerId', () => { + let testData: Record<string, any>; + let configObj: ProjectConfig; + + beforeEach(function() { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + }); + + it('should retrieve layer ID for valid experiment key in getLayerId', function() { + expect(projectConfig.getLayerId(configObj, '111127')).toBe('4'); + }); + + it('should throw error for invalid experiment key in getLayerId', function() { + expect(() => projectConfig.getLayerId(configObj, 'invalidExperimentKey')).toThrowError( + expect.objectContaining({ + baseMessage: INVALID_EXPERIMENT_ID, + params: ['invalidExperimentKey'], + }) + ); + }); +}); + +describe('getAttributeId', () => { + let testData: Record<string, any>; + let configObj: ProjectConfig; + let createdLogger: ReturnType<typeof getMockLogger>; + + beforeEach(function() { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + createdLogger = getMockLogger(); + }); + + it('should retrieve attribute ID for valid attribute key in getAttributeId', function() { + expect(projectConfig.getAttributeId(configObj, 'browser_type')).toBe('111094'); + }); + + it('should retrieve attribute ID for reserved attribute key in getAttributeId', function() { + expect(projectConfig.getAttributeId(configObj, '$opt_user_agent')).toBe('$opt_user_agent'); + }); + + it('should return null for invalid attribute key in getAttributeId', function() { + expect(projectConfig.getAttributeId(configObj, 'invalidAttributeKey', createdLogger)).toBe(null); + expect(createdLogger.warn).toHaveBeenCalledWith(UNRECOGNIZED_ATTRIBUTE, 'invalidAttributeKey'); + }); + + it('should return null for invalid attribute key in getAttributeId', () => { + // Adding attribute in key map with reserved prefix + configObj.attributeKeyMap['$opt_some_reserved_attribute'] = { + id: '42', + }; + + expect(projectConfig.getAttributeId(configObj, '$opt_some_reserved_attribute', createdLogger)).toBe('42'); + expect(createdLogger.warn).toHaveBeenCalledWith( + UNEXPECTED_RESERVED_ATTRIBUTE_PREFIX, + '$opt_some_reserved_attribute', + '$opt_' + ); + }); +}); + +describe('getEventId', () => { + let testData: Record<string, any>; + let configObj: ProjectConfig; + + beforeEach(function() { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + }); + + it('should retrieve event ID for valid event key in getEventId', function() { + expect(projectConfig.getEventId(configObj, 'testEvent')).toBe('111095'); + }); + + it('should return null for invalid event key in getEventId', function() { + expect(projectConfig.getEventId(configObj, 'invalidEventKey')).toBe(null); + }); +}); + +describe('getExperimentStatus', () => { + let testData: Record<string, any>; + let configObj: ProjectConfig; + + beforeEach(function() { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + }); + + it('should retrieve experiment status for valid experiment key in getExperimentStatus', function() { + expect(projectConfig.getExperimentStatus(configObj, testData.experiments[0].key)).toBe( + testData.experiments[0].status + ); + }); + + it('should throw error for invalid experiment key in getExperimentStatus', function() { + expect(() => { + projectConfig.getExperimentStatus(configObj, 'invalidExeprimentKey'); + }).toThrowError(OptimizelyError); + }); +}); + +describe('isActive', () => { + let testData: Record<string, any>; + let configObj: ProjectConfig; + + beforeEach(function() { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + }); + + it('should return true if experiment status is set to Running in isActive', function() { + expect(projectConfig.isActive(configObj, 'testExperiment')).toBe(true); + }); + + it('should return false if experiment status is not set to Running in isActive', function() { + expect(projectConfig.isActive(configObj, 'testExperimentNotRunning')).toBe(false); + }); +}); + +describe('isRunning', () => { + let testData: Record<string, any>; + let configObj: ProjectConfig; + + beforeEach(() => { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + }); + + it('should return true if experiment status is set to Running in isRunning', function() { + expect(projectConfig.isRunning(configObj, 'testExperiment')).toBe(true); + }); + + it('should return false if experiment status is not set to Running in isRunning', function() { + expect(projectConfig.isRunning(configObj, 'testExperimentLaunched')).toBe(false); + }); +}); + +describe('getVariationKeyFromId', () => { + let testData: Record<string, any>; + let configObj: ProjectConfig; + + beforeEach(function() { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + }); + it('should retrieve variation key for valid experiment key and variation ID in getVariationKeyFromId', function() { + expect(projectConfig.getVariationKeyFromId(configObj, testData.experiments[0].variations[0].id)).toBe( + testData.experiments[0].variations[0].key + ); + }); +}); + +describe('getTrafficAllocation', () => { + let testData: Record<string, any>; + let configObj: ProjectConfig; + + beforeEach(function() { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + }); + + it('should retrieve traffic allocation given valid experiment key in getTrafficAllocation', function() { + expect(projectConfig.getTrafficAllocation(configObj, testData.experiments[0].id)).toEqual( + testData.experiments[0].trafficAllocation + ); + }); + + it('should throw error for invalid experient key in getTrafficAllocation', function() { + expect(() => { + projectConfig.getTrafficAllocation(configObj, 'invalidExperimentId'); + }).toThrowError( + expect.objectContaining({ + baseMessage: INVALID_EXPERIMENT_ID, + params: ['invalidExperimentId'], + }) + ); + }); +}); + +describe('getVariationIdFromExperimentAndVariationKey', () => { + let testData: Record<string, any>; + let configObj: ProjectConfig; + + beforeEach(function() { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + }); + + it('should return the variation id for the given experiment key and variation key', () => { + expect( + projectConfig.getVariationIdFromExperimentAndVariationKey( + configObj, + testData.experiments[0].key, + testData.experiments[0].variations[0].key + ) + ).toBe(testData.experiments[0].variations[0].id); + }); +}); + +describe('getSendFlagDecisionsValue', () => { + let testData: Record<string, any>; + let configObj: ProjectConfig; + + beforeEach(function() { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + }); + + it('should return false when sendFlagDecisions is undefined', () => { + configObj.sendFlagDecisions = undefined; + + expect(projectConfig.getSendFlagDecisionsValue(configObj)).toBe(false); + }); + + it('should return false when sendFlagDecisions is set to false', () => { + configObj.sendFlagDecisions = false; + + expect(projectConfig.getSendFlagDecisionsValue(configObj)).toBe(false); + }); + + it('should return true when sendFlagDecisions is set to true', () => { + configObj.sendFlagDecisions = true; + + expect(projectConfig.getSendFlagDecisionsValue(configObj)).toBe(true); + }); +}); + +describe('getVariableForFeature', function() { + let featureManagementLogger: ReturnType<typeof getMockLogger>; + let configObj: ProjectConfig; + + beforeEach(() => { + featureManagementLogger = getMockLogger(); + configObj = projectConfig.createProjectConfig(testDatafile.getTestProjectConfigWithFeatures()); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return a variable object for a valid variable and feature key', function() { + const featureKey = 'test_feature_for_experiment'; + const variableKey = 'num_buttons'; + const result = projectConfig.getVariableForFeature(configObj, featureKey, variableKey, featureManagementLogger); + + expect(result).toEqual({ + type: 'integer', + key: 'num_buttons', + id: '4792309476491264', + defaultValue: '10', + }); + }); + + it('should return null for an invalid variable key and a valid feature key', function() { + const featureKey = 'test_feature_for_experiment'; + const variableKey = 'notARealVariable____'; + const result = projectConfig.getVariableForFeature(configObj, featureKey, variableKey, featureManagementLogger); + + expect(result).toBe(null); + expect(featureManagementLogger.error).toHaveBeenCalledOnce(); + expect(featureManagementLogger.error).toHaveBeenCalledWith( + VARIABLE_KEY_NOT_IN_DATAFILE, + 'notARealVariable____', + 'test_feature_for_experiment' + ); + }); + + it('should return null for an invalid feature key', function() { + const featureKey = 'notARealFeature_____'; + const variableKey = 'num_buttons'; + const result = projectConfig.getVariableForFeature(configObj, featureKey, variableKey, featureManagementLogger); + + expect(result).toBe(null); + expect(featureManagementLogger.error).toHaveBeenCalledOnce(); + expect(featureManagementLogger.error).toHaveBeenCalledWith(FEATURE_NOT_IN_DATAFILE, 'notARealFeature_____'); + }); + + it('should return null for an invalid variable key and an invalid feature key', function() { + const featureKey = 'notARealFeature_____'; + const variableKey = 'notARealVariable____'; + const result = projectConfig.getVariableForFeature(configObj, featureKey, variableKey, featureManagementLogger); + + expect(result).toBe(null); + expect(featureManagementLogger.error).toHaveBeenCalledOnce(); + expect(featureManagementLogger.error).toHaveBeenCalledWith(FEATURE_NOT_IN_DATAFILE, 'notARealFeature_____'); + }); +}); + +describe('getVariableValueForVariation', () => { + let featureManagementLogger: ReturnType<typeof getMockLogger>; + let configObj: ProjectConfig; + + beforeEach(() => { + featureManagementLogger = getMockLogger(); + configObj = projectConfig.createProjectConfig(testDatafile.getTestProjectConfigWithFeatures()); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return a value for a valid variation and variable', () => { + const variation = configObj.variationIdMap['594096']; + let variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.num_buttons; + let result = projectConfig.getVariableValueForVariation(configObj, variable, variation, featureManagementLogger); + expect(result).toBe('2'); + + variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.is_button_animated; + result = projectConfig.getVariableValueForVariation(configObj, variable, variation, featureManagementLogger); + + expect(result).toBe('true'); + + variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.button_txt; + result = projectConfig.getVariableValueForVariation(configObj, variable, variation, featureManagementLogger); + + expect(result).toBe('Buy me NOW'); + + variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.button_width; + result = projectConfig.getVariableValueForVariation(configObj, variable, variation, featureManagementLogger); + + expect(result).toBe('20.25'); + }); + + it('should return null for a null variation', () => { + const variation = null; + const variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.num_buttons; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const result = projectConfig.getVariableValueForVariation(configObj, variable, variation, featureManagementLogger); + + expect(result).toBe(null); + }); + + it('should return null for a null variable', () => { + const variation = configObj.variationIdMap['594096']; + const variable = null; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const result = projectConfig.getVariableValueForVariation(configObj, variable, variation, featureManagementLogger); + + expect(result).toBe(null); + }); + + it('should return null for a null variation and null variable', () => { + const variation = null; + const variable = null; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const result = projectConfig.getVariableValueForVariation(configObj, variable, variation, featureManagementLogger); + + expect(result).toBe(null); + }); + + it('should return null for a variation whose id is not in the datafile', () => { + const variation = { + key: 'some_variation', + id: '999999999999', + variables: [], + }; + const variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.num_buttons; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + const result = projectConfig.getVariableValueForVariation(configObj, variable, variation, featureManagementLogger); + + expect(result).toBe(null); + }); + + it('should return null if the variation does not have a value for this variable', () => { + const variation = configObj.variationIdMap['595008']; // This variation has no variable values associated with it + const variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.num_buttons; + const result = projectConfig.getVariableValueForVariation(configObj, variable, variation, featureManagementLogger); + + expect(result).toBe(null); + }); +}); + +describe('getTypeCastValue', () => { + let featureManagementLogger: ReturnType<typeof getMockLogger>; + let configObj: ProjectConfig; + + beforeEach(() => { + featureManagementLogger = getMockLogger(); + configObj = projectConfig.createProjectConfig(testDatafile.getTestProjectConfigWithFeatures()); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should cast a boolean', () => { + let result = projectConfig.getTypeCastValue( + 'true', + FEATURE_VARIABLE_TYPES.BOOLEAN as VariableType, + featureManagementLogger + ); + + expect(result).toBe(true); + + result = projectConfig.getTypeCastValue( + 'false', + FEATURE_VARIABLE_TYPES.BOOLEAN as VariableType, + featureManagementLogger + ); + + expect(result).toBe(false); + }); + + it('should cast an integer', () => { + let result = projectConfig.getTypeCastValue( + '50', + FEATURE_VARIABLE_TYPES.INTEGER as VariableType, + featureManagementLogger + ); + + expect(result).toBe(50); + + result = projectConfig.getTypeCastValue( + '-7', + FEATURE_VARIABLE_TYPES.INTEGER as VariableType, + featureManagementLogger + ); + + expect(result).toBe(-7); + + result = projectConfig.getTypeCastValue( + '0', + FEATURE_VARIABLE_TYPES.INTEGER as VariableType, + featureManagementLogger + ); + + expect(result).toBe(0); + }); + + it('should cast a double', () => { + let result = projectConfig.getTypeCastValue( + '89.99', + FEATURE_VARIABLE_TYPES.DOUBLE as VariableType, + featureManagementLogger + ); + + expect(result).toBe(89.99); + + result = projectConfig.getTypeCastValue( + '-257.21', + FEATURE_VARIABLE_TYPES.DOUBLE as VariableType, + featureManagementLogger + ); + + expect(result).toBe(-257.21); + + result = projectConfig.getTypeCastValue( + '0', + FEATURE_VARIABLE_TYPES.DOUBLE as VariableType, + featureManagementLogger + ); + + expect(result).toBe(0); + + result = projectConfig.getTypeCastValue( + '10', + FEATURE_VARIABLE_TYPES.DOUBLE as VariableType, + featureManagementLogger + ); + + expect(result).toBe(10); + }); + + it('should return a string unmodified', () => { + const result = projectConfig.getTypeCastValue( + 'message', + FEATURE_VARIABLE_TYPES.STRING as VariableType, + featureManagementLogger + ); + + expect(result).toBe('message'); + }); + + it('should return null and logs an error for an invalid boolean', () => { + const result = projectConfig.getTypeCastValue( + 'notabool', + FEATURE_VARIABLE_TYPES.BOOLEAN as VariableType, + featureManagementLogger + ); + + expect(result).toBe(null); + expect(featureManagementLogger.error).toHaveBeenCalledWith(UNABLE_TO_CAST_VALUE, 'notabool', 'boolean'); + }); + + it('should return null and logs an error for an invalid integer', () => { + const result = projectConfig.getTypeCastValue( + 'notanint', + FEATURE_VARIABLE_TYPES.INTEGER as VariableType, + featureManagementLogger + ); + + expect(result).toBe(null); + expect(featureManagementLogger.error).toHaveBeenCalledWith(UNABLE_TO_CAST_VALUE, 'notanint', 'integer'); + }); + + it('should return null and logs an error for an invalid double', () => { + const result = projectConfig.getTypeCastValue( + 'notadouble', + FEATURE_VARIABLE_TYPES.DOUBLE as VariableType, + featureManagementLogger + ); + + expect(result).toBe(null); + expect(featureManagementLogger.error).toHaveBeenCalledWith(UNABLE_TO_CAST_VALUE, 'notadouble', 'double'); + }); +}); + +describe('getAudiencesById', () => { + let configObj: ProjectConfig; + + beforeEach(() => { + configObj = projectConfig.createProjectConfig(testDatafile.getTypedAudiencesConfig()); + }); + + it('should retrieve audiences by checking first in typedAudiences, and then second in audiences', () => { + expect(projectConfig.getAudiencesById(configObj)).toEqual(testDatafile.typedAudiencesById); + }); +}); + +describe('getExperimentAudienceConditions', () => { + let configObj: ProjectConfig; + let testData: Record<string, any>; + + beforeEach(() => { + testData = cloneDeep(testDatafile.getTestProjectConfig()); + }); + + it('should retrieve audiences for valid experiment key', () => { + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + + expect(projectConfig.getExperimentAudienceConditions(configObj, testData.experiments[1].id)).toEqual(['11154']); + }); + + it('should throw error for invalid experiment key', () => { + configObj = projectConfig.createProjectConfig(cloneDeep(testData) as JSON); + + expect(() => { + projectConfig.getExperimentAudienceConditions(configObj, 'invalidExperimentId'); + }).toThrowError( + expect.objectContaining({ + baseMessage: INVALID_EXPERIMENT_ID, + params: ['invalidExperimentId'], + }) + ); + }); + + it('should return experiment audienceIds if experiment has no audienceConditions', () => { + configObj = projectConfig.createProjectConfig(testDatafile.getTypedAudiencesConfig()); + const result = projectConfig.getExperimentAudienceConditions(configObj, '11564051718'); + + expect(result).toEqual([ + '3468206642', + '3988293898', + '3988293899', + '3468206646', + '3468206647', + '3468206644', + '3468206643', + ]); + }); + + it('should return experiment audienceConditions if experiment has audienceConditions', () => { + configObj = projectConfig.createProjectConfig(testDatafile.getTypedAudiencesConfig()); + // audience_combinations_experiment has both audienceConditions and audienceIds + // audienceConditions should be preferred over audienceIds + const result = projectConfig.getExperimentAudienceConditions(configObj, '1323241598'); + + expect(result).toEqual([ + 'and', + ['or', '3468206642', '3988293898'], + ['or', '3988293899', '3468206646', '3468206647', '3468206644', '3468206643'], + ]); + }); +}); + +describe('isFeatureExperiment', () => { + it('should return true for a feature test', () => { + const config = projectConfig.createProjectConfig(testDatafile.getTestProjectConfigWithFeatures()); + const result = projectConfig.isFeatureExperiment(config, '594098'); // id of 'testing_my_feature' + + expect(result).toBe(true); + }); + + it('should return false for an A/B test', () => { + const config = projectConfig.createProjectConfig(testDatafile.getTestProjectConfig()); + const result = projectConfig.isFeatureExperiment(config, '111127'); // id of 'testExperiment' + + expect(result).toBe(false); + }); + + it('should return true for a feature test in a mutex group', () => { + const config = projectConfig.createProjectConfig(testDatafile.getMutexFeatureTestsConfig()); + let result = projectConfig.isFeatureExperiment(config, '17128410791'); // id of 'f_test1' + + expect(result).toBe(true); + + result = projectConfig.isFeatureExperiment(config, '17139931304'); // id of 'f_test2' + + expect(result).toBe(true); + }); +}); + +describe('getAudienceSegments', () => { + it('should return all qualified segments from an audience', () => { + const dummyQualifiedAudienceJson = { + id: '13389142234', + conditions: [ + 'and', + [ + 'or', + [ + 'or', + { + value: 'odp-segment-1', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + ], + ], + name: 'odp-segment-1', + }; + + const dummyQualifiedAudienceJsonSegments = projectConfig.getAudienceSegments(dummyQualifiedAudienceJson); + + expect(dummyQualifiedAudienceJsonSegments).toEqual(['odp-segment-1']); + + const dummyUnqualifiedAudienceJson = { + id: '13389142234', + conditions: [ + 'and', + [ + 'or', + [ + 'or', + { + value: 'odp-segment-1', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'invalid', + }, + ], + ], + ], + name: 'odp-segment-1', + }; + + const dummyUnqualifiedAudienceJsonSegments = projectConfig.getAudienceSegments(dummyUnqualifiedAudienceJson); + + expect(dummyUnqualifiedAudienceJsonSegments).toEqual([]); + }); +}); + +describe('integrations: with segments', () => { + let configObj: ProjectConfig; + + beforeEach(() => { + configObj = projectConfig.createProjectConfig(testDatafile.getOdpIntegratedConfigWithSegments()); + }); + + it('should convert integrations from the datafile into the project config', () => { + expect(configObj.integrations).toBeDefined(); + expect(configObj.integrations.length).toBe(4); + }); + + it('should populate odpIntegrationConfig', () => { + expect(configObj.odpIntegrationConfig.integrated).toBe(true); + + assert(configObj.odpIntegrationConfig.integrated); + + expect(configObj.odpIntegrationConfig.odpConfig.apiKey).toBe('W4WzcEs-ABgXorzY7h1LCQ'); + expect(configObj.odpIntegrationConfig.odpConfig.apiHost).toBe('https://api.zaius.com/'); + expect(configObj.odpIntegrationConfig.odpConfig.pixelUrl).toBe('https://jumbe.zaius.com/'); + expect(configObj.odpIntegrationConfig.odpConfig.segmentsToCheck).toEqual([ + 'odp-segment-1', + 'odp-segment-2', + 'odp-segment-3', + ]); + }); +}); + +describe('integrations: without segments', () => { + let config: ProjectConfig; + beforeEach(() => { + config = projectConfig.createProjectConfig(testDatafile.getOdpIntegratedConfigWithoutSegments()); + }); + + it('should convert integrations from the datafile into the project config', () => { + expect(config.integrations).toBeDefined(); + expect(config.integrations.length).toBe(3); + }); + + it('should populate odpIntegrationConfig', () => { + expect(config.odpIntegrationConfig.integrated).toBe(true); + + assert(config.odpIntegrationConfig.integrated); + + expect(config.odpIntegrationConfig.odpConfig.apiKey).toBe('W4WzcEs-ABgXorzY7h1LCQ'); + expect(config.odpIntegrationConfig.odpConfig.apiHost).toBe('https://api.zaius.com/'); + expect(config.odpIntegrationConfig.odpConfig.pixelUrl).toBe('https://jumbe.zaius.com/'); + expect(config.odpIntegrationConfig.odpConfig.segmentsToCheck).toEqual([]); + }); +}); + +describe('without valid integration key', () => { + it('should throw an error when parsing the project config due to integrations not containing a key', () => { + const odpIntegratedConfigWithoutKey = testDatafile.getOdpIntegratedConfigWithoutKey(); + + expect(() => projectConfig.createProjectConfig(odpIntegratedConfigWithoutKey)).toThrowError(OptimizelyError); + }); +}); + +describe('without integrations', () => { + let config: ProjectConfig; + + beforeEach(() => { + const odpIntegratedConfigWithSegments = testDatafile.getOdpIntegratedConfigWithSegments(); + const noIntegrationsConfigWithSegments = { ...odpIntegratedConfigWithSegments, integrations: [] }; + config = projectConfig.createProjectConfig(noIntegrationsConfigWithSegments); + }); + + it('should convert integrations from the datafile into the project config', () => { + expect(config.integrations.length).toBe(0); + }); + + it('should populate odpIntegrationConfig', () => { + expect(config.odpIntegrationConfig.integrated).toBe(false); + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(config.odpIntegrationConfig.odpConfig).toBeUndefined(); + }); +}); + +describe('tryCreatingProjectConfig', () => { + let mockJsonSchemaValidator: Mock; + beforeEach(() => { + mockJsonSchemaValidator = vi.fn().mockReturnValue(true); + vi.spyOn(configValidator, 'validateDatafile').mockReturnValue(true); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('should return a project config object created by createProjectConfig when all validation is applied and there are no errors', () => { + const configDatafile = { + foo: 'bar', + experiments: [{ key: 'a' }, { key: 'b' }], + }; + + vi.spyOn(configValidator, 'validateDatafile').mockReturnValueOnce(configDatafile); + + const configObj = { + foo: 'bar', + experimentKeyMap: { + a: { key: 'a', variationKeyMap: {} }, + b: { key: 'b', variationKeyMap: {} }, + }, + }; + + // stubJsonSchemaValidator.returns(true); + mockJsonSchemaValidator.mockReturnValueOnce(true); + + const result = projectConfig.tryCreatingProjectConfig({ + datafile: configDatafile, + jsonSchemaValidator: mockJsonSchemaValidator, + logger: logger, + }); + + expect(result).toMatchObject(configObj); + }); + + it('should throw an error when validateDatafile throws', function() { + vi.spyOn(configValidator, 'validateDatafile').mockImplementationOnce(() => { + throw new Error(); + }); + mockJsonSchemaValidator.mockReturnValueOnce(true); + + expect(() => + projectConfig.tryCreatingProjectConfig({ + datafile: { foo: 'bar' }, + jsonSchemaValidator: mockJsonSchemaValidator, + logger: logger, + }) + ).toThrowError(); + }); + + it('should throw an error when jsonSchemaValidator.validate throws', function() { + vi.spyOn(configValidator, 'validateDatafile').mockReturnValueOnce(true); + mockJsonSchemaValidator.mockImplementationOnce(() => { + throw new Error(); + }); + + expect(() => + projectConfig.tryCreatingProjectConfig({ + datafile: { foo: 'bar' }, + jsonSchemaValidator: mockJsonSchemaValidator, + logger: logger, + }) + ).toThrowError(); + }); + + it('should skip json validation when jsonSchemaValidator is not provided', function() { + const configDatafile = { + foo: 'bar', + experiments: [{ key: 'a' }, { key: 'b' }], + }; + + vi.spyOn(configValidator, 'validateDatafile').mockReturnValueOnce(configDatafile); + + const configObj = { + foo: 'bar', + experimentKeyMap: { + a: { key: 'a', variationKeyMap: {} }, + b: { key: 'b', variationKeyMap: {} }, + }, + }; + + const result = projectConfig.tryCreatingProjectConfig({ + datafile: configDatafile, + logger: logger, + }); + + expect(result).toMatchObject(configObj); + expect(logger.error).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/optimizely-sdk/lib/core/project_config/index.tests.js b/lib/project_config/project_config.tests.js similarity index 73% rename from packages/optimizely-sdk/lib/core/project_config/index.tests.js rename to lib/project_config/project_config.tests.js index 479d7c18c..d69afda46 100644 --- a/packages/optimizely-sdk/lib/core/project_config/index.tests.js +++ b/lib/project_config/project_config.tests.js @@ -1,11 +1,11 @@ /** - * Copyright 2016-2022, Optimizely + * Copyright 2016-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -16,30 +16,40 @@ import sinon from 'sinon'; import { assert } from 'chai'; import { forEach, cloneDeep } from 'lodash'; -import { sprintf } from '../../utils/fns'; -import { getLogger } from '../../modules/logging'; - -import fns from '../../utils/fns'; -import projectConfig from './'; -import { - ERROR_MESSAGES, - FEATURE_VARIABLE_TYPES, - LOG_LEVEL, -} from '../../utils/enums'; -import * as loggerPlugin from '../../plugins/logger'; -import testDatafile from '../../tests/test_data'; -import configValidator from '../../utils/config_validator'; +import { sprintf } from '../utils/fns'; +import fns from '../utils/fns'; +import projectConfig from './project_config'; +import { FEATURE_VARIABLE_TYPES, LOG_LEVEL } from '../utils/enums'; +import testDatafile from '../tests/test_data'; +import configValidator from '../utils/config_validator'; +import { + INVALID_EXPERIMENT_ID, + INVALID_EXPERIMENT_KEY, + UNEXPECTED_RESERVED_ATTRIBUTE_PREFIX, + UNRECOGNIZED_ATTRIBUTE, + VARIABLE_KEY_NOT_IN_DATAFILE, + FEATURE_NOT_IN_DATAFILE, + UNABLE_TO_CAST_VALUE +} from 'error_message'; + +var createLogger = () => ({ + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + child: () => createLogger(), +}) var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); -var logger = getLogger(); +var logger = createLogger(); -describe('lib/core/project_config', function () { - describe('createProjectConfig method', function () { - it('should set properties correctly when createProjectConfig is called', function () { +describe('lib/core/project_config', function() { + describe('createProjectConfig method', function() { + it('should set properties correctly when createProjectConfig is called', function() { var testData = testDatafile.getTestProjectConfig(); var configObj = projectConfig.createProjectConfig(testData); - forEach(testData.audiences, function (audience) { + forEach(testData.audiences, function(audience) { audience.conditions = JSON.parse(audience.conditions); }); @@ -48,8 +58,8 @@ describe('lib/core/project_config', function () { assert.strictEqual(configObj.revision, testData.revision); assert.deepEqual(configObj.events, testData.events); assert.deepEqual(configObj.audiences, testData.audiences); - testData.groups.forEach(function (group) { - group.experiments.forEach(function (experiment) { + testData.groups.forEach(function(group) { + group.experiments.forEach(function(experiment) { experiment.groupId = group.id; experiment.variationKeyMap = fns.keyBy(experiment.variations, 'key'); }); @@ -64,14 +74,14 @@ describe('lib/core/project_config', function () { assert.deepEqual(configObj.groupIdMap, expectedGroupIdMap); var expectedExperiments = testData.experiments; - forEach(configObj.groupIdMap, function (group, Id) { - forEach(group.experiments, function (experiment) { + forEach(configObj.groupIdMap, function(group, Id) { + forEach(group.experiments, function(experiment) { experiment.groupId = Id; expectedExperiments.push(experiment); }); }); - forEach(expectedExperiments, function (experiment) { + forEach(expectedExperiments, function(experiment) { experiment.variationKeyMap = fns.keyBy(experiment.variations, 'key'); }); @@ -174,35 +184,35 @@ describe('lib/core/project_config', function () { }; }); - it('should not mutate the datafile', function () { + it('should not mutate the datafile', function() { var datafile = testDatafile.getTypedAudiencesConfig(); var datafileClone = cloneDeep(datafile); projectConfig.createProjectConfig(datafile); assert.deepEqual(datafileClone, datafile); }); - describe('feature management', function () { + describe('feature management', function() { var configObj; - beforeEach(function () { + beforeEach(function() { configObj = projectConfig.createProjectConfig(testDatafile.getTestProjectConfigWithFeatures()); }); - it('creates a rolloutIdMap from rollouts in the datafile', function () { + it('creates a rolloutIdMap from rollouts in the datafile', function() { assert.deepEqual(configObj.rolloutIdMap, testDatafile.datafileWithFeaturesExpectedData.rolloutIdMap); }); - it('creates a variationVariableUsageMap from rollouts and experiments with features in the datafile', function () { + it('creates a variationVariableUsageMap from rollouts and experiments with features in the datafile', function() { assert.deepEqual( configObj.variationVariableUsageMap, testDatafile.datafileWithFeaturesExpectedData.variationVariableUsageMap ); }); - it('creates a featureKeyMap from feature flags in the datafile', function () { + it('creates a featureKeyMap from feature flags in the datafile', function() { assert.deepEqual(configObj.featureKeyMap, testDatafile.datafileWithFeaturesExpectedData.featureKeyMap); }); - it('adds variations from rollout experiments to variationIdMap', function () { + it('adds variations from rollout experiments to variationIdMap', function() { assert.deepEqual(configObj.variationIdMap['594032'], { variables: [ { value: 'true', id: '4919852825313280' }, @@ -252,24 +262,24 @@ describe('lib/core/project_config', function () { }); }); - describe('flag variations', function () { + describe('flag variations', function() { var configObj; - beforeEach(function () { + beforeEach(function() { configObj = projectConfig.createProjectConfig(testDatafile.getTestDecideProjectConfig()); }); - it('it should populate flagVariationsMap correctly', function () { + it('it should populate flagVariationsMap correctly', function() { var allVariationsForFlag = configObj.flagVariationsMap; var feature1Variations = allVariationsForFlag.feature_1; var feature2Variations = allVariationsForFlag.feature_2; var feature3Variations = allVariationsForFlag.feature_3; - var feature1VariationsKeys = feature1Variations.map((variation) => { + var feature1VariationsKeys = feature1Variations.map(variation => { return variation.key; }, {}); - var feature2VariationsKeys = feature2Variations.map((variation) => { + var feature2VariationsKeys = feature2Variations.map(variation => { return variation.key; }, {}); - var feature3VariationsKeys = feature3Variations.map((variation) => { + var feature3VariationsKeys = feature3Variations.map(variation => { return variation.key; }, {}); @@ -280,131 +290,135 @@ describe('lib/core/project_config', function () { }); }); - describe('projectConfig helper methods', function () { + describe('projectConfig helper methods', function() { var testData = cloneDeep(testDatafile.getTestProjectConfig()); var configObj; - var createdLogger = loggerPlugin.createLogger({ logLevel: LOG_LEVEL.INFO }); + var createdLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); - beforeEach(function () { + beforeEach(function() { configObj = projectConfig.createProjectConfig(cloneDeep(testData)); - sinon.stub(createdLogger, 'log'); + sinon.stub(createdLogger, 'warn'); }); - afterEach(function () { - createdLogger.log.restore(); + afterEach(function() { + createdLogger.warn.restore(); }); - it('should retrieve experiment ID for valid experiment key in getExperimentId', function () { + it('should retrieve experiment ID for valid experiment key in getExperimentId', function() { assert.strictEqual( projectConfig.getExperimentId(configObj, testData.experiments[0].key), testData.experiments[0].id ); }); - it('should throw error for invalid experiment key in getExperimentId', function () { - assert.throws(function () { + it('should throw error for invalid experiment key in getExperimentId', function() { + const ex = assert.throws(function() { projectConfig.getExperimentId(configObj, 'invalidExperimentKey'); - }, sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, 'PROJECT_CONFIG', 'invalidExperimentKey')); + }); + assert.equal(ex.baseMessage, INVALID_EXPERIMENT_KEY); + assert.deepEqual(ex.params, ['invalidExperimentKey']); }); - it('should retrieve layer ID for valid experiment key in getLayerId', function () { + it('should retrieve layer ID for valid experiment key in getLayerId', function() { assert.strictEqual(projectConfig.getLayerId(configObj, '111127'), '4'); }); - it('should throw error for invalid experiment key in getLayerId', function () { - assert.throws(function () { + it('should throw error for invalid experiment key in getLayerId', function() { + const ex = assert.throws(function() { projectConfig.getLayerId(configObj, 'invalidExperimentKey'); - }, sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_ID, 'PROJECT_CONFIG', 'invalidExperimentKey')); + }); + assert.equal(ex.baseMessage, INVALID_EXPERIMENT_ID); + assert.deepEqual(ex.params, ['invalidExperimentKey']); }); - it('should retrieve attribute ID for valid attribute key in getAttributeId', function () { + it('should retrieve attribute ID for valid attribute key in getAttributeId', function() { assert.strictEqual(projectConfig.getAttributeId(configObj, 'browser_type'), '111094'); }); - it('should retrieve attribute ID for reserved attribute key in getAttributeId', function () { + it('should retrieve attribute ID for reserved attribute key in getAttributeId', function() { assert.strictEqual(projectConfig.getAttributeId(configObj, '$opt_user_agent'), '$opt_user_agent'); }); - it('should return null for invalid attribute key in getAttributeId', function () { + it('should return null for invalid attribute key in getAttributeId', function() { assert.isNull(projectConfig.getAttributeId(configObj, 'invalidAttributeKey', createdLogger)); - assert.strictEqual( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'PROJECT_CONFIG: Unrecognized attribute invalidAttributeKey provided. Pruning before sending event to Optimizely.' - ); + + assert.deepEqual(createdLogger.warn.lastCall.args, [UNRECOGNIZED_ATTRIBUTE, 'invalidAttributeKey']); }); - it('should return null for invalid attribute key in getAttributeId', function () { + it('should return null for invalid attribute key in getAttributeId', function() { // Adding attribute in key map with reserved prefix configObj.attributeKeyMap['$opt_some_reserved_attribute'] = { id: '42', key: '$opt_some_reserved_attribute', }; assert.strictEqual(projectConfig.getAttributeId(configObj, '$opt_some_reserved_attribute', createdLogger), '42'); - assert.strictEqual( - buildLogMessageFromArgs(createdLogger.log.lastCall.args), - 'Attribute $opt_some_reserved_attribute unexpectedly has reserved prefix $opt_; using attribute ID instead of reserved attribute name.' - ); + + assert.deepEqual(createdLogger.warn.lastCall.args, [UNEXPECTED_RESERVED_ATTRIBUTE_PREFIX, '$opt_some_reserved_attribute', '$opt_']); }); - it('should retrieve event ID for valid event key in getEventId', function () { + it('should retrieve event ID for valid event key in getEventId', function() { assert.strictEqual(projectConfig.getEventId(configObj, 'testEvent'), '111095'); }); - it('should return null for invalid event key in getEventId', function () { + it('should return null for invalid event key in getEventId', function() { assert.isNull(projectConfig.getEventId(configObj, 'invalidEventKey')); }); - it('should retrieve experiment status for valid experiment key in getExperimentStatus', function () { + it('should retrieve experiment status for valid experiment key in getExperimentStatus', function() { assert.strictEqual( projectConfig.getExperimentStatus(configObj, testData.experiments[0].key), testData.experiments[0].status ); }); - it('should throw error for invalid experiment key in getExperimentStatus', function () { - assert.throws(function () { + it('should throw error for invalid experiment key in getExperimentStatus', function() { + const ex = assert.throws(function() { projectConfig.getExperimentStatus(configObj, 'invalidExperimentKey'); - }, sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, 'PROJECT_CONFIG', 'invalidExperimentKey')); + }); + assert.equal(ex.baseMessage, INVALID_EXPERIMENT_KEY); + assert.deepEqual(ex.params, ['invalidExperimentKey']); }); - it('should return true if experiment status is set to Running in isActive', function () { + it('should return true if experiment status is set to Running in isActive', function() { assert.isTrue(projectConfig.isActive(configObj, 'testExperiment')); }); - it('should return false if experiment status is not set to Running in isActive', function () { + it('should return false if experiment status is not set to Running in isActive', function() { assert.isFalse(projectConfig.isActive(configObj, 'testExperimentNotRunning')); }); - it('should return true if experiment status is set to Running in isRunning', function () { + it('should return true if experiment status is set to Running in isRunning', function() { assert.isTrue(projectConfig.isRunning(configObj, 'testExperiment')); }); - it('should return false if experiment status is not set to Running in isRunning', function () { + it('should return false if experiment status is not set to Running in isRunning', function() { assert.isFalse(projectConfig.isRunning(configObj, 'testExperimentLaunched')); }); - it('should retrieve variation key for valid experiment key and variation ID in getVariationKeyFromId', function () { + it('should retrieve variation key for valid experiment key and variation ID in getVariationKeyFromId', function() { assert.deepEqual( projectConfig.getVariationKeyFromId(configObj, testData.experiments[0].variations[0].id), testData.experiments[0].variations[0].key ); }); - it('should retrieve traffic allocation given valid experiment key in getTrafficAllocation', function () { + it('should retrieve traffic allocation given valid experiment key in getTrafficAllocation', function() { assert.deepEqual( projectConfig.getTrafficAllocation(configObj, testData.experiments[0].id), testData.experiments[0].trafficAllocation ); }); - it('should throw error for invalid experient key in getTrafficAllocation', function () { - assert.throws(function () { + it('should throw error for invalid experient key in getTrafficAllocation', function() { + const ex = assert.throws(function() { projectConfig.getTrafficAllocation(configObj, 'invalidExperimentId'); - }, sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_ID, 'PROJECT_CONFIG', 'invalidExperimentId')); + }); + assert.equal(ex.baseMessage, INVALID_EXPERIMENT_ID); + assert.deepEqual(ex.params, ['invalidExperimentId']); }); - - describe('#getVariationIdFromExperimentAndVariationKey', function () { - it('should return the variation id for the given experiment key and variation key', function () { + + describe('#getVariationIdFromExperimentAndVariationKey', function() { + it('should return the variation id for the given experiment key and variation key', function() { assert.strictEqual( projectConfig.getVariationIdFromExperimentAndVariationKey( configObj, @@ -416,45 +430,42 @@ describe('lib/core/project_config', function () { }); }); - describe('#getSendFlagDecisionsValue', function () { - it('should return false when sendFlagDecisions is undefined', function () { + describe('#getSendFlagDecisionsValue', function() { + it('should return false when sendFlagDecisions is undefined', function() { configObj.sendFlagDecisions = undefined; - assert.deepEqual( - projectConfig.getSendFlagDecisionsValue(configObj), - false - ); + assert.deepEqual(projectConfig.getSendFlagDecisionsValue(configObj), false); }); - it('should return false when sendFlagDecisions is set to false', function () { + it('should return false when sendFlagDecisions is set to false', function() { configObj.sendFlagDecisions = false; - assert.deepEqual( - projectConfig.getSendFlagDecisionsValue(configObj), - false - ); + assert.deepEqual(projectConfig.getSendFlagDecisionsValue(configObj), false); }); - it('should return true when sendFlagDecisions is set to true', function () { + it('should return true when sendFlagDecisions is set to true', function() { configObj.sendFlagDecisions = true; - assert.deepEqual( - projectConfig.getSendFlagDecisionsValue(configObj), - true - ); + assert.deepEqual(projectConfig.getSendFlagDecisionsValue(configObj), true); }); }); - describe('feature management', function () { - var featureManagementLogger = loggerPlugin.createLogger({ logLevel: LOG_LEVEL.INFO }); - beforeEach(function () { + describe('feature management', function() { + var featureManagementLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); + beforeEach(function() { configObj = projectConfig.createProjectConfig(testDatafile.getTestProjectConfigWithFeatures()); - sinon.stub(featureManagementLogger, 'log'); + sinon.stub(featureManagementLogger, 'warn'); + sinon.stub(featureManagementLogger, 'error'); + sinon.stub(featureManagementLogger, 'info'); + sinon.stub(featureManagementLogger, 'debug'); }); - afterEach(function () { - featureManagementLogger.log.restore(); + afterEach(function() { + featureManagementLogger.warn.restore(); + featureManagementLogger.error.restore(); + featureManagementLogger.info.restore(); + featureManagementLogger.debug.restore(); }); - describe('getVariableForFeature', function () { - it('should return a variable object for a valid variable and feature key', function () { + describe('getVariableForFeature', function() { + it('should return a variable object for a valid variable and feature key', function() { var featureKey = 'test_feature_for_experiment'; var variableKey = 'num_buttons'; var result = projectConfig.getVariableForFeature(configObj, featureKey, variableKey, featureManagementLogger); @@ -466,45 +477,39 @@ describe('lib/core/project_config', function () { }); }); - it('should return null for an invalid variable key and a valid feature key', function () { + it('should return null for an invalid variable key and a valid feature key', function() { var featureKey = 'test_feature_for_experiment'; var variableKey = 'notARealVariable____'; var result = projectConfig.getVariableForFeature(configObj, featureKey, variableKey, featureManagementLogger); assert.strictEqual(result, null); - sinon.assert.calledOnce(featureManagementLogger.log); - assert.strictEqual( - buildLogMessageFromArgs(featureManagementLogger.log.lastCall.args), - 'PROJECT_CONFIG: Variable with key "notARealVariable____" associated with feature with key "test_feature_for_experiment" is not in datafile.' - ); + sinon.assert.calledOnce(featureManagementLogger.error); + + assert.deepEqual(featureManagementLogger.error.lastCall.args, [VARIABLE_KEY_NOT_IN_DATAFILE, 'notARealVariable____', 'test_feature_for_experiment']); }); - it('should return null for an invalid feature key', function () { + it('should return null for an invalid feature key', function() { var featureKey = 'notARealFeature_____'; var variableKey = 'num_buttons'; var result = projectConfig.getVariableForFeature(configObj, featureKey, variableKey, featureManagementLogger); assert.strictEqual(result, null); - sinon.assert.calledOnce(featureManagementLogger.log); - assert.strictEqual( - buildLogMessageFromArgs(featureManagementLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key notARealFeature_____ is not in datafile.' - ); + sinon.assert.calledOnce(featureManagementLogger.error); + + assert.deepEqual(featureManagementLogger.error.lastCall.args, [FEATURE_NOT_IN_DATAFILE, 'notARealFeature_____']); }); - it('should return null for an invalid variable key and an invalid feature key', function () { + it('should return null for an invalid variable key and an invalid feature key', function() { var featureKey = 'notARealFeature_____'; var variableKey = 'notARealVariable____'; var result = projectConfig.getVariableForFeature(configObj, featureKey, variableKey, featureManagementLogger); assert.strictEqual(result, null); - sinon.assert.calledOnce(featureManagementLogger.log); - assert.strictEqual( - buildLogMessageFromArgs(featureManagementLogger.log.lastCall.args), - 'PROJECT_CONFIG: Feature key notARealFeature_____ is not in datafile.' - ); + sinon.assert.calledOnce(featureManagementLogger.error); + + assert.deepEqual(featureManagementLogger.error.lastCall.args, [FEATURE_NOT_IN_DATAFILE, 'notARealFeature_____']); }); }); - describe('getVariableValueForVariation', function () { - it('returns a value for a valid variation and variable', function () { + describe('getVariableValueForVariation', function() { + it('returns a value for a valid variation and variable', function() { var variation = configObj.variationIdMap['594096']; var variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.num_buttons; var result = projectConfig.getVariableValueForVariation( @@ -528,7 +533,7 @@ describe('lib/core/project_config', function () { assert.strictEqual(result, '20.25'); }); - it('returns null for a null variation', function () { + it('returns null for a null variation', function() { var variation = null; var variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.num_buttons; var result = projectConfig.getVariableValueForVariation( @@ -540,7 +545,7 @@ describe('lib/core/project_config', function () { assert.strictEqual(result, null); }); - it('returns null for a null variable', function () { + it('returns null for a null variable', function() { var variation = configObj.variationIdMap['594096']; var variable = null; var result = projectConfig.getVariableValueForVariation( @@ -552,7 +557,7 @@ describe('lib/core/project_config', function () { assert.strictEqual(result, null); }); - it('returns null for a null variation and null variable', function () { + it('returns null for a null variation and null variable', function() { var variation = null; var variable = null; var result = projectConfig.getVariableValueForVariation( @@ -564,7 +569,7 @@ describe('lib/core/project_config', function () { assert.strictEqual(result, null); }); - it('returns null for a variation whose id is not in the datafile', function () { + it('returns null for a variation whose id is not in the datafile', function() { var variation = { key: 'some_variation', id: '999999999999', @@ -580,7 +585,7 @@ describe('lib/core/project_config', function () { assert.strictEqual(result, null); }); - it('returns null if the variation does not have a value for this variable', function () { + it('returns null if the variation does not have a value for this variable', function() { var variation = configObj.variationIdMap['595008']; // This variation has no variable values associated with it var variable = configObj.featureKeyMap.test_feature_for_experiment.variableKeyMap.num_buttons; var result = projectConfig.getVariableValueForVariation( @@ -593,15 +598,15 @@ describe('lib/core/project_config', function () { }); }); - describe('getTypeCastValue', function () { - it('can cast a boolean', function () { + describe('getTypeCastValue', function() { + it('can cast a boolean', function() { var result = projectConfig.getTypeCastValue('true', FEATURE_VARIABLE_TYPES.BOOLEAN, featureManagementLogger); assert.strictEqual(result, true); result = projectConfig.getTypeCastValue('false', FEATURE_VARIABLE_TYPES.BOOLEAN, featureManagementLogger); assert.strictEqual(result, false); }); - it('can cast an integer', function () { + it('can cast an integer', function() { var result = projectConfig.getTypeCastValue('50', FEATURE_VARIABLE_TYPES.INTEGER, featureManagementLogger); assert.strictEqual(result, 50); var result = projectConfig.getTypeCastValue('-7', FEATURE_VARIABLE_TYPES.INTEGER, featureManagementLogger); @@ -610,7 +615,7 @@ describe('lib/core/project_config', function () { assert.strictEqual(result, 0); }); - it('can cast a double', function () { + it('can cast a double', function() { var result = projectConfig.getTypeCastValue('89.99', FEATURE_VARIABLE_TYPES.DOUBLE, featureManagementLogger); assert.strictEqual(result, 89.99); var result = projectConfig.getTypeCastValue( @@ -625,7 +630,7 @@ describe('lib/core/project_config', function () { assert.strictEqual(result, 10); }); - it('can return a string unmodified', function () { + it('can return a string unmodified', function() { var result = projectConfig.getTypeCastValue( 'message', FEATURE_VARIABLE_TYPES.STRING, @@ -634,73 +639,69 @@ describe('lib/core/project_config', function () { assert.strictEqual(result, 'message'); }); - it('returns null and logs an error for an invalid boolean', function () { + it('returns null and logs an error for an invalid boolean', function() { var result = projectConfig.getTypeCastValue( 'notabool', FEATURE_VARIABLE_TYPES.BOOLEAN, featureManagementLogger ); assert.strictEqual(result, null); - assert.strictEqual( - buildLogMessageFromArgs(featureManagementLogger.log.lastCall.args), - 'PROJECT_CONFIG: Unable to cast value notabool to type boolean, returning null.' - ); + + assert.deepEqual(featureManagementLogger.error.lastCall.args, [UNABLE_TO_CAST_VALUE, 'notabool', 'boolean']); }); - it('returns null and logs an error for an invalid integer', function () { + it('returns null and logs an error for an invalid integer', function() { var result = projectConfig.getTypeCastValue( 'notanint', FEATURE_VARIABLE_TYPES.INTEGER, featureManagementLogger ); assert.strictEqual(result, null); - assert.strictEqual( - buildLogMessageFromArgs(featureManagementLogger.log.lastCall.args), - 'PROJECT_CONFIG: Unable to cast value notanint to type integer, returning null.' - ); + + assert.deepEqual(featureManagementLogger.error.lastCall.args, [UNABLE_TO_CAST_VALUE, 'notanint', 'integer']); }); - it('returns null and logs an error for an invalid double', function () { + it('returns null and logs an error for an invalid double', function() { var result = projectConfig.getTypeCastValue( 'notadouble', FEATURE_VARIABLE_TYPES.DOUBLE, featureManagementLogger ); assert.strictEqual(result, null); - assert.strictEqual( - buildLogMessageFromArgs(featureManagementLogger.log.lastCall.args), - 'PROJECT_CONFIG: Unable to cast value notadouble to type double, returning null.' - ); + + assert.deepEqual(featureManagementLogger.error.lastCall.args, [UNABLE_TO_CAST_VALUE, 'notadouble', 'double']); }); }); }); - describe('#getAudiencesById', function () { - beforeEach(function () { + describe('#getAudiencesById', function() { + beforeEach(function() { configObj = projectConfig.createProjectConfig(testDatafile.getTypedAudiencesConfig()); }); - it('should retrieve audiences by checking first in typedAudiences, and then second in audiences', function () { + it('should retrieve audiences by checking first in typedAudiences, and then second in audiences', function() { assert.deepEqual(projectConfig.getAudiencesById(configObj), testDatafile.typedAudiencesById); }); }); - describe('#getExperimentAudienceConditions', function () { - it('should retrieve audiences for valid experiment key', function () { + describe('#getExperimentAudienceConditions', function() { + it('should retrieve audiences for valid experiment key', function() { configObj = projectConfig.createProjectConfig(cloneDeep(testData)); assert.deepEqual(projectConfig.getExperimentAudienceConditions(configObj, testData.experiments[1].id), [ '11154', ]); }); - it('should throw error for invalid experiment key', function () { + it('should throw error for invalid experiment key', function() { configObj = projectConfig.createProjectConfig(cloneDeep(testData)); - assert.throws(function () { + const ex = assert.throws(function() { projectConfig.getExperimentAudienceConditions(configObj, 'invalidExperimentId'); - }, sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_ID, 'PROJECT_CONFIG', 'invalidExperimentId')); + }); + assert.equal(ex.baseMessage, INVALID_EXPERIMENT_ID); + assert.deepEqual(ex.params, ['invalidExperimentId']); }); - it('should return experiment audienceIds if experiment has no audienceConditions', function () { + it('should return experiment audienceIds if experiment has no audienceConditions', function() { configObj = projectConfig.createProjectConfig(testDatafile.getTypedAudiencesConfig()); var result = projectConfig.getExperimentAudienceConditions(configObj, '11564051718'); assert.deepEqual(result, [ @@ -714,7 +715,7 @@ describe('lib/core/project_config', function () { ]); }); - it('should return experiment audienceConditions if experiment has audienceConditions', function () { + it('should return experiment audienceConditions if experiment has audienceConditions', function() { configObj = projectConfig.createProjectConfig(testDatafile.getTypedAudiencesConfig()); // audience_combinations_experiment has both audienceConditions and audienceIds // audienceConditions should be preferred over audienceIds @@ -727,20 +728,20 @@ describe('lib/core/project_config', function () { }); }); - describe('#isFeatureExperiment', function () { - it('returns true for a feature test', function () { + describe('#isFeatureExperiment', function() { + it('returns true for a feature test', function() { var config = projectConfig.createProjectConfig(testDatafile.getTestProjectConfigWithFeatures()); var result = projectConfig.isFeatureExperiment(config, '594098'); // id of 'testing_my_feature' assert.isTrue(result); }); - it('returns false for an A/B test', function () { + it('returns false for an A/B test', function() { var config = projectConfig.createProjectConfig(testDatafile.getTestProjectConfig()); var result = projectConfig.isFeatureExperiment(config, '111127'); // id of 'testExperiment' assert.isFalse(result); }); - it('returns true for a feature test in a mutex group', function () { + it('returns true for a feature test in a mutex group', function() { var config = projectConfig.createProjectConfig(testDatafile.getMutexFeatureTestsConfig()); var result = projectConfig.isFeatureExperiment(config, '17128410791'); // id of 'f_test1' assert.isTrue(result); @@ -749,62 +750,62 @@ describe('lib/core/project_config', function () { }); }); - describe('#getAudienceSegments', function () { - it('returns all qualified segments from an audience', function () { + describe('#getAudienceSegments', function() { + it('returns all qualified segments from an audience', function() { const dummyQualifiedAudienceJson = { - "id": "13389142234", - "conditions": [ - "and", + id: '13389142234', + conditions: [ + 'and', [ - "or", + 'or', [ - "or", + 'or', { - "value": "odp-segment-1", - "type": "third_party_dimension", - "name": "odp.audiences", - "match": "qualified" - } - ] - ] + value: 'odp-segment-1', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + ], ], - "name": "odp-segment-1" + name: 'odp-segment-1', }; const dummyQualifiedAudienceJsonSegments = projectConfig.getAudienceSegments(dummyQualifiedAudienceJson); assert.deepEqual(dummyQualifiedAudienceJsonSegments, ['odp-segment-1']); const dummyUnqualifiedAudienceJson = { - "id": "13389142234", - "conditions": [ - "and", + id: '13389142234', + conditions: [ + 'and', [ - "or", + 'or', [ - "or", + 'or', { - "value": "odp-segment-1", - "type": "third_party_dimension", - "name": "odp.audiences", - "match": "invalid" - } - ] - ] + value: 'odp-segment-1', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'invalid', + }, + ], + ], ], - "name": "odp-segment-1" + name: 'odp-segment-1', }; const dummyUnqualifiedAudienceJsonSegments = projectConfig.getAudienceSegments(dummyUnqualifiedAudienceJson); assert.deepEqual(dummyUnqualifiedAudienceJsonSegments, []); }); - it('returns false for an A/B test', function () { + it('returns false for an A/B test', function() { var config = projectConfig.createProjectConfig(testDatafile.getTestProjectConfig()); var result = projectConfig.isFeatureExperiment(config, '111127'); // id of 'testExperiment' assert.isFalse(result); }); - it('returns true for a feature test in a mutex group', function () { + it('returns true for a feature test in a mutex group', function() { var config = projectConfig.createProjectConfig(testDatafile.getMutexFeatureTestsConfig()); var result = projectConfig.isFeatureExperiment(config, '17128410791'); // id of 'f_test1' assert.isTrue(result); @@ -815,7 +816,6 @@ describe('lib/core/project_config', function () { }); describe('integrations', () => { - describe('#withSegments', () => { var config; beforeEach(() => { @@ -824,21 +824,16 @@ describe('lib/core/project_config', function () { it('should convert integrations from the datafile into the project config', () => { assert.exists(config.integrations); - assert.equal(config.integrations.length, 3); + assert.equal(config.integrations.length, 4); }); - it('should populate the public key value from the odp integration', () => { - assert.exists(config.publicKeyForOdp) - }) - - it('should populate the host value from the odp integration', () => { - assert.exists(config.hostForOdp) - }) - - it('should contain all expected unique odp segments in allSegments', () => { - assert.equal(config.allSegments.size, 3) - assert.deepEqual(config.allSegments, new Set(['odp-segment-1', 'odp-segment-2', 'odp-segment-3'])) - }) + it('should populate odpIntegrationConfig', () => { + assert.isTrue(config.odpIntegrationConfig.integrated); + assert.equal(config.odpIntegrationConfig.odpConfig.apiKey, 'W4WzcEs-ABgXorzY7h1LCQ'); + assert.equal(config.odpIntegrationConfig.odpConfig.apiHost, 'https://api.zaius.com/'); + assert.equal(config.odpIntegrationConfig.odpConfig.pixelUrl, 'https://jumbe.zaius.com/'); + assert.deepEqual(config.odpIntegrationConfig.odpConfig.segmentsToCheck, ['odp-segment-1', 'odp-segment-2', 'odp-segment-3']); + }); }); describe('#withoutSegments', () => { @@ -852,70 +847,72 @@ describe('lib/core/project_config', function () { assert.equal(config.integrations.length, 3); }); - it('should populate the public key value from the odp integration', () => { - assert.exists(config.publicKeyForOdp) - assert.equal(config.publicKeyForOdp, 'W4WzcEs-ABgXorzY7h1LCQ') - }) - - it('should populate the host value from the odp integration', () => { - assert.exists(config.hostForOdp) - assert.equal(config.hostForOdp, 'https://api.zaius.com/') - }) + it('should populate odpIntegrationConfig', () => { + assert.isTrue(config.odpIntegrationConfig.integrated); + assert.equal(config.odpIntegrationConfig.odpConfig.apiKey, 'W4WzcEs-ABgXorzY7h1LCQ'); + assert.equal(config.odpIntegrationConfig.odpConfig.apiHost, 'https://api.zaius.com/'); + assert.equal(config.odpIntegrationConfig.odpConfig.pixelUrl, 'https://jumbe.zaius.com/'); + assert.deepEqual(config.odpIntegrationConfig.odpConfig.segmentsToCheck, []); + }); + }); - it('should contain all expected unique odp segments in all segments', () => { - assert.equal(config.allSegments.size, 0) - }) + describe('#withoutValidIntegrationKey', () => { + it('should throw an error when parsing the project config due to integrations not containing a key', () => { + const odpIntegratedConfigWithoutKey = testDatafile.getOdpIntegratedConfigWithoutKey(); + assert.throws(() => { + projectConfig.createProjectConfig(odpIntegratedConfigWithoutKey); + }); + }); }); describe('#withoutIntegrations', () => { var config; beforeEach(() => { - const odpIntegratedConfigWithSegments = testDatafile.getOdpIntegratedConfigWithSegments() - const noIntegrationsConfigWithSegments = { ...odpIntegratedConfigWithSegments, integrations: [] } + const odpIntegratedConfigWithSegments = testDatafile.getOdpIntegratedConfigWithSegments(); + const noIntegrationsConfigWithSegments = { ...odpIntegratedConfigWithSegments, integrations: [] }; config = projectConfig.createProjectConfig(noIntegrationsConfigWithSegments); }); it('should convert integrations from the datafile into the project config', () => { assert.equal(config.integrations.length, 0); }); - }); - }) + it('should populate odpIntegrationConfig', () => { + assert.isFalse(config.odpIntegrationConfig.integrated); + assert.isUndefined(config.odpIntegrationConfig.odpConfig); + }); + }); + }); }); -describe('#tryCreatingProjectConfig', function () { +describe('#tryCreatingProjectConfig', function() { var stubJsonSchemaValidator; - beforeEach(function () { - stubJsonSchemaValidator = { - validate: sinon.stub().returns(true), - }; + beforeEach(function() { + stubJsonSchemaValidator = sinon.stub().returns(true); sinon.stub(configValidator, 'validateDatafile').returns(true); sinon.spy(logger, 'error'); }); - afterEach(function () { + afterEach(function() { configValidator.validateDatafile.restore(); logger.error.restore(); }); - it('returns a project config object created by createProjectConfig when all validation is applied and there are no errors', function () { + it('returns a project config object created by createProjectConfig when all validation is applied and there are no errors', function() { var configDatafile = { foo: 'bar', - experiments: [ - { key: 'a' }, - { key: 'b' } - ] - } + experiments: [{ key: 'a' }, { key: 'b' }], + }; configValidator.validateDatafile.returns(configDatafile); var configObj = { foo: 'bar', experimentKeyMap: { - "a": { key: "a", variationKeyMap: {} }, - "b": { key: "b", variationKeyMap: {} } + a: { key: 'a', variationKeyMap: {} }, + b: { key: 'b', variationKeyMap: {} }, }, }; - stubJsonSchemaValidator.validate.returns(true); + stubJsonSchemaValidator.returns(true); var result = projectConfig.tryCreatingProjectConfig({ datafile: configDatafile, @@ -923,40 +920,38 @@ describe('#tryCreatingProjectConfig', function () { logger: logger, }); - assert.deepInclude(result.configObj, configObj) + assert.deepInclude(result, configObj); }); - it('returns an error when validateDatafile throws', function () { + it('throws an error when validateDatafile throws', function() { configValidator.validateDatafile.throws(); - stubJsonSchemaValidator.validate.returns(true); - var { error } = projectConfig.tryCreatingProjectConfig({ - datafile: { foo: 'bar' }, - jsonSchemaValidator: stubJsonSchemaValidator, - logger: logger, + stubJsonSchemaValidator.returns(true); + assert.throws(() => { + projectConfig.tryCreatingProjectConfig({ + datafile: { foo: 'bar' }, + jsonSchemaValidator: stubJsonSchemaValidator, + logger: logger, + }); }); - assert.isNotNull(error); }); - it('returns an error when jsonSchemaValidator.validate throws', function () { + it('throws an error when jsonSchemaValidator.validate throws', function() { configValidator.validateDatafile.returns(true); - stubJsonSchemaValidator.validate.throws(); - var { error } = projectConfig.tryCreatingProjectConfig({ - datafile: { foo: 'bar' }, - jsonSchemaValidator: stubJsonSchemaValidator, - logger: logger, + stubJsonSchemaValidator.throws(); + assert.throws(() => { + projectConfig.tryCreatingProjectConfig({ + datafile: { foo: 'bar' }, + jsonSchemaValidator: stubJsonSchemaValidator, + logger: logger, + }); }); - assert.isNotNull(error); }); - it('skips json validation when jsonSchemaValidator is not provided', function () { - + it('skips json validation when jsonSchemaValidator is not provided', function() { var configDatafile = { foo: 'bar', - experiments: [ - { key: 'a' }, - { key: 'b' } - ] - } + experiments: [{ key: 'a' }, { key: 'b' }], + }; configValidator.validateDatafile.returns(configDatafile); @@ -973,7 +968,7 @@ describe('#tryCreatingProjectConfig', function () { logger: logger, }); - assert.deepInclude(result.configObj, configObj); + assert.deepInclude(result, configObj); sinon.assert.notCalled(logger.error); }); }); diff --git a/packages/optimizely-sdk/lib/core/project_config/index.ts b/lib/project_config/project_config.ts similarity index 63% rename from packages/optimizely-sdk/lib/core/project_config/index.ts rename to lib/project_config/project_config.ts index aa89cc566..9a611ff1a 100644 --- a/packages/optimizely-sdk/lib/core/project_config/index.ts +++ b/lib/project_config/project_config.ts @@ -1,11 +1,11 @@ /** - * Copyright 2016-2022, Optimizely + * Copyright 2016-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,24 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { - find, - objectEntries, - objectValues, - sprintf, - assign, - keyBy -} from '../../utils/fns'; +import { find, objectEntries, objectValues, keyBy, assignBy } from '../utils/fns'; + +import { FEATURE_VARIABLE_TYPES } from '../utils/enums'; +import configValidator from '../utils/config_validator'; + +import { LoggerFacade } from '../logging/logger'; -import { - ERROR_MESSAGES, - LOG_LEVEL, - LOG_MESSAGES, - FEATURE_VARIABLE_TYPES, -} from '../../utils/enums'; -import configValidator from '../../utils/config_validator'; - -import { LogHandler } from '../../modules/logging'; import { Audience, Experiment, @@ -44,29 +33,49 @@ import { VariableType, VariationVariable, Integration, -} from '../../shared_types'; + FeatureVariableValue, + Holdout, +} from '../shared_types'; +import { OdpConfig, OdpIntegrationConfig } from '../odp/odp_config'; +import { Transformer } from '../utils/type'; +import { + EXPERIMENT_KEY_NOT_IN_DATAFILE, + FEATURE_NOT_IN_DATAFILE, + INVALID_EXPERIMENT_ID, + INVALID_EXPERIMENT_KEY, + MISSING_INTEGRATION_KEY, + UNABLE_TO_CAST_VALUE, + UNEXPECTED_RESERVED_ATTRIBUTE_PREFIX, + UNRECOGNIZED_ATTRIBUTE, + VARIABLE_KEY_NOT_IN_DATAFILE, + VARIATION_ID_NOT_IN_DATAFILE, +} from 'error_message'; +import { SKIPPING_JSON_VALIDATION, VALID_DATAFILE } from 'log_message'; +import { OptimizelyError } from '../error/optimizly_error'; +import * as featureToggle from '../feature_toggle'; interface TryCreatingProjectConfigConfig { // TODO[OASIS-6649]: Don't use object type // eslint-disable-next-line @typescript-eslint/ban-types datafile: string | object; - jsonSchemaValidator?: { - validate(jsonObject: unknown): boolean, - }; - logger: LogHandler; + jsonSchemaValidator?: Transformer<unknown, boolean>; + logger?: LoggerFacade; } interface Event { key: string; id: string; - experimentsIds: string[]; + experimentIds: string[]; } interface VariableUsageMap { [id: string]: VariationVariable; } +export type Region = 'US' | 'EU'; + export interface ProjectConfig { + region: Region; revision: string; projectId: string; sdkKey: string; @@ -84,6 +93,7 @@ export interface ProjectConfig { eventKeyMap: { [key: string]: Event }; audiences: Audience[]; attributeKeyMap: { [key: string]: { id: string } }; + attributeIdMap: { [id: string]: { key: string } }; variationIdMap: { [id: string]: OptimizelyVariation }; variationVariableUsageMap: { [id: string]: VariableUsageMap }; audiencesById: { [id: string]: Audience }; @@ -101,38 +111,42 @@ export interface ProjectConfig { flagVariationsMap: { [key: string]: Variation[] }; integrations: Integration[]; integrationKeyMap?: { [key: string]: Integration }; - publicKeyForOdp?: string; - hostForOdp?: string; - allSegments: Set<string>; + odpIntegrationConfig: OdpIntegrationConfig; + holdouts: Holdout[]; + holdoutIdMap?: { [id: string]: Holdout }; + globalHoldouts: Holdout[]; + includedHoldouts: { [key: string]: Holdout[]; } + excludedHoldouts: { [key: string]: Holdout[]; } + flagHoldoutsMap: { [key: string]: Holdout[]; } } const EXPERIMENT_RUNNING_STATUS = 'Running'; const RESERVED_ATTRIBUTE_PREFIX = '$opt_'; -const MODULE_NAME = 'PROJECT_CONFIG'; // eslint-disable-next-line @typescript-eslint/no-explicit-any function createMutationSafeDatafileCopy(datafile: any): ProjectConfig { - const datafileCopy = assign({}, datafile); + const datafileCopy = { ...datafile }; + datafileCopy.audiences = (datafile.audiences || []).map((audience: Audience) => { - return assign({}, audience); + return { ...audience }; }); datafileCopy.experiments = (datafile.experiments || []).map((experiment: Experiment) => { - return assign({}, experiment); + return { ...experiment }; }); datafileCopy.featureFlags = (datafile.featureFlags || []).map((featureFlag: FeatureFlag) => { - return assign({}, featureFlag); + return { ...featureFlag }; }); datafileCopy.groups = (datafile.groups || []).map((group: Group) => { - const groupCopy = assign({}, group); - groupCopy.experiments = (group.experiments || []).map((experiment) => { - return assign({}, experiment); + const groupCopy = { ...group }; + groupCopy.experiments = (group.experiments || []).map(experiment => { + return { ...experiment }; }); return groupCopy; }); datafileCopy.rollouts = (datafile.rollouts || []).map((rollout: Rollout) => { - const rolloutCopy = assign({}, rollout); - rolloutCopy.experiments = (rollout.experiments || []).map((experiment) => { - return assign({}, experiment); + const rolloutCopy = { ...rollout }; + rolloutCopy.experiments = (rollout.experiments || []).map(experiment => { + return { ...experiment }; }); return rolloutCopy; }); @@ -149,65 +163,99 @@ function createMutationSafeDatafileCopy(datafile: any): ProjectConfig { * @param {string|null} datafileStr JSON string representation of the datafile * @return {ProjectConfig} Object representing project configuration */ -export const createProjectConfig = function ( - datafileObj?: JSON, - datafileStr: string | null = null -): ProjectConfig { +export const createProjectConfig = function(datafileObj?: JSON, datafileStr: string | null = null): ProjectConfig { const projectConfig = createMutationSafeDatafileCopy(datafileObj); + if (!projectConfig.region) { + projectConfig.region = 'US'; // Default to US region if not specified + } + projectConfig.__datafileStr = datafileStr === null ? JSON.stringify(datafileObj) : datafileStr; /* * Conditions of audiences in projectConfig.typedAudiences are not * expected to be string-encoded as they are here in projectConfig.audiences. */ - (projectConfig.audiences || []).forEach((audience) => { + (projectConfig.audiences || []).forEach(audience => { audience.conditions = JSON.parse(audience.conditions as string); }); - projectConfig.audiencesById = keyBy(projectConfig.audiences, 'id'); - assign(projectConfig.audiencesById, keyBy(projectConfig.typedAudiences, 'id')); - projectConfig.allSegments = new Set<string>([]) - Object.keys(projectConfig.audiencesById) - .map((audience) => getAudienceSegments(projectConfig.audiencesById[audience])) - .forEach(audienceSegments => { - audienceSegments.forEach(segment => { - projectConfig.allSegments.add(segment) - }) - }) + projectConfig.audiencesById = {}; + assignBy(projectConfig.audiences, 'id', projectConfig.audiencesById); + assignBy(projectConfig.typedAudiences, 'id', projectConfig.audiencesById); + + projectConfig.attributes = projectConfig.attributes || []; + projectConfig.attributeKeyMap = {}; + projectConfig.attributeIdMap = {}; + projectConfig.attributes.forEach(attribute => { + projectConfig.attributeKeyMap[attribute.key] = attribute; + projectConfig.attributeIdMap[attribute.id] = attribute; + }); - projectConfig.attributeKeyMap = keyBy(projectConfig.attributes, 'key'); projectConfig.eventKeyMap = keyBy(projectConfig.events, 'key'); projectConfig.groupIdMap = keyBy(projectConfig.groups, 'id'); let experiments; - Object.keys(projectConfig.groupIdMap || {}).forEach((Id) => { + Object.keys(projectConfig.groupIdMap || {}).forEach(Id => { experiments = projectConfig.groupIdMap[Id].experiments; - (experiments || []).forEach((experiment) => { - projectConfig.experiments.push(assign(experiment, { groupId: Id })); + (experiments || []).forEach(experiment => { + experiment.groupId = Id; + projectConfig.experiments.push(experiment); }); }); projectConfig.rolloutIdMap = keyBy(projectConfig.rollouts || [], 'id'); - objectValues(projectConfig.rolloutIdMap || {}).forEach( - (rollout) => { - (rollout.experiments || []).forEach((experiment) => { - projectConfig.experiments.push(experiment); - // Creates { <variationKey>: <variation> } map inside of the experiment - experiment.variationKeyMap = keyBy(experiment.variations, 'key'); + objectValues(projectConfig.rolloutIdMap || {}).forEach(rollout => { + (rollout.experiments || []).forEach(experiment => { + experiment.isRollout = true + projectConfig.experiments.push(experiment); + // Creates { <variationKey>: <variation> } map inside of the experiment + experiment.variationKeyMap = keyBy(experiment.variations, 'key'); + }); + }); + + const allSegmentsSet = new Set<string>(); + + Object.keys(projectConfig.audiencesById) + .map(audience => getAudienceSegments(projectConfig.audiencesById[audience])) + .forEach(audienceSegments => { + audienceSegments.forEach(segment => { + allSegmentsSet.add(segment); }); - } - ); + }); + + const allSegments = Array.from(allSegmentsSet); + + let odpIntegrated = false; + let odpApiHost = ''; + let odpApiKey = ''; + let odpPixelUrl = ''; if (projectConfig.integrations) { projectConfig.integrationKeyMap = keyBy(projectConfig.integrations, 'key'); - projectConfig.integrations - .filter((integration) => integration.key === 'odp') - .forEach((integration) => { - if (integration.publicKey) projectConfig.publicKeyForOdp = integration.publicKey - if (integration.host) projectConfig.hostForOdp = integration.host - }) + + projectConfig.integrations.forEach(integration => { + if (!('key' in integration)) { + throw new OptimizelyError(MISSING_INTEGRATION_KEY); + } + + if (integration.key === 'odp') { + odpIntegrated = true; + odpApiKey = odpApiKey || integration.publicKey || ''; + odpApiHost = odpApiHost || integration.host || ''; + odpPixelUrl = odpPixelUrl || integration.pixelUrl || ''; + } + }); + } + + if (odpIntegrated) { + projectConfig.odpIntegrationConfig = { + integrated: true, + odpConfig: new OdpConfig(odpApiKey, odpApiHost, odpPixelUrl, allSegments), + } + } else { + projectConfig.odpIntegrationConfig = { integrated: false }; } projectConfig.experimentKeyMap = keyBy(projectConfig.experiments, 'key'); @@ -215,13 +263,13 @@ export const createProjectConfig = function ( projectConfig.variationIdMap = {}; projectConfig.variationVariableUsageMap = {}; - (projectConfig.experiments || []).forEach((experiment) => { + (projectConfig.experiments || []).forEach(experiment => { // Creates { <variationKey>: <variation> } map inside of the experiment experiment.variationKeyMap = keyBy(experiment.variations, 'key'); - // Creates { <variationId>: { key: <variationKey>, id: <variationId> } } mapping for quick lookup - assign(projectConfig.variationIdMap, keyBy(experiment.variations, 'id')); - objectValues(experiment.variationKeyMap || {}).forEach((variation) => { + assignBy(experiment.variations, 'id', projectConfig.variationIdMap); + + objectValues(experiment.variationKeyMap || {}).forEach(variation => { if (variation.variables) { projectConfig.variationVariableUsageMap[variation.id] = keyBy(variation.variables, 'id'); } @@ -233,28 +281,26 @@ export const createProjectConfig = function ( projectConfig.experimentFeatureMap = {}; projectConfig.featureKeyMap = keyBy(projectConfig.featureFlags || [], 'key'); - objectValues(projectConfig.featureKeyMap || {}).forEach( - (feature) => { - // Json type is represented in datafile as a subtype of string for the sake of backwards compatibility. - // Converting it to a first-class json type while creating Project Config - feature.variables.forEach((variable) => { - if (variable.type === FEATURE_VARIABLE_TYPES.STRING && variable.subType === FEATURE_VARIABLE_TYPES.JSON) { - variable.type = FEATURE_VARIABLE_TYPES.JSON as VariableType; - delete variable.subType; - } - }); + objectValues(projectConfig.featureKeyMap || {}).forEach(feature => { + // Json type is represented in datafile as a subtype of string for the sake of backwards compatibility. + // Converting it to a first-class json type while creating Project Config + feature.variables.forEach(variable => { + if (variable.type === FEATURE_VARIABLE_TYPES.STRING && variable.subType === FEATURE_VARIABLE_TYPES.JSON) { + variable.type = FEATURE_VARIABLE_TYPES.JSON as VariableType; + delete variable.subType; + } + }); - feature.variableKeyMap = keyBy(feature.variables, 'key'); - (feature.experimentIds || []).forEach((experimentId) => { - // Add this experiment in experiment-feature map. - if (projectConfig.experimentFeatureMap[experimentId]) { - projectConfig.experimentFeatureMap[experimentId].push(feature.id); - } else { - projectConfig.experimentFeatureMap[experimentId] = [feature.id]; - } - }); - } - ); + feature.variableKeyMap = keyBy(feature.variables, 'key'); + (feature.experimentIds || []).forEach(experimentId => { + // Add this experiment in experiment-feature map. + if (projectConfig.experimentFeatureMap[experimentId]) { + projectConfig.experimentFeatureMap[experimentId].push(feature.id); + } else { + projectConfig.experimentFeatureMap[experimentId] = [feature.id]; + } + }); + }); // all rules (experiment rules and delivery rules) for each flag projectConfig.flagRulesMap = {}; @@ -281,30 +327,101 @@ export const createProjectConfig = function ( // - we collect variations used in each rule (experiment rules and delivery rules) projectConfig.flagVariationsMap = {}; - objectEntries(projectConfig.flagRulesMap || {}).forEach( - ([flagKey, rules]) => { - const variations: OptimizelyVariation[] = []; - rules.forEach(rule => { - rule.variations.forEach(variation => { - if (!find(variations, item => item.id === variation.id)) { - variations.push(variation); - } - }); + objectEntries(projectConfig.flagRulesMap || {}).forEach(([flagKey, rules]) => { + const variations: OptimizelyVariation[] = []; + rules.forEach(rule => { + rule.variations.forEach(variation => { + if (!find(variations, item => item.id === variation.id)) { + variations.push(variation); + } }); - projectConfig.flagVariationsMap[flagKey] = variations; - } - ); + }); + projectConfig.flagVariationsMap[flagKey] = variations; + }); + + parseHoldoutsConfig(projectConfig); return projectConfig; }; +const parseHoldoutsConfig = (projectConfig: ProjectConfig): void => { + if (!featureToggle.holdout()) { + return; + } + + projectConfig.holdouts = projectConfig.holdouts || []; + projectConfig.holdoutIdMap = keyBy(projectConfig.holdouts, 'id'); + projectConfig.globalHoldouts = []; + projectConfig.includedHoldouts = {}; + projectConfig.excludedHoldouts = {}; + projectConfig.flagHoldoutsMap = {}; + + const featureFlagIdMap = keyBy(projectConfig.featureFlags, 'id'); + + projectConfig.holdouts.forEach((holdout) => { + if (!holdout.includedFlags) { + holdout.includedFlags = []; + } + + if (!holdout.excludedFlags) { + holdout.excludedFlags = []; + } + + holdout.variationKeyMap = keyBy(holdout.variations, 'key'); + + assignBy(holdout.variations, 'id', projectConfig.variationIdMap); + + if (holdout.includedFlags.length === 0) { + projectConfig.globalHoldouts.push(holdout); + + holdout.excludedFlags.forEach((flagId: string) => { + const flag = featureFlagIdMap[flagId]; + if (flag) { + const flagKey = flag.key; + if (!projectConfig.excludedHoldouts[flagKey]) { + projectConfig.excludedHoldouts[flagKey] = []; + } + projectConfig.excludedHoldouts[flagKey].push(holdout); + } + }); + } else { + holdout.includedFlags.forEach((flagId: string) => { + const flag = featureFlagIdMap[flagId]; + if (flag) { + const flagKey = flag.key; + if (!projectConfig.includedHoldouts[flagKey]) { + projectConfig.includedHoldouts[flagKey] = []; + } + projectConfig.includedHoldouts[flagKey].push(holdout); + } + }) + } + }); +} + +export const getHoldoutsForFlag = (projectConfig: ProjectConfig, flagKey: string): Holdout[] => { + if (projectConfig.flagHoldoutsMap[flagKey]) { + return projectConfig.flagHoldoutsMap[flagKey]; + } + + const flagHoldouts: Holdout[] = [ + ...projectConfig.globalHoldouts.filter((holdout) => { + return !(projectConfig.excludedHoldouts[flagKey] || []).includes(holdout); + }), + ...(projectConfig.includedHoldouts[flagKey] || []), + ]; + + projectConfig.flagHoldoutsMap[flagKey] = flagHoldouts; + return flagHoldouts; +} + /** * Extract all audience segments used in this audience's conditions * @param {Audience} audience Object representing the audience being parsed * @return {string[]} List of all audience segments */ -export const getAudienceSegments = function (audience: Audience): string[] { - if (!audience.conditions) return [] +export const getAudienceSegments = function(audience: Audience): string[] { + if (!audience.conditions) return []; return getSegmentsFromConditions(audience.conditions); }; @@ -313,20 +430,18 @@ const getSegmentsFromConditions = (condition: any): string[] => { const segments = []; if (isLogicalOperator(condition)) { - return [] - } - else if (Array.isArray(condition)) { - condition.forEach((nextCondition) => segments.push(...getSegmentsFromConditions(nextCondition))) - } - else if (condition['match'] === 'qualified') { - segments.push(condition['value']) + return []; + } else if (Array.isArray(condition)) { + condition.forEach(nextCondition => segments.push(...getSegmentsFromConditions(nextCondition))); + } else if (condition['match'] === 'qualified') { + segments.push(condition['value']); } return segments; -} +}; function isLogicalOperator(condition: string): boolean { - return ['and', 'or', 'not'].includes(condition) + return ['and', 'or', 'not'].includes(condition); } /** @@ -336,10 +451,10 @@ function isLogicalOperator(condition: string): boolean { * @return {string} Experiment ID corresponding to the provided experiment key * @throws If experiment key is not in datafile */ -export const getExperimentId = function (projectConfig: ProjectConfig, experimentKey: string): string { +export const getExperimentId = function(projectConfig: ProjectConfig, experimentKey: string): string { const experiment = projectConfig.experimentKeyMap[experimentKey]; if (!experiment) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, MODULE_NAME, experimentKey)); + throw new OptimizelyError(INVALID_EXPERIMENT_KEY, experimentKey); } return experiment.id; }; @@ -351,10 +466,10 @@ export const getExperimentId = function (projectConfig: ProjectConfig, experimen * @return {string} Layer ID corresponding to the provided experiment key * @throws If experiment key is not in datafile */ -export const getLayerId = function (projectConfig: ProjectConfig, experimentId: string): string { +export const getLayerId = function(projectConfig: ProjectConfig, experimentId: string): string { const experiment = projectConfig.experimentIdMap[experimentId]; if (!experiment) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_ID, MODULE_NAME, experimentId)); + throw new OptimizelyError(INVALID_EXPERIMENT_ID, experimentId); } return experiment.layerId; }; @@ -366,20 +481,19 @@ export const getLayerId = function (projectConfig: ProjectConfig, experimentId: * @param {LogHandler} logger * @return {string|null} Attribute ID corresponding to the provided attribute key. Attribute key if it is a reserved attribute. */ -export const getAttributeId = function ( +export const getAttributeId = function( projectConfig: ProjectConfig, attributeKey: string, - logger: LogHandler + logger?: LoggerFacade ): string | null { const attribute = projectConfig.attributeKeyMap[attributeKey]; const hasReservedPrefix = attributeKey.indexOf(RESERVED_ATTRIBUTE_PREFIX) === 0; if (attribute) { if (hasReservedPrefix) { - logger.log( - LOG_LEVEL.WARNING, - 'Attribute %s unexpectedly has reserved prefix %s; using attribute ID instead of reserved attribute name.', + logger?.warn( + UNEXPECTED_RESERVED_ATTRIBUTE_PREFIX, attributeKey, - RESERVED_ATTRIBUTE_PREFIX, + RESERVED_ATTRIBUTE_PREFIX ); } return attribute.id; @@ -387,7 +501,7 @@ export const getAttributeId = function ( return attributeKey; } - logger.log(LOG_LEVEL.DEBUG, ERROR_MESSAGES.UNRECOGNIZED_ATTRIBUTE, MODULE_NAME, attributeKey); + logger?.warn(UNRECOGNIZED_ATTRIBUTE, attributeKey); return null; }; @@ -397,7 +511,7 @@ export const getAttributeId = function ( * @param {string} eventKey Event key for which ID is to be determined * @return {string|null} Event ID corresponding to the provided event key */ -export const getEventId = function (projectConfig: ProjectConfig, eventKey: string): string | null { +export const getEventId = function(projectConfig: ProjectConfig, eventKey: string): string | null { const event = projectConfig.eventKeyMap[eventKey]; if (event) { return event.id; @@ -412,10 +526,10 @@ export const getEventId = function (projectConfig: ProjectConfig, eventKey: stri * @return {string} Experiment status corresponding to the provided experiment key * @throws If experiment key is not in datafile */ -export const getExperimentStatus = function (projectConfig: ProjectConfig, experimentKey: string): string { +export const getExperimentStatus = function(projectConfig: ProjectConfig, experimentKey: string): string { const experiment = projectConfig.experimentKeyMap[experimentKey]; if (!experiment) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_KEY, MODULE_NAME, experimentKey)); + throw new OptimizelyError(INVALID_EXPERIMENT_KEY, experimentKey); } return experiment.status; }; @@ -426,7 +540,7 @@ export const getExperimentStatus = function (projectConfig: ProjectConfig, exper * @param {string} experimentKey Experiment key for which status is to be compared with 'Running' * @return {boolean} True if experiment status is set to 'Running', false otherwise */ -export const isActive = function (projectConfig: ProjectConfig, experimentKey: string): boolean { +export const isActive = function(projectConfig: ProjectConfig, experimentKey: string): boolean { return getExperimentStatus(projectConfig, experimentKey) === EXPERIMENT_RUNNING_STATUS; }; @@ -438,7 +552,7 @@ export const isActive = function (projectConfig: ProjectConfig, experimentKey: s * False if the experiment is not running * */ -export const isRunning = function (projectConfig: ProjectConfig, experimentKey: string): boolean { +export const isRunning = function(projectConfig: ProjectConfig, experimentKey: string): boolean { return getExperimentStatus(projectConfig, experimentKey) === EXPERIMENT_RUNNING_STATUS; }; @@ -451,13 +565,13 @@ export const isRunning = function (projectConfig: ProjectConfig, experimentKey: * Examples: ["5", "6"], ["and", ["or", "1", "2"], "3"] * @throws If experiment key is not in datafile */ -export const getExperimentAudienceConditions = function ( +export const getExperimentAudienceConditions = function( projectConfig: ProjectConfig, experimentId: string ): Array<string | string[]> { const experiment = projectConfig.experimentIdMap[experimentId]; if (!experiment) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_ID, MODULE_NAME, experimentId)); + throw new OptimizelyError(INVALID_EXPERIMENT_ID, experimentId); } return experiment.audienceConditions || experiment.audienceIds; @@ -469,7 +583,7 @@ export const getExperimentAudienceConditions = function ( * @param {string} variationId ID of the variation * @return {string|null} Variation key or null if the variation ID is not found */ -export const getVariationKeyFromId = function (projectConfig: ProjectConfig, variationId: string): string | null { +export const getVariationKeyFromId = function(projectConfig: ProjectConfig, variationId: string): string | null { if (projectConfig.variationIdMap.hasOwnProperty(variationId)) { return projectConfig.variationIdMap[variationId].key; } @@ -483,7 +597,7 @@ export const getVariationKeyFromId = function (projectConfig: ProjectConfig, var * @param {string} variationId ID of the variation * @return {Variation|null} Variation or null if the variation ID is not found */ -export const getVariationFromId = function (projectConfig: ProjectConfig, variationId: string): Variation | null { +export const getVariationFromId = function(projectConfig: ProjectConfig, variationId: string): Variation | null { if (projectConfig.variationIdMap.hasOwnProperty(variationId)) { return projectConfig.variationIdMap[variationId]; } @@ -498,7 +612,7 @@ export const getVariationFromId = function (projectConfig: ProjectConfig, variat * @param {string} variationKey The variation key * @return {string|null} Variation ID or null */ -export const getVariationIdFromExperimentAndVariationKey = function ( +export const getVariationIdFromExperimentAndVariationKey = function( projectConfig: ProjectConfig, experimentKey: string, variationKey: string @@ -518,7 +632,7 @@ export const getVariationIdFromExperimentAndVariationKey = function ( * @return {Experiment} Experiment * @throws If experiment key is not in datafile */ -export const getExperimentFromKey = function (projectConfig: ProjectConfig, experimentKey: string): Experiment { +export const getExperimentFromKey = function(projectConfig: ProjectConfig, experimentKey: string): Experiment { if (projectConfig.experimentKeyMap.hasOwnProperty(experimentKey)) { const experiment = projectConfig.experimentKeyMap[experimentKey]; if (experiment) { @@ -526,9 +640,10 @@ export const getExperimentFromKey = function (projectConfig: ProjectConfig, expe } } - throw new Error(sprintf(ERROR_MESSAGES.EXPERIMENT_KEY_NOT_IN_DATAFILE, MODULE_NAME, experimentKey)); + throw new OptimizelyError(EXPERIMENT_KEY_NOT_IN_DATAFILE, experimentKey); }; + /** * Given an experiment id, returns the traffic allocation within that experiment * @param {ProjectConfig} projectConfig Object representing project configuration @@ -536,10 +651,10 @@ export const getExperimentFromKey = function (projectConfig: ProjectConfig, expe * @return {TrafficAllocation[]} Traffic allocation for the experiment * @throws If experiment key is not in datafile */ -export const getTrafficAllocation = function (projectConfig: ProjectConfig, experimentId: string): TrafficAllocation[] { +export const getTrafficAllocation = function(projectConfig: ProjectConfig, experimentId: string): TrafficAllocation[] { const experiment = projectConfig.experimentIdMap[experimentId]; if (!experiment) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_EXPERIMENT_ID, MODULE_NAME, experimentId)); + throw new OptimizelyError(INVALID_EXPERIMENT_ID, experimentId); } return experiment.trafficAllocation; }; @@ -552,10 +667,10 @@ export const getTrafficAllocation = function (projectConfig: ProjectConfig, expe * @param {LogHandler} logger * @return {Experiment|null} Experiment object or null */ -export const getExperimentFromId = function ( +export const getExperimentFromId = function( projectConfig: ProjectConfig, experimentId: string, - logger: LogHandler + logger?: LoggerFacade ): Experiment | null { if (projectConfig.experimentIdMap.hasOwnProperty(experimentId)) { const experiment = projectConfig.experimentIdMap[experimentId]; @@ -564,23 +679,27 @@ export const getExperimentFromId = function ( } } - logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.INVALID_EXPERIMENT_ID, MODULE_NAME, experimentId); + logger?.error(INVALID_EXPERIMENT_ID, experimentId); return null; }; /** -* Returns flag variation for specified flagKey and variationKey -* @param {flagKey} string -* @param {variationKey} string -* @return {Variation|null} -*/ -export const getFlagVariationByKey = function (projectConfig: ProjectConfig, flagKey: string, variationKey: string): Variation | null { + * Returns flag variation for specified flagKey and variationKey + * @param {flagKey} string + * @param {variationKey} string + * @return {Variation|null} + */ +export const getFlagVariationByKey = function( + projectConfig: ProjectConfig, + flagKey: string, + variationKey: string +): Variation | null { if (!projectConfig) { return null; } const variations = projectConfig.flagVariationsMap[flagKey]; - const result = find(variations, item => item.key === variationKey) + const result = find(variations, item => item.key === variationKey); if (result) { return result; } @@ -597,10 +716,10 @@ export const getFlagVariationByKey = function (projectConfig: ProjectConfig, fla * @return {FeatureFlag|null} Feature object, or null if no feature with the given * key exists */ -export const getFeatureFromKey = function ( +export const getFeatureFromKey = function( projectConfig: ProjectConfig, featureKey: string, - logger: LogHandler + logger?: LoggerFacade ): FeatureFlag | null { if (projectConfig.featureKeyMap.hasOwnProperty(featureKey)) { const feature = projectConfig.featureKeyMap[featureKey]; @@ -609,7 +728,7 @@ export const getFeatureFromKey = function ( } } - logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.FEATURE_NOT_IN_DATAFILE, MODULE_NAME, featureKey); + logger?.error(FEATURE_NOT_IN_DATAFILE, featureKey); return null; }; @@ -624,27 +743,21 @@ export const getFeatureFromKey = function ( * @return {FeatureVariable|null} Variable object, or null one or both of the given * feature and variable keys are invalid */ -export const getVariableForFeature = function ( +export const getVariableForFeature = function( projectConfig: ProjectConfig, featureKey: string, variableKey: string, - logger: LogHandler + logger?: LoggerFacade ): FeatureVariable | null { const feature = projectConfig.featureKeyMap[featureKey]; if (!feature) { - logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.FEATURE_NOT_IN_DATAFILE, MODULE_NAME, featureKey); + logger?.error(FEATURE_NOT_IN_DATAFILE, featureKey); return null; } const variable = feature.variableKeyMap[variableKey]; if (!variable) { - logger.log( - LOG_LEVEL.ERROR, - ERROR_MESSAGES.VARIABLE_KEY_NOT_IN_DATAFILE, - MODULE_NAME, - variableKey, - featureKey, - ); + logger?.error(VARIABLE_KEY_NOT_IN_DATAFILE, variableKey, featureKey); return null; } @@ -663,23 +776,18 @@ export const getVariableForFeature = function ( * variation, or null if the given variable has no value * for the given variation or if the variation or variable are invalid */ -export const getVariableValueForVariation = function ( +export const getVariableValueForVariation = function( projectConfig: ProjectConfig, variable: FeatureVariable, variation: Variation, - logger: LogHandler + logger?: LoggerFacade ): string | null { if (!variable || !variation) { return null; } if (!projectConfig.variationVariableUsageMap.hasOwnProperty(variation.id)) { - logger.log( - LOG_LEVEL.ERROR, - ERROR_MESSAGES.VARIATION_ID_NOT_IN_DATAFILE_NO_EXPERIMENT, - MODULE_NAME, - variation.id, - ); + logger?.error(VARIATION_ID_NOT_IN_DATAFILE, variation.id); return null; } @@ -705,23 +813,17 @@ export const getVariableValueForVariation = function ( * @returns {*} Variable value of the appropriate type, or * null if the type cast failed */ -export const getTypeCastValue = function ( +export const getTypeCastValue = function( variableValue: string, variableType: VariableType, - logger: LogHandler -): unknown { - let castValue; + logger?: LoggerFacade +): FeatureVariableValue { + let castValue : FeatureVariableValue; switch (variableType) { case FEATURE_VARIABLE_TYPES.BOOLEAN: if (variableValue !== 'true' && variableValue !== 'false') { - logger.log( - LOG_LEVEL.ERROR, - ERROR_MESSAGES.UNABLE_TO_CAST_VALUE, - MODULE_NAME, - variableValue, - variableType, - ); + logger?.error(UNABLE_TO_CAST_VALUE, variableValue, variableType); castValue = null; } else { castValue = variableValue === 'true'; @@ -731,13 +833,7 @@ export const getTypeCastValue = function ( case FEATURE_VARIABLE_TYPES.INTEGER: castValue = parseInt(variableValue, 10); if (isNaN(castValue)) { - logger.log( - LOG_LEVEL.ERROR, - ERROR_MESSAGES.UNABLE_TO_CAST_VALUE, - MODULE_NAME, - variableValue, - variableType, - ); + logger?.error(UNABLE_TO_CAST_VALUE, variableValue, variableType); castValue = null; } break; @@ -745,13 +841,7 @@ export const getTypeCastValue = function ( case FEATURE_VARIABLE_TYPES.DOUBLE: castValue = parseFloat(variableValue); if (isNaN(castValue)) { - logger.log( - LOG_LEVEL.ERROR, - ERROR_MESSAGES.UNABLE_TO_CAST_VALUE, - MODULE_NAME, - variableValue, - variableType, - ); + logger?.error(UNABLE_TO_CAST_VALUE, variableValue, variableType); castValue = null; } break; @@ -759,14 +849,8 @@ export const getTypeCastValue = function ( case FEATURE_VARIABLE_TYPES.JSON: try { castValue = JSON.parse(variableValue); - } catch (e: any) { - logger.log( - LOG_LEVEL.ERROR, - ERROR_MESSAGES.UNABLE_TO_CAST_VALUE, - MODULE_NAME, - variableValue, - variableType, - ); + } catch (e) { + logger?.error(UNABLE_TO_CAST_VALUE, variableValue, variableType); castValue = null; } break; @@ -786,7 +870,7 @@ export const getTypeCastValue = function ( * @param {ProjectConfig} projectConfig * @returns {{ [id: string]: Audience }} */ -export const getAudiencesById = function (projectConfig: ProjectConfig): { [id: string]: Audience } { +export const getAudiencesById = function(projectConfig: ProjectConfig): { [id: string]: Audience } { return projectConfig.audiencesById; }; @@ -796,7 +880,7 @@ export const getAudiencesById = function (projectConfig: ProjectConfig): { [id: * @param {string} eventKey * @returns {boolean} */ -export const eventWithKeyExists = function (projectConfig: ProjectConfig, eventKey: string): boolean { +export const eventWithKeyExists = function(projectConfig: ProjectConfig, eventKey: string): boolean { return projectConfig.eventKeyMap.hasOwnProperty(eventKey); }; @@ -804,9 +888,9 @@ export const eventWithKeyExists = function (projectConfig: ProjectConfig, eventK * Returns true if experiment belongs to any feature, false otherwise. * @param {ProjectConfig} projectConfig * @param {string} experimentId - * @returns {boolean} + * @returns {boolean} */ -export const isFeatureExperiment = function (projectConfig: ProjectConfig, experimentId: string): boolean { +export const isFeatureExperiment = function(projectConfig: ProjectConfig, experimentId: string): boolean { return projectConfig.experimentFeatureMap.hasOwnProperty(experimentId); }; @@ -815,9 +899,9 @@ export const isFeatureExperiment = function (projectConfig: ProjectConfig, exper * @param {ProjectConfig} projectConfig * @returns {string} */ -export const toDatafile = function (projectConfig: ProjectConfig): string { +export const toDatafile = function(projectConfig: ProjectConfig): string { return projectConfig.__datafileStr; -} +}; /** * @typedef {Object} @@ -828,34 +912,25 @@ export const toDatafile = function (projectConfig: ProjectConfig): string { /** * Try to create a project config object from the given datafile and * configuration properties. - * Returns an object with configObj and error properties. - * If successful, configObj is the project config object, and error is null. - * Otherwise, configObj is null and error is an error with more information. + * Returns a ProjectConfig if successful. + * Otherwise, throws an error. * @param {Object} config * @param {Object|string} config.datafile * @param {Object} config.jsonSchemaValidator * @param {Object} config.logger - * @returns {Object} Object containing configObj and error properties + * @returns {Object} ProjectConfig + * @throws {Error} */ -export const tryCreatingProjectConfig = function ( +export const tryCreatingProjectConfig = function( config: TryCreatingProjectConfigConfig -): { configObj: ProjectConfig | null; error: Error | null } { - let newDatafileObj; - try { - newDatafileObj = configValidator.validateDatafile(config.datafile); - } catch (error: any) { - return { configObj: null, error }; - } +): ProjectConfig { + const newDatafileObj = configValidator.validateDatafile(config.datafile); if (config.jsonSchemaValidator) { - try { - config.jsonSchemaValidator.validate(newDatafileObj); - config.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.VALID_DATAFILE, MODULE_NAME); - } catch (error : any) { - return { configObj: null, error }; - } + config.jsonSchemaValidator(newDatafileObj); + config.logger?.info(VALID_DATAFILE); } else { - config.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.SKIPPING_JSON_VALIDATION, MODULE_NAME); + config.logger?.info(SKIPPING_JSON_VALIDATION); } const createProjectConfigArgs = [newDatafileObj]; @@ -865,11 +940,7 @@ export const tryCreatingProjectConfig = function ( } const newConfigObj = createProjectConfig(...createProjectConfigArgs); - - return { - configObj: newConfigObj, - error: null, - }; + return newConfigObj; }; /** @@ -877,9 +948,9 @@ export const tryCreatingProjectConfig = function ( * @param {ProjectConfig} projectConfig * @return {boolean} A boolean value that indicates if we should send flag decisions */ -export const getSendFlagDecisionsValue = function (projectConfig: ProjectConfig): boolean { +export const getSendFlagDecisionsValue = function(projectConfig: ProjectConfig): boolean { return !!projectConfig.sendFlagDecisions; -} +}; export default { createProjectConfig, @@ -895,7 +966,6 @@ export default { getVariationKeyFromId, getVariationIdFromExperimentAndVariationKey, getExperimentFromKey, - getTrafficAllocation, getExperimentFromId, getFlagVariationByKey, getFeatureFromKey, @@ -909,4 +979,5 @@ export default { isFeatureExperiment, toDatafile, tryCreatingProjectConfig, + getTrafficAllocation, }; diff --git a/lib/project_config/project_config_manager.spec.ts b/lib/project_config/project_config_manager.spec.ts new file mode 100644 index 000000000..76c9af736 --- /dev/null +++ b/lib/project_config/project_config_manager.spec.ts @@ -0,0 +1,569 @@ +/** + * Copyright 2019-2020, 2022, 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, vi } from 'vitest'; +import { LOGGER_NAME, ProjectConfigManagerImpl } from './project_config_manager'; +import { getMockLogger } from '../tests/mock/mock_logger'; +import { ServiceState } from '../service'; +import * as testData from '../tests/test_data'; +import { createProjectConfig } from './project_config'; +import { resolvablePromise } from '../utils/promise/resolvablePromise'; +import { getMockDatafileManager } from '../tests/mock/mock_datafile_manager'; +import { wait } from '../tests/testUtils'; + +const cloneDeep = (x: any) => JSON.parse(JSON.stringify(x)); + +describe('ProjectConfigManagerImpl', () => { + describe('a logger is passed in the constructor', () => { + it('should set name on the logger passed into the constructor', () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({ logger }); + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME) + }); + + it('should pass a child logger to the datafileManager', () => { + const logger = getMockLogger(); + const childLogger = getMockLogger(); + logger.child.mockReturnValue(childLogger); + const datafileManager = getMockDatafileManager({}); + const datafileManagerSetLogger = vi.spyOn(datafileManager, 'setLogger'); + + const manager = new ProjectConfigManagerImpl({ logger, datafileManager }); + expect(datafileManagerSetLogger).toHaveBeenCalledWith(childLogger); + }); + }); + + describe('setLogger method', () => { + it('should set name on the logger', () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({}); + manager.setLogger(logger); + expect(logger.setName).toHaveBeenCalledWith(LOGGER_NAME) + }); + + it('should pass a child logger to the datafileManager', () => { + const logger = getMockLogger(); + const childLogger = getMockLogger(); + logger.child.mockReturnValue(childLogger); + const datafileManager = getMockDatafileManager({}); + const datafileManagerSetLogger = vi.spyOn(datafileManager, 'setLogger'); + + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.setLogger(logger); + expect(datafileManagerSetLogger).toHaveBeenCalledWith(childLogger); + }); + }); + + it('should reject onRunning() and log error if neither datafile nor a datafileManager is passed into the constructor', async () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({ logger}); + manager.start(); + await expect(manager.onRunning()).rejects.toThrow(); + expect(logger.error).toHaveBeenCalled(); + }); + + it('should set status to Failed if neither datafile nor a datafileManager is passed into the constructor', async () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({ logger}); + manager.start(); + await expect(manager.onRunning()).rejects.toThrow(); + expect(manager.getState()).toBe(ServiceState.Failed); + }); + + it('should reject onTerminated if neither datafile nor a datafileManager is passed into the constructor', async () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({ logger}); + manager.start(); + await expect(manager.onTerminated()).rejects.toThrow(); + }); + + describe('when constructed with only a datafile', () => { + it('should reject onRunning() and log error if the datafile is invalid', async () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({ logger, datafile: {}}); + manager.start(); + await expect(manager.onRunning()).rejects.toThrow(); + expect(logger.error).toHaveBeenCalled(); + }); + + it('should set status to Failed if the datafile is invalid', async () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({ logger, datafile: {}}); + manager.start(); + await expect(manager.onRunning()).rejects.toThrow(); + expect(manager.getState()).toBe(ServiceState.Failed); + }); + + it('should reject onTerminated if the datafile is invalid', async () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({ logger}); + manager.start(); + await expect(manager.onTerminated()).rejects.toThrow(); + }); + + it('should fulfill onRunning() and set status to Running if the datafile is valid', async () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({ logger, datafile: testData.getTestProjectConfig()}); + manager.start(); + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(manager.getState()).toBe(ServiceState.Running); + }); + + it('should call onUpdate listeners registered before start() with the project config', async () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({ logger, datafile: testData.getTestProjectConfig()}); + const listener = vi.fn(); + manager.onUpdate(listener); + manager.start(); + + await manager.onRunning(); + + expect(listener).toHaveBeenCalledOnce(); + expect(listener).toHaveBeenCalledWith(createProjectConfig(testData.getTestProjectConfig())); + }); + + it('should return the correct config from getConfig() both before or after onRunning() resolves', async () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({ logger, datafile: testData.getTestProjectConfig()}); + manager.start(); + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); + await manager.onRunning(); + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); + }); + }); + + describe('when constructed with a datafileManager', () => { + describe('when datafile is also provided', () => { + describe('when datafile is valid', () => { + it('should resolve onRunning() before datafileManger.onRunning() resolves', async () => { + const datafileManager = getMockDatafileManager({ + onRunning: resolvablePromise<void>().promise, // this will not be resolved + }); + vi.spyOn(datafileManager, 'onRunning'); + const manager = new ProjectConfigManagerImpl({ datafile: testData.getTestProjectConfig(), datafileManager }); + manager.start(); + + expect(datafileManager.onRunning).toHaveBeenCalled(); + await expect(manager.onRunning()).resolves.not.toThrow(); + }); + + it('should resolve onRunning() even if datafileManger.onRunning() rejects', async () => { + const onRunning = Promise.reject(new Error("onRunning error")); + const datafileManager = getMockDatafileManager({ + onRunning, + }); + vi.spyOn(datafileManager, 'onRunning'); + const manager = new ProjectConfigManagerImpl({ datafile: testData.getTestProjectConfig(), datafileManager }); + manager.start(); + + expect(datafileManager.onRunning).toHaveBeenCalled(); + await expect(manager.onRunning()).resolves.not.toThrow(); + }); + + it('should call the onUpdate handler before datafileManger.onRunning() resolves', async () => { + const datafileManager = getMockDatafileManager({ + onRunning: resolvablePromise<void>().promise, // this will not be resolved + }); + + const listener = vi.fn(); + + const manager = new ProjectConfigManagerImpl({ datafile: testData.getTestProjectConfig(), datafileManager }); + manager.onUpdate(listener); + manager.start(); + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(listener).toHaveBeenCalledWith(createProjectConfig(testData.getTestProjectConfig())); + }); + + it('should return the correct config from getConfig() both before or after onRunning() resolves', async () => { + const datafileManager = getMockDatafileManager({ + onRunning: resolvablePromise<void>().promise, // this will not be resolved + }); + + const manager = new ProjectConfigManagerImpl({ datafile: testData.getTestProjectConfig(), datafileManager }); + manager.start(); + + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); + await manager.onRunning(); + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); + }); + }); + + describe('when datafile is invalid', () => { + it('should reject onRunning() with the same error if datafileManager.onRunning() rejects', async () => { + const datafileManager = getMockDatafileManager({ onRunning: Promise.reject(new Error('test error')) }); + const manager = new ProjectConfigManagerImpl({ datafile: {}, datafileManager }); + manager.start(); + await expect(manager.onRunning()).rejects.toThrow('DatafileManager failed to start, reason: test error'); + }); + + it('should resolve onRunning() if datafileManager.onUpdate() is fired and should update config', async () => { + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve() }); + const manager = new ProjectConfigManagerImpl({ datafile: {}, datafileManager }); + manager.start(); + datafileManager.pushUpdate(testData.getTestProjectConfig()); + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); + }); + + it('should resolve onRunning(), update config and call onUpdate listeners if datafileManager.onUpdate() is fired', async () => { + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve() }); + const manager = new ProjectConfigManagerImpl({ datafile: {}, datafileManager }); + manager.start(); + const listener = vi.fn(); + manager.onUpdate(listener); + + datafileManager.pushUpdate(testData.getTestProjectConfig()); + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); + expect(listener).toHaveBeenCalledWith(createProjectConfig(testData.getTestProjectConfig())); + }); + + it('should return undefined from getConfig() before onRunning() resolves', async () => { + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve() }); + const manager = new ProjectConfigManagerImpl({ datafile: {}, datafileManager }); + manager.start(); + expect(manager.getConfig()).toBeUndefined(); + }); + + it('should return the correct config from getConfig() after onRunning() resolves', async () => { + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve() }); + const manager = new ProjectConfigManagerImpl({ datafile: {}, datafileManager }); + manager.start(); + + datafileManager.pushUpdate(testData.getTestProjectConfig()); + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); + }); + }); + }); + + describe('when datafile is not provided', () => { + it('should reject onRunning() if datafileManager.onRunning() rejects', async () => { + const datafileManager = getMockDatafileManager({ onRunning: Promise.reject(new Error('test error')) }); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.start(); + await expect(manager.onRunning()).rejects.toThrow('DatafileManager failed to start, reason: test error'); + }); + + it('should reject onRunning() and onTerminated if datafileManager emits an invalid datafile in the first onUpdate', async () => { + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve() }); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.start(); + datafileManager.pushUpdate('foo'); + await expect(manager.onRunning()).rejects.toThrow(); + await expect(manager.onTerminated()).rejects.toThrow(); + }); + + it('should resolve onRunning(), update config and call onUpdate listeners if datafileManager.onUpdate() is fired', async () => { + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve() }); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.start(); + const listener = vi.fn(); + manager.onUpdate(listener); + + datafileManager.pushUpdate(testData.getTestProjectConfig()); + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); + expect(listener).toHaveBeenCalledWith(createProjectConfig(testData.getTestProjectConfig())); + }); + + it('should return undefined from getConfig() before onRunning() resolves', async () => { + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve() }); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.start(); + expect(manager.getConfig()).toBeUndefined(); + }); + + it('should return the correct config from getConfig() after onRunning() resolves', async () => { + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve() }); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.start(); + + datafileManager.pushUpdate(testData.getTestProjectConfig()); + await expect(manager.onRunning()).resolves.not.toThrow(); + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); + }); + }); + + it('should update the config and call onUpdate handlers when datafileManager onUpdate is fired with valid datafile', async () => { + const datafileManager = getMockDatafileManager({}); + + const datafile = testData.getTestProjectConfig(); + const manager = new ProjectConfigManagerImpl({ datafile, datafileManager }); + + const listener = vi.fn(); + manager.onUpdate(listener); + + manager.start(); + + expect(manager.getConfig()).toEqual(createProjectConfig(datafile)); + await manager.onRunning(); + expect(manager.getConfig()).toEqual(createProjectConfig(datafile)); + expect(listener).toHaveBeenNthCalledWith(1, createProjectConfig(datafile)); + + const updatedDatafile = cloneDeep(datafile); + updatedDatafile['revision'] = '99'; + datafileManager.pushUpdate(updatedDatafile); + await Promise.resolve(); + + expect(manager.getConfig()).toEqual(createProjectConfig(updatedDatafile)); + expect(listener).toHaveBeenNthCalledWith(2, createProjectConfig(updatedDatafile)); + }); + + it('should not call onUpdate handlers and should log error when datafileManager onUpdate is fired with invalid datafile', async () => { + const datafileManager = getMockDatafileManager({}); + + const logger = getMockLogger(); + const datafile = testData.getTestProjectConfig(); + const manager = new ProjectConfigManagerImpl({ logger, datafile, datafileManager }); + + const listener = vi.fn(); + manager.onUpdate(listener); + + manager.start(); + + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); + await manager.onRunning(); + expect(manager.getConfig()).toEqual(createProjectConfig(testData.getTestProjectConfig())); + + expect(listener).toHaveBeenCalledWith(createProjectConfig(datafile)); + + const updatedDatafile = {}; + datafileManager.pushUpdate(updatedDatafile); + await Promise.resolve(); + + expect(manager.getConfig()).toEqual(createProjectConfig(datafile)); + expect(listener).toHaveBeenCalledTimes(1); + expect(logger.error).toHaveBeenCalled(); + }); + + it('should use the JSON schema validator to validate the datafile', async () => { + const datafileManager = getMockDatafileManager({}); + + const datafile = testData.getTestProjectConfig(); + const jsonSchemaValidator = vi.fn().mockReturnValue(true); + const manager = new ProjectConfigManagerImpl({ datafile, datafileManager, jsonSchemaValidator }); + manager.start(); + + await manager.onRunning(); + + const updatedDatafile = cloneDeep(datafile); + updatedDatafile['revision'] = '99'; + datafileManager.pushUpdate(updatedDatafile); + await Promise.resolve(); + + expect(jsonSchemaValidator).toHaveBeenCalledTimes(2); + expect(jsonSchemaValidator).toHaveBeenNthCalledWith(1, datafile); + expect(jsonSchemaValidator).toHaveBeenNthCalledWith(2, updatedDatafile); + }); + + it('should not call onUpdate handlers when datafileManager onUpdate is fired with the same datafile', async () => { + const datafileManager = getMockDatafileManager({}); + + const datafile = testData.getTestProjectConfig(); + const manager = new ProjectConfigManagerImpl({ datafile, datafileManager }); + + const listener = vi.fn(); + manager.onUpdate(listener); + + manager.start(); + + expect(manager.getConfig()).toEqual(createProjectConfig(datafile)); + await manager.onRunning(); + expect(manager.getConfig()).toEqual(createProjectConfig(datafile)); + expect(listener).toHaveBeenNthCalledWith(1, createProjectConfig(datafile)); + + datafileManager.pushUpdate(cloneDeep(datafile)); + await Promise.resolve(); + + expect(manager.getConfig()).toEqual(createProjectConfig(datafile)); + expect(listener).toHaveBeenCalledTimes(1); + }); + + it('should remove onUpdate handlers when the returned fuction is called', async () => { + const datafile = testData.getTestProjectConfig(); + const datafileManager = getMockDatafileManager({}); + + const manager = new ProjectConfigManagerImpl({ datafile }); + + const listener = vi.fn(); + const dispose = manager.onUpdate(listener); + + manager.start(); + + await manager.onRunning(); + expect(listener).toHaveBeenNthCalledWith(1, createProjectConfig(datafile)); + + dispose(); + + datafileManager.pushUpdate(cloneDeep(testData.getTestProjectConfigWithFeatures())); + await Promise.resolve(); + expect(listener).toHaveBeenCalledTimes(1); + }); + + it('should work with datafile specified as string', async () => { + const datafile = testData.getTestProjectConfig(); + + const manager = new ProjectConfigManagerImpl({ datafile: JSON.stringify(datafile) }); + + const listener = vi.fn(); + manager.onUpdate(listener); + + manager.start(); + + await manager.onRunning(); + expect(listener).toHaveBeenCalledWith(createProjectConfig(datafile)); + expect(manager.getConfig()).toEqual(createProjectConfig(datafile)); + }); + + it('should reject onRunning() and log error if the datafile string is an invalid json', async () => { + const logger = getMockLogger(); + const manager = new ProjectConfigManagerImpl({ logger, datafile: 'foo'}); + manager.start(); + await expect(manager.onRunning()).rejects.toThrow(); + expect(logger.error).toHaveBeenCalled(); + }); + + it('should reject onRunning() and log error if the datafile version is not supported', async () => { + const logger = getMockLogger(); + const datafile = testData.getUnsupportedVersionConfig(); + const manager = new ProjectConfigManagerImpl({ logger, datafile }); + manager.start(); + + await expect(manager.onRunning()).rejects.toThrow(); + expect(logger.error).toHaveBeenCalled(); + }); + + describe('stop()', () => { + it('should reject onRunning() if stop is called when the datafileManager state is New', async () => { + const datafileManager = getMockDatafileManager({}); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + + expect(manager.getState()).toBe(ServiceState.New); + manager.stop(); + await expect(manager.onRunning()).rejects.toThrow(); + }); + + it('should reject onRunning() if stop is called when the datafileManager state is Starting', async () => { + const datafileManager = getMockDatafileManager({}); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + + manager.start(); + expect(manager.getState()).toBe(ServiceState.Starting); + manager.stop(); + await expect(manager.onRunning()).rejects.toThrow(); + }); + + it('should call datafileManager.stop()', async () => { + const datafileManager = getMockDatafileManager({}); + const spy = vi.spyOn(datafileManager, 'stop'); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.start(); + manager.stop(); + expect(spy).toHaveBeenCalled(); + }); + + it('should set status to Terminated immediately if no datafile manager is provided and resolve onTerminated', async () => { + const manager = new ProjectConfigManagerImpl({ datafile: testData.getTestProjectConfig() }); + manager.stop(); + expect(manager.getState()).toBe(ServiceState.Terminated); + await expect(manager.onTerminated()).resolves.not.toThrow(); + }); + + it('should set status to Stopping while awaiting for datafileManager onTerminated', async () => { + const datafileManagerTerminated = resolvablePromise<void>(); + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve(), onTerminated: datafileManagerTerminated.promise }); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.start(); + datafileManager.pushUpdate(testData.getTestProjectConfig()); + await manager.onRunning(); + manager.stop(); + + for (let i = 0; i < 100; i++) { + expect(manager.getState()).toBe(ServiceState.Stopping); + await wait(0); + } + }); + + it('should set status to Terminated and resolve onTerminated after datafileManager.onTerminated() resolves', async () => { + const datafileManagerTerminated = resolvablePromise<void>(); + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve(), onTerminated: datafileManagerTerminated.promise }); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.start(); + datafileManager.pushUpdate(testData.getTestProjectConfig()); + await manager.onRunning(); + manager.stop(); + + for (let i = 0; i < 50; i++) { + expect(manager.getState()).toBe(ServiceState.Stopping); + await wait(0); + } + + datafileManagerTerminated.resolve(); + await expect(manager.onTerminated()).resolves.not.toThrow(); + expect(manager.getState()).toBe(ServiceState.Terminated); + }); + + it('should set status to Failed and reject onTerminated after datafileManager.onTerminated() rejects', async () => { + const datafileManagerTerminated = resolvablePromise<void>(); + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve(), onTerminated: datafileManagerTerminated.promise }); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.start(); + datafileManager.pushUpdate(testData.getTestProjectConfig()); + await manager.onRunning(); + manager.stop(); + + for (let i = 0; i < 50; i++) { + expect(manager.getState()).toBe(ServiceState.Stopping); + await wait(0); + } + + datafileManagerTerminated.reject(); + await expect(manager.onTerminated()).rejects.toThrow(); + expect(manager.getState()).toBe(ServiceState.Failed); + }); + + it('should not call onUpdate handlers after stop is called', async () => { + const datafileManagerTerminated = resolvablePromise<void>(); + const datafileManager = getMockDatafileManager({ onRunning: Promise.resolve(), onTerminated: datafileManagerTerminated.promise }); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.start(); + const listener = vi.fn(); + manager.onUpdate(listener); + + datafileManager.pushUpdate(testData.getTestProjectConfig()); + await manager.onRunning(); + + expect(listener).toHaveBeenCalledTimes(1); + manager.stop(); + datafileManager.pushUpdate(testData.getTestProjectConfigWithFeatures()); + + datafileManagerTerminated.resolve(); + await expect(manager.onTerminated()).resolves.not.toThrow(); + + expect(listener).toHaveBeenCalledTimes(1); + }); + + it('should make datafileManager disposable if makeDisposable() is called', async () => { + const datafileManager = getMockDatafileManager({}); + vi.spyOn(datafileManager, 'makeDisposable'); + const manager = new ProjectConfigManagerImpl({ datafileManager }); + manager.makeDisposable(); + + expect(datafileManager.makeDisposable).toHaveBeenCalled(); + }) + }); + }); +}); diff --git a/lib/project_config/project_config_manager.ts b/lib/project_config/project_config_manager.ts new file mode 100644 index 000000000..8d7002c03 --- /dev/null +++ b/lib/project_config/project_config_manager.ts @@ -0,0 +1,237 @@ +/** + * Copyright 2019-2022, 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LoggerFacade } from '../logging/logger'; +import { createOptimizelyConfig } from './optimizely_config'; +import { OptimizelyConfig } from '../shared_types'; +import { DatafileManager } from './datafile_manager'; +import { ProjectConfig, toDatafile, tryCreatingProjectConfig } from './project_config'; +import { Service, ServiceState, BaseService } from '../service'; +import { Consumer, Fn, Transformer } from '../utils/type'; +import { EventEmitter } from '../utils/event_emitter/event_emitter'; + +import { + SERVICE_FAILED_TO_START, + SERVICE_STOPPED_BEFORE_RUNNING, +} from '../service' + +export const NO_SDKKEY_OR_DATAFILE = 'sdkKey or datafile must be provided'; +export const GOT_INVALID_DATAFILE = 'got invalid datafile'; + +import { sprintf } from '../utils/fns'; +interface ProjectConfigManagerConfig { + datafile?: string | Record<string, unknown>; + jsonSchemaValidator?: Transformer<unknown, boolean>, + datafileManager?: DatafileManager; + logger?: LoggerFacade; +} + +export interface ProjectConfigManager extends Service { + setLogger(logger: LoggerFacade): void; + getConfig(): ProjectConfig | undefined; + getOptimizelyConfig(): OptimizelyConfig | undefined; + onUpdate(listener: Consumer<ProjectConfig>): Fn; +} + +/** + * ProjectConfigManager provides project config objects via its methods + * getConfig and onUpdate. It uses a DatafileManager to fetch datafile if provided. + * It is responsible for parsing and validating datafiles, and converting datafile + * string into project config objects. + * @param {ProjectConfigManagerConfig} config + */ + +export const LOGGER_NAME = 'ProjectConfigManager'; + +export class ProjectConfigManagerImpl extends BaseService implements ProjectConfigManager { + private datafile?: string | object; + private projectConfig?: ProjectConfig; + private optimizelyConfig?: OptimizelyConfig; + public jsonSchemaValidator?: Transformer<unknown, boolean>; + public datafileManager?: DatafileManager; + private eventEmitter: EventEmitter<{ update: ProjectConfig }> = new EventEmitter(); + + constructor(config: ProjectConfigManagerConfig) { + super(); + this.jsonSchemaValidator = config.jsonSchemaValidator; + this.datafile = config.datafile; + this.datafileManager = config.datafileManager; + + if (config.logger) { + this.setLogger(config.logger); + } + } + + setLogger(logger: LoggerFacade): void { + this.logger = logger; + this.logger.setName(LOGGER_NAME); + this.datafileManager?.setLogger(logger.child()); + } + + start(): void { + if (!this.isNew()) { + return; + } + + this.state = ServiceState.Starting; + + if (!this.datafile && !this.datafileManager) { + this.handleInitError(new Error(NO_SDKKEY_OR_DATAFILE)); + return; + } + + if (this.datafile) { + this.handleNewDatafile(this.datafile, true); + } + + this.datafileManager?.start(); + + // This handles the case where the datafile manager starts successfully. The + // datafile manager will only start successfully when it has downloaded a datafile, + // an will fire an onUpdate event. + this.datafileManager?.onUpdate(this.handleNewDatafile.bind(this)); + + // If the datafile manager runs successfully, it will emit a onUpdate event. We can + // handle the success case in the onUpdate handler. Hanlding the error case in the + // catch callback + this.datafileManager?.onRunning().catch((err) => { + this.handleDatafileManagerError(err); + }); + } + + makeDisposable(): void { + super.makeDisposable(); + this.datafileManager?.makeDisposable(); + } + + private handleInitError(error: Error): void { + this.logger?.error(error); + this.state = ServiceState.Failed; + this.datafileManager?.stop(); + this.startPromise.reject(error); + this.stopPromise.reject(error); + } + + private handleDatafileManagerError(err: Error): void { + this.logger?.error(SERVICE_FAILED_TO_START, 'DatafileManager', err.message); + + // If datafile manager onRunning() promise is rejected, and the project config manager + // is still in starting state, that means a datafile was not provided in cofig or was invalid, + // otherwise the state would have already been set to running synchronously. + // In this case, we cannot recover. + if (this.isStarting()) { + this.handleInitError(new Error( + sprintf(SERVICE_FAILED_TO_START, 'DatafileManager', err.message) + )); + } + } + + /** + * Handle new datafile by attemping to create a new Project Config object. If successful and + * the new config object's revision is newer than the current one, sets/updates the project config + * and emits onUpdate event. If unsuccessful, + * the project config and optimizely config objects will not be updated. If the error + * is fatal, handleInitError will be called. + */ + private handleNewDatafile(newDatafile: string | object, fromConfig = false): void { + if (this.isDone()) { + return; + } + + try { + const config = tryCreatingProjectConfig({ + datafile: newDatafile, + jsonSchemaValidator: this.jsonSchemaValidator, + logger: this.logger, + }); + + if(this.isStarting()) { + this.state = ServiceState.Running; + this.startPromise.resolve(); + } + + if (this.projectConfig?.revision !== config.revision) { + this.projectConfig = config; + this.optimizelyConfig = undefined; + this.eventEmitter.emit('update', config); + } + } catch (err) { + this.logger?.error(err); + + // if the state is starting and no datafileManager is provided, we cannot recover. + // If the state is starting and the datafileManager has emitted a datafile, + // that means a datafile was not provided in config or an invalid datafile was provided, + // otherwise the state would have already been set to running synchronously. + // If the first datafile emitted by the datafileManager is invalid, + // we consider this to be an initialization error as well. + const fatalError = (this.isStarting() && !this.datafileManager) || + (this.isStarting() && !fromConfig); + if (fatalError) { + this.handleInitError(new Error(GOT_INVALID_DATAFILE)); + } + } + } + + getConfig(): ProjectConfig | undefined { + return this.projectConfig; + } + + getOptimizelyConfig(): OptimizelyConfig | undefined { + if (!this.optimizelyConfig && this.projectConfig) { + this.optimizelyConfig = createOptimizelyConfig(this.projectConfig, toDatafile(this.projectConfig), this.logger); + } + return this.optimizelyConfig; + } + + /** + * Add a listener for project config updates. The listener will be called + * whenever this instance has a new project config object available. + * Returns a dispose function that removes the subscription + * @param {Function} listener + * @return {Function} + */ + onUpdate(listener: Consumer<ProjectConfig>): Fn { + return this.eventEmitter.on('update', listener); + } + + stop(): void { + if (this.isDone()) { + return; + } + + if (this.isNew() || this.isStarting()) { + this.startPromise.reject(new Error( + sprintf(SERVICE_STOPPED_BEFORE_RUNNING, 'ProjectConfigManager') + )); + } + + this.state = ServiceState.Stopping; + this.eventEmitter.removeAllListeners(); + if (!this.datafileManager) { + this.state = ServiceState.Terminated; + this.stopPromise.resolve(); + return; + } + + this.datafileManager.stop(); + this.datafileManager.onTerminated().then(() => { + this.state = ServiceState.Terminated; + this.stopPromise.resolve(); + }).catch((err) => { + this.state = ServiceState.Failed; + this.stopPromise.reject(err); + }); + } +} diff --git a/packages/optimizely-sdk/lib/core/project_config/project_config_schema.ts b/lib/project_config/project_config_schema.ts similarity index 94% rename from packages/optimizely-sdk/lib/core/project_config/project_config_schema.ts rename to lib/project_config/project_config_schema.ts index 27fbb0aad..f842179dc 100644 --- a/packages/optimizely-sdk/lib/core/project_config/project_config_schema.ts +++ b/lib/project_config/project_config_schema.ts @@ -1,5 +1,5 @@ /** - * Copyright 2016-2017, 2020, Optimizely + * Copyright 2016-2017, 2020, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -202,6 +202,19 @@ var schemaDefinition = { type: 'object', required: true, }, + cmab: { + type: 'object', + required: false, + properties: { + attributes: { + type: 'array', + items: { + type: 'string', + }, + required: true, + } + } + } }, }, required: true, @@ -290,10 +303,13 @@ var schemaDefinition = { }, publicKey: { type: 'string' - } - } - } - } + }, + pixelUrl: { + type: 'string' + }, + }, + }, + }, }, }; diff --git a/lib/service.spec.ts b/lib/service.spec.ts new file mode 100644 index 000000000..0b9d7c754 --- /dev/null +++ b/lib/service.spec.ts @@ -0,0 +1,134 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { it, expect } from 'vitest'; +import { BaseService, ServiceState, StartupLog } from './service'; +import { LogLevel } from './logging/logger'; +import { getMockLogger } from './tests/mock/mock_logger'; +class TestService extends BaseService { + constructor(startUpLogs?: StartupLog[]) { + super(startUpLogs); + } + + start(): void { + super.start(); + this.setState(ServiceState.Running); + this.startPromise.resolve(); + } + + failStart(): void { + this.setState(ServiceState.Failed); + this.startPromise.reject(); + } + + stop(): void { + this.setState(ServiceState.Running); + this.startPromise.resolve(); + } + + failStop(): void { + this.setState(ServiceState.Failed); + this.startPromise.reject(); + } + + setState(state: ServiceState): void { + this.state = state; + } +} + + +it('should set state to New on construction', async () => { + const service = new TestService(); + expect(service.getState()).toBe(ServiceState.New); +}); + +it('should return correct state when getState() is called', () => { + const service = new TestService(); + expect(service.getState()).toBe(ServiceState.New); + service.setState(ServiceState.Running); + expect(service.getState()).toBe(ServiceState.Running); + service.setState(ServiceState.Terminated); + expect(service.getState()).toBe(ServiceState.Terminated); + service.setState(ServiceState.Failed); + expect(service.getState()).toBe(ServiceState.Failed); +}); + +it('should log startupLogs on start', () => { + const startUpLogs: StartupLog[] = [ + { + level: LogLevel.Warn, + message: 'warn message', + params: [1, 2] + }, + { + level: LogLevel.Error, + message: 'error message', + params: [3, 4] + }, + ]; + + const logger = getMockLogger(); + const service = new TestService(startUpLogs); + service.setLogger(logger); + service.start(); + + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.error).toHaveBeenCalledTimes(1); + expect(logger.warn).toHaveBeenCalledWith('warn message', 1, 2); + expect(logger.error).toHaveBeenCalledWith('error message', 3, 4); +}); + +it('should return an appropraite promise when onRunning() is called', () => { + const service1 = new TestService(); + const onRunning1 = service1.onRunning(); + + const service2 = new TestService(); + const onRunning2 = service2.onRunning(); + + return new Promise<void>((done) => { + Promise.all([ + onRunning1.then(() => { + expect(service1.getState()).toBe(ServiceState.Running); + }), onRunning2.catch(() => { + expect(service2.getState()).toBe(ServiceState.Failed); + }) + ]).then(() => done()); + + service1.start(); + service2.failStart(); + }); +}); + +it('should return an appropraite promise when onRunning() is called', () => { + const service1 = new TestService(); + const onRunning1 = service1.onRunning(); + + const service2 = new TestService(); + const onRunning2 = service2.onRunning(); + + return new Promise<void>((done) => { + Promise.all([ + onRunning1.then(() => { + expect(service1.getState()).toBe(ServiceState.Running); + }), onRunning2.catch(() => { + expect(service2.getState()).toBe(ServiceState.Failed); + }) + ]).then(() => done()); + + service1.start(); + service2.failStart(); + }); +}); diff --git a/lib/service.ts b/lib/service.ts new file mode 100644 index 000000000..3022aa806 --- /dev/null +++ b/lib/service.ts @@ -0,0 +1,134 @@ +/** + * Copyright 2024-2025 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { LoggerFacade, LogLevel, LogLevelToLower } from './logging/logger' +import { resolvablePromise, ResolvablePromise } from "./utils/promise/resolvablePromise"; + +export const SERVICE_FAILED_TO_START = '%s failed to start, reason: %s'; +export const SERVICE_STOPPED_BEFORE_RUNNING = '%s stopped before running'; + +/** + * The service interface represents an object with an operational state, + * with methods to start and stop. The design of this interface in modelled + * after Guava Service interface (https://github.com/google/guava/wiki/ServiceExplained). + */ + +export enum ServiceState { + New, + Starting, + Running, + Stopping, + Terminated, + Failed, +} + +export type StartupLog = { + level: LogLevel; + message: string; + params: any[]; +} + +export interface Service { + getState(): ServiceState; + start(): void; + // onRunning will reject if the service fails to start + // or stopped before it could start. + // It will resolve if the service is starts successfully. + onRunning(): Promise<void>; + stop(): void; + // onTerminated will reject if the service enters a failed state + // either by failing to start or stop. + // It will resolve if the service is stopped successfully. + onTerminated(): Promise<void>; + makeDisposable(): void; +} + +export abstract class BaseService implements Service { + protected state: ServiceState; + protected startPromise: ResolvablePromise<void>; + protected stopPromise: ResolvablePromise<void>; + protected logger?: LoggerFacade; + protected startupLogs: StartupLog[]; + protected disposable = false; + constructor(startupLogs: StartupLog[] = []) { + this.state = ServiceState.New; + this.startPromise = resolvablePromise(); + this.stopPromise = resolvablePromise(); + this.startupLogs = startupLogs; + + // avoid unhandled promise rejection + this.startPromise.promise.catch(() => {}); + this.stopPromise.promise.catch(() => {}); + } + + makeDisposable(): void { + this.disposable = true; + } + + setLogger(logger: LoggerFacade): void { + this.logger = logger; + } + + protected printStartupLogs(): void { + if (!this.logger) { + return; + } + + for (const { level, message, params } of this.startupLogs) { + const methodName: string = LogLevelToLower[level]; + const method = this.logger[methodName as keyof LoggerFacade]; + method.call(this.logger, message, ...params); + } + } + + onRunning(): Promise<void> { + return this.startPromise.promise; + } + + onTerminated(): Promise<void> { + return this.stopPromise.promise; + } + + getState(): ServiceState { + return this.state; + } + + isStarting(): boolean { + return this.state === ServiceState.Starting; + } + + isRunning(): boolean { + return this.state === ServiceState.Running; + } + + isNew(): boolean { + return this.state === ServiceState.New; + } + + isDone(): boolean { + return [ + ServiceState.Stopping, + ServiceState.Terminated, + ServiceState.Failed + ].includes(this.state); + } + + start(): void { + this.printStartupLogs(); + } + + abstract stop(): void; +} diff --git a/packages/optimizely-sdk/lib/shared_types.ts b/lib/shared_types.ts similarity index 59% rename from packages/optimizely-sdk/lib/shared_types.ts rename to lib/shared_types.ts index f3ff5251b..1b450b1dd 100644 --- a/packages/optimizely-sdk/lib/shared_types.ts +++ b/lib/shared_types.ts @@ -1,5 +1,5 @@ /** - * Copyright 2020-2022, Optimizely + * Copyright 2020-2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,11 +13,46 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { ErrorHandler, LogHandler, LogLevel, LoggerFacade } from '../lib/modules/logging'; -import { EventProcessor } from '../lib/modules/event_processor'; -import { NotificationCenter as NotificationCenterImpl } from './core/notification_center' -import { NOTIFICATION_TYPES } from './utils/enums'; +/** + * This file contains the shared type definitions collected from the SDK. + * These shared type definitions include ones that will be referenced by external consumers via export_types.ts. + */ + +// import { ErrorHandler, LogHandler, LogLevel, LoggerFacade } from './modules/logging'; +import { LoggerFacade, LogLevel } from './logging/logger'; +import { ErrorHandler } from './error/error_handler'; + +import { NotificationCenter, DefaultNotificationCenter } from './notification_center'; + +import { IOptimizelyUserContext as OptimizelyUserContext } from './optimizely_user_context'; + +import { RequestHandler } from './utils/http_request_handler/http'; +import { OptimizelySegmentOption } from './odp/segment_manager/optimizely_segment_option'; +import { OdpSegmentApiManager } from './odp/segment_manager/odp_segment_api_manager'; +import { OdpSegmentManager } from './odp/segment_manager/odp_segment_manager'; +import { DefaultOdpEventApiManager } from './odp/event_manager/odp_event_api_manager'; +import { OdpEventManager } from './odp/event_manager/odp_event_manager'; +import { OdpManager } from './odp/odp_manager'; +import { ProjectConfig } from './project_config/project_config'; +import { OpaqueConfigManager } from './project_config/config_manager_factory'; +import { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; +import { EventProcessor } from './event_processor/event_processor'; +import { VuidManager } from './vuid/vuid_manager'; +import { ErrorNotifier } from './error/error_notifier'; +import { OpaqueLogger } from './logging/logger_factory'; +import { OpaqueErrorNotifier } from './error/error_notifier_factory'; +import { OpaqueEventProcessor } from './event_processor/event_processor_factory'; +import { OpaqueOdpManager } from './odp/odp_manager_factory'; +import { OpaqueVuidManager } from './vuid/vuid_manager_factory'; +import { CacheWithRemove } from './utils/cache/cache'; + +export { EventDispatcher } from './event_processor/event_dispatcher/event_dispatcher'; +export { EventProcessor } from './event_processor/event_processor'; +export { NotificationCenter } from './notification_center'; +export { VuidManager } from './vuid/vuid_manager'; +export { OpaqueLogger } from './logging/logger_factory'; +export { OpaqueErrorNotifier } from './error/error_notifier_factory'; export interface BucketerParams { experimentId: string; @@ -28,24 +63,27 @@ export interface BucketerParams { experimentIdMap: { [id: string]: Experiment }; groupIdMap: { [key: string]: Group }; variationIdMap: { [id: string]: Variation }; - logger: LogHandler; + logger?: LoggerFacade; bucketingId: string; + validateEntity?: boolean; } export interface DecisionResponse<T> { + readonly error?: boolean; readonly result: T; - readonly reasons: (string | number)[][]; + readonly reasons: [string, ...any[]][]; } +export type UserAttributeValue = string | number | boolean | null | undefined | ExperimentBucketMap; + export type UserAttributes = { - // TODO[OASIS-6649]: Don't use any type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [name: string]: any; -} + $opt_bucketing_id?: string; + $opt_experiment_bucket_map?: ExperimentBucketMap; + [name: string]: UserAttributeValue; +}; export interface ExperimentBucketMap { - [experiment_id: string]: - { variation_id: string } + [experiment_id: string]: { variation_id: string }; } // Information about past bucketing decisions for a user. @@ -55,7 +93,10 @@ export interface UserProfile { } export type EventTags = { - [key: string]: string | number | null; + revenue?: string | number | null; + value?: string | number | null; + $opt_event_properties?: Record<string, unknown>; + [key: string]: unknown; }; export interface UserProfileService { @@ -63,8 +104,13 @@ export interface UserProfileService { save(profile: UserProfile): void; } +export interface UserProfileServiceAsync { + lookup(userId: string): Promise<UserProfile>; + save(profile: UserProfile): Promise<void>; +} + export interface DatafileManagerConfig { - sdkKey: string, + sdkKey: string; datafile?: string; } @@ -80,19 +126,6 @@ export interface ListenerPayload { attributes?: UserAttributes; } -export type NotificationListener<T extends ListenerPayload> = (notificationData: T) => void; - -// NotificationCenter-related types -export interface NotificationCenter { - addNotificationListener<T extends ListenerPayload>( - notificationType: string, - callback: NotificationListener<T> - ): number; - removeNotificationListener(listenerId: number): boolean; - clearAllNotificationListeners(): void; - clearNotificationListeners(notificationType: NOTIFICATION_TYPES): void; -} - // An event to be submitted to Optimizely, enabling tracking the reach and impact of // tests and feature rollouts. export interface Event { @@ -106,17 +139,6 @@ export interface Event { params: any; } -export interface EventDispatcher { - /** - * @param event - * Event being submitted for eventual dispatch. - * @param callback - * After the event has at least been queued for dispatch, call this function to return - * control back to the Client. - */ - dispatchEvent: (event: Event, callback: (response: { statusCode: number; }) => void) => void; -} - export interface VariationVariable { id: string; value: string; @@ -130,18 +152,43 @@ export interface Variation { variables?: VariationVariable[]; } -export interface Experiment { +export interface ExperimentCore { id: string; key: string; variations: Variation[]; variationKeyMap: { [key: string]: Variation }; - groupId?: string; - layerId: string; - status: string; audienceConditions: Array<string | string[]>; audienceIds: string[]; trafficAllocation: TrafficAllocation[]; +} + +export interface Experiment extends ExperimentCore { + layerId: string; + groupId?: string; + status: string; forcedVariations?: { [key: string]: string }; + isRollout?: boolean; + cmab?: { + trafficAllocation: number; + attributeIds: string[]; + }; +} + +export type HoldoutStatus = 'Draft' | 'Running' | 'Concluded' | 'Archived'; + +export interface Holdout extends ExperimentCore { + status: HoldoutStatus; + includedFlags: string[]; + excludedFlags: string[]; +} + +export function isHoldout(obj: Experiment | Holdout): obj is Holdout { + // Holdout has 'status', 'includedFlags', and 'excludedFlags' properties + return ( + (obj as Holdout).status !== undefined && + Array.isArray((obj as Holdout).includedFlags) && + Array.isArray((obj as Holdout).excludedFlags) + ); } export enum VariableType { @@ -164,9 +211,9 @@ export interface FeatureFlag { rolloutId: string; key: string; id: string; - experimentIds: string[], - variables: FeatureVariable[], - variableKeyMap: { [key: string]: FeatureVariable } + experimentIds: string[]; + variables: FeatureVariable[]; + variableKeyMap: { [key: string]: FeatureVariable }; groupId?: string; } @@ -175,7 +222,7 @@ export type Condition = { type: string; match?: string; value: string | number | boolean | null; -} +}; export interface Audience { id: string; @@ -187,6 +234,7 @@ export interface Integration { key: string; host?: string; publicKey?: string; + pixelUrl?: string; } export interface TrafficAllocation { @@ -214,7 +262,7 @@ export interface Group { } export interface FeatureKeyMap { - [key: string]: FeatureFlag + [key: string]: FeatureFlag; } export interface OnReadyResult { @@ -224,7 +272,7 @@ export interface OnReadyResult { export type ObjectWithUnknownProperties = { [key: string]: unknown; -} +}; export interface Rollout { id: string; @@ -237,31 +285,10 @@ export enum OptimizelyDecideOption { ENABLED_FLAGS_ONLY = 'ENABLED_FLAGS_ONLY', IGNORE_USER_PROFILE_SERVICE = 'IGNORE_USER_PROFILE_SERVICE', INCLUDE_REASONS = 'INCLUDE_REASONS', - EXCLUDE_VARIABLES = 'EXCLUDE_VARIABLES' -} - -/** - * options required to create optimizely object - */ -export interface OptimizelyOptions { - UNSTABLE_conditionEvaluators?: unknown; - clientEngine: string; - clientVersion?: string; - // TODO[OASIS-6649]: Don't use object type - // eslint-disable-next-line @typescript-eslint/ban-types - datafile?: string | object; - datafileManager?: DatafileManager; - errorHandler: ErrorHandler; - eventProcessor: EventProcessor; - isValidInstance: boolean; - jsonSchemaValidator?: { - validate(jsonObject: unknown): boolean, - }; - logger: LoggerFacade; - sdkKey?: string; - userProfileService?: UserProfileService | null; - defaultDecideOptions?: OptimizelyDecideOption[]; - notificationCenter: NotificationCenterImpl; + EXCLUDE_VARIABLES = 'EXCLUDE_VARIABLES', + IGNORE_CMAB_CACHE = 'IGNORE_CMAB_CACHE', + RESET_CMAB_CACHE = 'RESET_CMAB_CACHE', + INVALIDATE_USER_CMAB_CACHE = 'INVALIDATE_USER_CMAB_CACHE', } /** @@ -276,6 +303,8 @@ export interface OptimizelyExperiment { }; } +export type FeatureVariableValue = number | string | boolean | object | null; + export interface OptimizelyVariable { id: string; key: string; @@ -284,44 +313,23 @@ export interface OptimizelyVariable { } export interface Client { + // TODO: In the future, will add a function to allow overriding the VUID. + getVuid(): string | undefined; + createUserContext(userId?: string, attributes?: UserAttributes): OptimizelyUserContext; notificationCenter: NotificationCenter; - createUserContext( - userId: string, - attributes?: UserAttributes - ): OptimizelyUserContext | null; - activate( - experimentKey: string, - userId: string, - attributes?: UserAttributes - ): string | null; - track( - eventKey: string, - userId: string, - attributes?: UserAttributes, - eventTags?: EventTags - ): void; - getVariation( - experimentKey: string, - userId: string, - attributes?: UserAttributes - ): string | null; + activate(experimentKey: string, userId: string, attributes?: UserAttributes): string | null; + track(eventKey: string, userId: string, attributes?: UserAttributes, eventTags?: EventTags): void; + getVariation(experimentKey: string, userId: string, attributes?: UserAttributes): string | null; setForcedVariation(experimentKey: string, userId: string, variationKey: string | null): boolean; getForcedVariation(experimentKey: string, userId: string): string | null; - isFeatureEnabled( - featureKey: string, - userId: string, - attributes?: UserAttributes - ): boolean; - getEnabledFeatures( - userId: string, - attributes?: UserAttributes - ): string[]; + isFeatureEnabled(featureKey: string, userId: string, attributes?: UserAttributes): boolean; + getEnabledFeatures(userId: string, attributes?: UserAttributes): string[]; getFeatureVariable( featureKey: string, variableKey: string, userId: string, attributes?: UserAttributes - ): unknown; + ): FeatureVariableValue; getFeatureVariableBoolean( featureKey: string, variableKey: string, @@ -346,91 +354,69 @@ export interface Client { userId: string, attributes?: UserAttributes ): string | null; - getFeatureVariableJSON( - featureKey: string, - variableKey: string, - userId: string, - attributes?: UserAttributes - ): unknown; + getFeatureVariableJSON(featureKey: string, variableKey: string, userId: string, attributes?: UserAttributes): unknown; getAllFeatureVariables( featureKey: string, userId: string, attributes?: UserAttributes ): { [variableKey: string]: unknown } | null; getOptimizelyConfig(): OptimizelyConfig | null; - onReady(options?: { timeout?: number }): Promise<{ success: boolean; reason?: string }>; - close(): Promise<{ success: boolean; reason?: string }>; + onReady(options?: { timeout?: number }): Promise<unknown>; + close(): Promise<unknown>; + sendOdpEvent(action: string, type?: string, identifiers?: Map<string, string>, data?: Map<string, unknown>): void; + isOdpIntegrated(): boolean; } -export interface ActivateListenerPayload extends ListenerPayload { - experiment: import('./shared_types').Experiment; - variation: import('./shared_types').Variation; - logEvent: Event; +export interface ActivateListenerPayload { + [key: string]: any; } -export interface TrackListenerPayload extends ListenerPayload { - eventKey: string; - eventTags: EventTags; - logEvent: Event; +export interface TrackListenerPayload { + [key: string]: any; } /** * Entry level Config Entities * For compatibility with the previous declaration file */ -export interface Config extends ConfigLite { - // options for Datafile Manager - datafileOptions?: DatafileOptions; - // limit of events to dispatch in a batch - eventBatchSize?: number; - // maximum time for an event to stay in the queue - eventFlushInterval?: number; - // maximum size for the event queue - eventMaxQueueSize?: number; - // sdk key - sdkKey?: string; -} - -/** - * Entry level Config Entities for Lite bundle - * For compatibility with the previous declaration file - */ -export interface ConfigLite { - // Datafile string - // TODO[OASIS-6649]: Don't use object type - // eslint-disable-next-line @typescript-eslint/ban-types - datafile?: object | string; - // errorHandler object for logging error - errorHandler?: ErrorHandler; - // event dispatcher function - eventDispatcher?: EventDispatcher; +export interface Config { + projectConfigManager: OpaqueConfigManager; + eventProcessor?: OpaqueEventProcessor; // The object to validate against the schema jsonSchemaValidator?: { - validate(jsonObject: unknown): boolean, + validate(jsonObject: unknown): boolean; }; - // level of logging i.e debug, info, error, warning etc - logLevel?: LogLevel | string; - // LogHandler object for logging - logger?: LogHandler; + logger?: OpaqueLogger; + errorNotifier?: OpaqueErrorNotifier; // user profile that contains user information userProfileService?: UserProfileService; + userProfileServiceAsync?: UserProfileServiceAsync; // dafault options for decide API defaultDecideOptions?: OptimizelyDecideOption[]; clientEngine?: string; clientVersion?: string; + odpManager?: OpaqueOdpManager; + vuidManager?: OpaqueVuidManager; + disposable?: boolean; + cmab?: { + cacheSize?: number; + cacheTtl?: number; + cache?: CacheWithRemove<string>; + predictionEndpointTemplate?: string; + } } export type OptimizelyExperimentsMap = { [experimentKey: string]: OptimizelyExperiment; -} +}; export type OptimizelyVariablesMap = { [variableKey: string]: OptimizelyVariable; -} +}; export type OptimizelyFeaturesMap = { [featureKey: string]: OptimizelyFeature; -} +}; export type OptimizelyAttribute = { id: string; @@ -446,7 +432,7 @@ export type OptimizelyAudience = { export type OptimizelyEvent = { id: string; key: string; - experimentsIds: string[]; + experimentIds: string[]; }; export interface OptimizelyFeature { @@ -489,28 +475,7 @@ export interface OptimizelyConfig { getDatafile(): string; } -export interface OptimizelyUserContext { - getUserId(): string; - getAttributes(): UserAttributes; - setAttribute(key: string, value: unknown): void; - decide( - key: string, - options?: OptimizelyDecideOption[] - ): OptimizelyDecision; - decideForKeys( - keys: string[], - options?: OptimizelyDecideOption[], - ): { [key: string]: OptimizelyDecision }; - decideAll( - options?: OptimizelyDecideOption[], - ): { [key: string]: OptimizelyDecision }; - trackEvent(eventName: string, eventTags?: EventTags): void; - setForcedDecision(context: OptimizelyDecisionContext, decision: OptimizelyForcedDecision): boolean; - getForcedDecision(context: OptimizelyDecisionContext): OptimizelyForcedDecision | null; - removeForcedDecision(context: OptimizelyDecisionContext): boolean; - removeAllForcedDecisions(): boolean; - isQualifiedFor(segment: string): boolean; -} +export { OptimizelyUserContext }; export interface OptimizelyDecision { variationKey: string | null; @@ -557,3 +522,15 @@ export interface OptimizelyDecisionContext { export interface OptimizelyForcedDecision { variationKey: string; } + +// ODP Exports + +export { + RequestHandler, + OptimizelySegmentOption, + OdpSegmentApiManager, + OdpSegmentManager, + DefaultOdpEventApiManager, + OdpEventManager, + OdpManager, +}; diff --git a/lib/tests/decision_test_datafile.ts b/lib/tests/decision_test_datafile.ts new file mode 100644 index 000000000..5048d2549 --- /dev/null +++ b/lib/tests/decision_test_datafile.ts @@ -0,0 +1,553 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// flag id starts from 1000 +// experiment id starts from 2000 +// rollout experiment id starts from 3000 +// audience id starts from 4000 +// variation id starts from 5000 +// variable id starts from 6000 +// attribute id starts from 7000 + +const testDatafile = { + accountId: "24535200037", + projectId: "5088239376138240", + revision: "21", + attributes: [ + { + id: "7001", + key: "age" + } + ], + audiences: [ + { + name: "age_22", + conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", + id: "4001" + }, + { + name: "age_60", + conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", + id: "4002" + }, + { + name: "age_90", + conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", + id: "4003" + }, + { + name: "age_94", + conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", + id: "4004" + }, + { + name: "age_95", + conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", + id: "4005" + }, + { + name: "age_96", + conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", + id: "4006" + }, + { + name: "age_97", + conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", + id: "4007" + }, + { + id: "$opt_dummy_audience", + name: "Optimizely-Generated Audience for Backwards Compatibility", + conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]" + } + ], + version: "4", + events: [], + integrations: [], + anonymizeIP: true, + botFiltering: false, + typedAudiences: [ + { + name: "age_22", + conditions: [ + "and", + [ + "or", + [ + "or", + { + "match": "le", + name: "age", + "type": "custom_attribute", + value: 22 + } + ] + ] + ], + id: "4001" + }, + { + name: "age_60", + conditions: [ + "and", + [ + "or", + [ + "or", + { + "match": "le", + name: "age", + "type": "custom_attribute", + value: 60 + } + ] + ] + ], + id: "4002" + }, + { + name: "age_90", + conditions: [ + "and", + [ + "or", + [ + "or", + { + "match": "le", + name: "age", + "type": "custom_attribute", + value: 90 + } + ] + ] + ], + id: "4003" + }, + { + name: "age_94", + conditions: [ + "and", + [ + "or", + [ + "or", + { + "match": "le", + name: "age", + "type": "custom_attribute", + value: 94 + } + ] + ] + ], + id: "4004" + }, + { + name: "age_95", + conditions: [ + "and", + [ + "or", + [ + "or", + { + "match": "le", + name: "age", + "type": "custom_attribute", + value: 95 + } + ] + ] + ], + id: "4005" + }, + { + name: "age_96", + conditions: [ + "and", + [ + "or", + [ + "or", + { + "match": "le", + name: "age", + "type": "custom_attribute", + value: 96 + } + ] + ] + ], + id: "4006" + }, + ], + variables: [], + environmentKey: "production", + sdkKey: "sdk_key", + featureFlags: [ + { + id: "1001", + key: "flag_1", + rolloutId: "rollout-371334-671741182375276", + experimentIds: [ + "2001", + "2002", + "2003" + ], + variables: [ + { + id: "6001", + key: "integer_variable", + "type": "integer", + "defaultValue": "0" + } + ] + }, + { + id: "1002", + key: "flag_2", + "rolloutId": "rollout-374517-931741182375293", + experimentIds: [ + "2004" + ], + "variables": [] + } + ], + "rollouts": [ + { + id: "rollout-371334-671741182375276", + experiments: [ + { + id: "3001", + key: "delivery_1", + status: "Running", + layerId: "9300001480454", + variations: [ + { + id: "5004", + key: "variation_4", + featureEnabled: true, + variables: [ + { + id: "6001", + value: "4" + } + ] + } + ], + trafficAllocation: [ + { + entityId: "5004", + endOfRange: 1500 + } + ], + forcedVariations: { + + }, + audienceIds: [ + "4001" + ], + audienceConditions: [ + "or", + "4001" + ] + }, + { + id: "3002", + key: "delivery_2", + status: "Running", + layerId: "9300001480455", + variations: [ + { + id: "5005", + key: "variation_5", + featureEnabled: true, + variables: [ + { + id: "6001", + value: "5" + } + ] + } + ], + trafficAllocation: [ + { + entityId: "5005", + endOfRange: 4000 + } + ], + forcedVariations: { + + }, + audienceIds: [ + "4002" + ], + audienceConditions: [ + "or", + "4002" + ] + }, + { + id: "3003", + key: "delivery_3", + status: "Running", + layerId: "9300001495996", + variations: [ + { + id: "5006", + key: "variation_6", + featureEnabled: true, + variables: [ + { + id: "6001", + value: "6" + } + ] + } + ], + trafficAllocation: [ + { + entityId: "5006", + endOfRange: 8000 + } + ], + forcedVariations: { + + }, + audienceIds: [ + "4003" + ], + audienceConditions: [ + "or", + "4003" + ] + }, + { + id: "default-rollout-id", + key: "default-rollout-key", + status: "Running", + layerId: "rollout-371334-671741182375276", + variations: [ + { + id: "5007", + key: "variation_7", + featureEnabled: true, + variables: [ + { + id: "6001", + value: "7" + } + ] + } + ], + trafficAllocation: [ + { + entityId: "5007", + endOfRange: 10000 + } + ], + forcedVariations: { + + }, + audienceIds: [], + audienceConditions: [] + }, + ] + }, + { + id: "rollout-374517-931741182375293", + experiments: [ + { + id: "default-rollout-374517-931741182375293", + key: "default-rollout-374517-931741182375293", + status: "Running", + layerId: "rollout-374517-931741182375293", + variations: [ + { + id: "1177722", + key: "off", + featureEnabled: false, + variables: [] + } + ], + trafficAllocation: [ + { + "entityId": "1177722", + "endOfRange": 10000 + } + ], + forcedVariations: { + + }, + audienceIds: [], + audienceConditions: [] + } + ] + }, + ], + experiments: [ + { + id: "2001", + key: "exp_1", + status: "Running", + layerId: "9300001480444", + variations: [ + { + id: "5001", + key: "variation_1", + featureEnabled: true, + variables: [ + { + id: "6001", + value: "1" + } + ] + } + ], + trafficAllocation: [ + { + entityId: "5001", + endOfRange: 5000 + }, + { + entityId: "5001", + endOfRange: 10000 + } + ], + forcedVariations: { + + }, + audienceIds: [ + "4001" + ], + audienceConditions: [ + "or", + "4001" + ] + }, + { + id: "2002", + key: "exp_2", + status: "Running", + layerId: "9300001480448", + variations: [ + { + id: "5002", + key: "variation_2", + featureEnabled: true, + variables: [ + { + id: "6001", + value: "2" + } + ] + } + ], + trafficAllocation: [ + { + entityId: "5002", + endOfRange: 5000 + }, + { + entityId: "5002", + endOfRange: 10000 + } + ], + forcedVariations: { + + }, + audienceConditions: [ + "or", + "4002" + ] + }, + { + id: "2003", + key: "exp_3", + status: "Running", + layerId: "9300001480451", + variations: [ + { + id: "5003", + key: "variation_3", + featureEnabled: true, + variables: [ + { + id: "6001", + value: "3" + } + ] + } + ], + trafficAllocation: [ + { + entityId: "5003", + endOfRange: 5000 + }, + { + entityId: "5003", + endOfRange: 10000 + } + ], + forcedVariations: { + + }, + audienceIds: [], + audienceConditions: [ + "or", + "4003" + ], + cmab: { + attributes: ["7001"], + } + }, + { + id: "2004", + key: "exp_4", + status: "Running", + layerId: "9300001497754", + variations: [ + { + id: "5100", + key: "variation_flag_2", + featureEnabled: true, + variables: [] + } + ], + trafficAllocation: [ + { + entityId: "5100", + endOfRange: 5000 + }, + { + entityId: "5100", + endOfRange: 10000 + } + ], + forcedVariations: { + + }, + audienceIds: [], + audienceConditions: [] + } + ], + groups: [] +} + +export const getDecisionTestDatafile = (): any => { + return JSON.parse(JSON.stringify(testDatafile)); +} diff --git a/packages/optimizely-sdk/lib/tests/exit_on_unhandled_rejection.js b/lib/tests/exit_on_unhandled_rejection.js similarity index 100% rename from packages/optimizely-sdk/lib/tests/exit_on_unhandled_rejection.js rename to lib/tests/exit_on_unhandled_rejection.js diff --git a/lib/tests/mock/create_event.ts b/lib/tests/mock/create_event.ts new file mode 100644 index 000000000..ec5dd9949 --- /dev/null +++ b/lib/tests/mock/create_event.ts @@ -0,0 +1,57 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export function createImpressionEvent(id = 'uuid'): any { + return { + type: 'impression' as const, + timestamp: 69, + uuid: id, + + context: { + accountId: 'accountId', + projectId: 'projectId', + clientName: 'node-sdk', + clientVersion: '3.0.0', + revision: '1', + botFiltering: true, + anonymizeIP: true, + }, + + user: { + id: 'userId', + attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], + }, + + layer: { + id: 'layerId', + }, + + experiment: { + id: 'expId', + key: 'expKey', + }, + + variation: { + id: 'varId', + key: 'varKey', + }, + + ruleKey: 'expKey', + flagKey: 'flagKey1', + ruleType: 'experiment', + enabled: true, + } +} \ No newline at end of file diff --git a/lib/tests/mock/mock_cache.ts b/lib/tests/mock/mock_cache.ts new file mode 100644 index 000000000..21a89e7a4 --- /dev/null +++ b/lib/tests/mock/mock_cache.ts @@ -0,0 +1,124 @@ +/** + * Copyright 2022-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { SyncCache, AsyncCache } from "../../utils/cache/cache"; +import { SyncStore, AsyncStore } from "../../utils/cache/store"; +import { Maybe } from "../../utils/type"; + +type SyncCacheWithAddOn<T> = SyncCache<T> & { + size(): number; + getAll(): Map<string, T>; +}; + +type AsyncCacheWithAddOn<T> = AsyncCache<T> & { + size(): Promise<number>; + getAll(): Promise<Map<string, T>>; +}; + +type SyncStoreWithAddOn<T> = SyncStore<T> & { + size(): number; + getAll(): Map<string, T>; +}; + +type AsyncStoreWithAddOn<T> = AsyncStore<T> & { + size(): Promise<number>; + getAll(): Promise<Map<string, T>>; +}; + +export const getMockSyncCache = <T>(): SyncCacheWithAddOn<T> & SyncStoreWithAddOn<T> => { + const cache = { + operation: 'sync' as const, + data: new Map<string, T>(), + remove(key: string): void { + this.data.delete(key); + }, + clear(): void { + this.data.clear(); + }, + reset(): void { + this.clear(); + }, + getKeys(): string[] { + return Array.from(this.data.keys()); + }, + getAll(): Map<string, T> { + return this.data; + }, + getBatched(keys: string[]): Maybe<T>[] { + return keys.map((key) => this.get(key)); + }, + size(): number { + return this.data.size; + }, + get(key: string): T | undefined { + return this.data.get(key); + }, + lookup(key: string): T | undefined { + return this.get(key); + }, + set(key: string, value: T): void { + this.data.set(key, value); + }, + save(key: string, value: T): void { + this.data.set(key, value); + } + } + + return cache; +}; + + +export const getMockAsyncCache = <T>(): AsyncCacheWithAddOn<T> & AsyncStoreWithAddOn<T> => { + const cache = { + operation: 'async' as const, + data: new Map<string, T>(), + async remove(key: string): Promise<void> { + this.data.delete(key); + }, + async clear(): Promise<void> { + this.data.clear(); + }, + async reset(): Promise<void> { + this.clear(); + }, + async getKeys(): Promise<string[]> { + return Array.from(this.data.keys()); + }, + async getAll(): Promise<Map<string, T>> { + return this.data; + }, + async getBatched(keys: string[]): Promise<Maybe<T>[]> { + return Promise.all(keys.map((key) => this.get(key))); + }, + async size(): Promise<number> { + return this.data.size; + }, + async get(key: string): Promise<Maybe<T>> { + return this.data.get(key); + }, + async lookup(key: string): Promise<Maybe<T>> { + return this.get(key); + }, + async set(key: string, value: T): Promise<void> { + this.data.set(key, value); + }, + async save(key: string, value: T): Promise<void> { + return this.set(key, value); + } + } + + return cache; +}; diff --git a/lib/tests/mock/mock_datafile_manager.ts b/lib/tests/mock/mock_datafile_manager.ts new file mode 100644 index 000000000..1c9d66b38 --- /dev/null +++ b/lib/tests/mock/mock_datafile_manager.ts @@ -0,0 +1,77 @@ +/** + * Copyright 2024 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Consumer } from '../../utils/type'; +import { DatafileManager } from '../../project_config/datafile_manager'; +import { EventEmitter } from '../../utils/event_emitter/event_emitter'; +import { BaseService } from '../../service'; +import { LoggerFacade } from '../../logging/logger'; + +type MockConfig = { + datafile?: string | object; + onRunning?: Promise<void>, + onTerminated?: Promise<void>, +} + +class MockDatafileManager extends BaseService implements DatafileManager { + eventEmitter: EventEmitter<{ update: string}> = new EventEmitter(); + datafile: string | object | undefined; + + constructor(opt: MockConfig) { + super(); + this.datafile = opt.datafile; + this.startPromise.resolve(opt.onRunning || Promise.resolve()); + this.stopPromise.resolve(opt.onTerminated || Promise.resolve()); + } + + start(): void { + return; + } + + stop(): void { + return; + } + + setLogger(logger: LoggerFacade): void { + } + + get(): string | undefined { + if (typeof this.datafile === 'object') { + return JSON.stringify(this.datafile); + } + return this.datafile; + } + + setDatafile(datafile: string): void { + this.datafile = datafile; + } + + onUpdate(listener: Consumer<string>): () => void { + return this.eventEmitter.on('update', listener) + } + + pushUpdate(datafile: string | object): void { + if (typeof datafile === 'object') { + datafile = JSON.stringify(datafile); + } + this.datafile = datafile; + this.eventEmitter.emit('update', datafile); + } +} + +export const getMockDatafileManager = (opt: MockConfig): MockDatafileManager => { + return new MockDatafileManager(opt); +}; diff --git a/packages/optimizely-sdk/tests/testUtils.ts b/lib/tests/mock/mock_logger.ts similarity index 50% rename from packages/optimizely-sdk/tests/testUtils.ts rename to lib/tests/mock/mock_logger.ts index c828089fc..e9d9cb4cf 100644 --- a/packages/optimizely-sdk/tests/testUtils.ts +++ b/lib/tests/mock/mock_logger.ts @@ -1,11 +1,11 @@ /** - * Copyright 2022, Optimizely + * Copyright 2024 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,14 +14,26 @@ * limitations under the License. */ -export function advanceTimersByTime(waitMs: number): Promise<void> { - const timeoutPromise: Promise<void> = new Promise(res => setTimeout(res, waitMs)); - jest.advanceTimersByTime(waitMs); - return timeoutPromise; -} +import { vi } from 'vitest'; +import { LoggerFacade } from '../../logging/logger'; -export function getTimerCount(): number { - // Type definition for jest doesn't include this, but it exists - // https://jestjs.io/docs/en/jest-object#jestgettimercount - return (jest as any).getTimerCount(); -} +type MockFn = ReturnType<typeof vi.fn>; +type MockLogger = { + info: MockFn; + error: MockFn; + warn: MockFn; + debug: MockFn; + child: MockFn; + setName: MockFn; +}; + +export const getMockLogger = (): MockLogger => { + return { + info: vi.fn(), + error: vi.fn(), + warn: vi.fn(), + debug: vi.fn(), + child: vi.fn().mockImplementation(() => getMockLogger()), + setName: vi.fn(), + }; +}; diff --git a/lib/tests/mock/mock_project_config_manager.ts b/lib/tests/mock/mock_project_config_manager.ts new file mode 100644 index 000000000..931f37da1 --- /dev/null +++ b/lib/tests/mock/mock_project_config_manager.ts @@ -0,0 +1,57 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ProjectConfigManager } from '../../project_config/project_config_manager'; +import { ProjectConfig } from '../../project_config/project_config'; +import { Consumer } from '../../utils/type'; + +type MockOpt = { + initConfig?: ProjectConfig, + onRunning?: Promise<void>, + onTerminated?: Promise<void>, +} + +export const getMockProjectConfigManager = (opt: MockOpt = {}): ProjectConfigManager => { + return { + disposable: false, + config: opt.initConfig, + makeDisposable(){ + this.disposable = true; + }, + start: () => {}, + onRunning: () => opt.onRunning || Promise.resolve(), + stop: () => {}, + onTerminated: () => opt.onTerminated || Promise.resolve(), + getConfig: function() { + return this.config; + }, + setConfig: function(config: ProjectConfig) { + this.config = config; + }, + onUpdate: function(listener: Consumer<ProjectConfig>) { + if (this.listeners === undefined) { + this.listeners = []; + } + this.listeners.push(listener); + return () => {}; + }, + pushUpdate: function(config: ProjectConfig) { + this.listeners.forEach((listener: any) => listener(config)); + }, + setLogger: function(logger: any) { + } + } as any as ProjectConfigManager; +}; diff --git a/lib/tests/mock/mock_repeater.ts b/lib/tests/mock/mock_repeater.ts new file mode 100644 index 000000000..f70b0b477 --- /dev/null +++ b/lib/tests/mock/mock_repeater.ts @@ -0,0 +1,44 @@ +/** + * Copyright 2024 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { vi } from 'vitest'; +import { AsyncTransformer } from '../../utils/type'; + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const getMockRepeater = () => { + const mock = { + running: false, + handler: undefined as any, + start: vi.fn(), + stop: vi.fn(), + reset: vi.fn(), + setTask(handler: AsyncTransformer<number, void>) { + this.handler = handler; + }, + // throw if not running. This ensures tests cannot + // do mock exection when the repeater is supposed to be not running. + execute(failureCount: number): Promise<void> { + if (!this.isRunning()) throw new Error(); + const ret = this.handler?.(failureCount); + ret?.catch(() => {}); + return ret; + }, + isRunning: () => mock.running, + }; + mock.start.mockImplementation(() => mock.running = true); + mock.stop.mockImplementation(() => mock.running = false); + return mock; +} diff --git a/lib/tests/mock/mock_request_handler.ts b/lib/tests/mock/mock_request_handler.ts new file mode 100644 index 000000000..3369bf125 --- /dev/null +++ b/lib/tests/mock/mock_request_handler.ts @@ -0,0 +1,43 @@ +/** + * Copyright 2024 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { vi } from 'vitest'; +import { AbortableRequest, Response } from '../../utils/http_request_handler/http'; +import { ResolvablePromise, resolvablePromise } from '../../utils/promise/resolvablePromise'; + + +export type MockAbortableRequest = AbortableRequest & { + mockResponse: ResolvablePromise<Response>; +}; + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const getMockAbortableRequest = (res?: Promise<Response>) => { + const response = resolvablePromise<Response>(); + if (res) response.resolve(res); + return { + mockResponse: response, + responsePromise: response.promise, + abort: vi.fn(), + }; +}; + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const getMockRequestHandler = () => { + const mock = { + makeRequest: vi.fn(), + } + return mock; +} diff --git a/packages/datafile-manager/__test__/testUtils.ts b/lib/tests/testUtils.ts similarity index 58% rename from packages/datafile-manager/__test__/testUtils.ts rename to lib/tests/testUtils.ts index 511f1e515..2a4cbe3c5 100644 --- a/packages/datafile-manager/__test__/testUtils.ts +++ b/lib/tests/testUtils.ts @@ -1,11 +1,11 @@ /** - * Copyright 2019-2020, Optimizely + * Copyright 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,15 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { vi } from 'vitest'; -export function advanceTimersByTime(waitMs: number): Promise<void> { +export const exhaustMicrotasks = async (loop = 100): Promise<void> => { + for(let i = 0; i < loop; i++) { + await Promise.resolve(); + } +}; + +export const wait = (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms)); + +export const advanceTimersByTime = (waitMs: number): Promise<void> => { const timeoutPromise: Promise<void> = new Promise(res => setTimeout(res, waitMs)); - jest.advanceTimersByTime(waitMs); + vi.advanceTimersByTime(waitMs); return timeoutPromise; } - -export function getTimerCount(): number { - // Type definition for jest doesn't include this, but it exists - // https://jestjs.io/docs/en/jest-object#jestgettimercount - return (jest as any).getTimerCount(); -} diff --git a/packages/optimizely-sdk/lib/tests/test_data.js b/lib/tests/test_data.ts similarity index 80% rename from packages/optimizely-sdk/lib/tests/test_data.js rename to lib/tests/test_data.ts index 849cc5e86..e16081939 100644 --- a/packages/optimizely-sdk/lib/tests/test_data.js +++ b/lib/tests/test_data.ts @@ -1,5 +1,5 @@ /** - * Copyright 2016-2021, Optimizely + * Copyright 2016-2021, 2024-2025 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,9 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import cloneDeep from 'lodash/cloneDeep'; -var config = { +/* eslint-disable */ +const cloneDeep = (x: any) => JSON.parse(JSON.stringify(x)); + +const config: any = { revision: '42', version: '2', events: [ @@ -343,9 +345,7 @@ var decideConfig = { { experiments: [ { - audienceIds: [ - '13389130056' - ], + audienceIds: ['13389130056'], forcedVariations: {}, id: '3332020515', key: '3332020515', @@ -354,22 +354,20 @@ var decideConfig = { trafficAllocation: [ { endOfRange: 10000, - entityId: '3324490633' - } + entityId: '3324490633', + }, ], variations: [ { featureEnabled: true, id: '3324490633', key: '3324490633', - variables: [] - } - ] + variables: [], + }, + ], }, { - audienceIds: [ - '12208130097' - ], + audienceIds: ['12208130097'], forcedVariations: {}, id: '3332020494', key: '3332020494', @@ -378,17 +376,17 @@ var decideConfig = { trafficAllocation: [ { endOfRange: 0, - entityId: '3324490562' - } + entityId: '3324490562', + }, ], variations: [ { featureEnabled: true, id: '3324490562', key: '3324490562', - variables: [] - } - ] + variables: [], + }, + ], }, { status: 'Running', @@ -398,8 +396,8 @@ var decideConfig = { variables: [], id: '18257766532', key: '18257766532', - featureEnabled: true - } + featureEnabled: true, + }, ], id: '18322080788', key: '18322080788', @@ -407,14 +405,14 @@ var decideConfig = { trafficAllocation: [ { entityId: '18257766532', - endOfRange: 10000 - } + endOfRange: 10000, + }, ], - forcedVariations: {} - } + forcedVariations: {}, + }, ], - id: '3319450668' - } + id: '3319450668', + }, ], anonymizeIP: true, botFiltering: true, @@ -424,9 +422,7 @@ var decideConfig = { variables: [], featureFlags: [ { - experimentIds: [ - '10390977673' - ], + experimentIds: ['10390977673'], id: '4482920077', key: 'feature_1', rolloutId: '3319450668', @@ -435,48 +431,46 @@ var decideConfig = { defaultValue: '42', id: '2687470095', key: 'i_42', - type: 'integer' + type: 'integer', }, { defaultValue: '4.2', id: '2689280165', key: 'd_4_2', - type: 'double' + type: 'double', }, { defaultValue: 'true', id: '2689660112', key: 'b_true', - type: 'boolean' + type: 'boolean', }, { defaultValue: 'foo', id: '2696150066', key: 's_foo', - type: 'string' + type: 'string', }, { defaultValue: { - value: 1 + value: 1, }, id: '2696150067', key: 'j_1', type: 'string', - subType: 'json' + subType: 'json', }, { defaultValue: 'invalid', id: '2696150068', key: 'i_1', type: 'invalid', - subType: '' - } - ] + subType: '', + }, + ], }, { - experimentIds: [ - '10420810910' - ], + experimentIds: ['10420810910'], id: '4482920078', key: 'feature_2', rolloutId: '', @@ -485,17 +479,17 @@ var decideConfig = { defaultValue: '42', id: '2687470095', key: 'i_42', - type: 'integer' - } - ] + type: 'integer', + }, + ], }, { experimentIds: [], id: '44829230000', key: 'feature_3', rolloutId: '', - variables: [] - } + variables: [], + }, ], experiments: [ { @@ -505,27 +499,25 @@ var decideConfig = { trafficAllocation: [ { entityId: '10389729780', - endOfRange: 10000 - } - ], - audienceIds: [ - '13389141123' + endOfRange: 10000, + }, ], + audienceIds: ['13389141123'], variations: [ { variables: [], featureEnabled: true, id: '10389729780', - key: 'a' + key: 'a', }, { variables: [], id: '10416523121', - key: 'b' - } + key: 'b', + }, ], forcedVariations: {}, - id: '10390977673' + id: '10390977673', }, { status: 'Running', @@ -534,8 +526,8 @@ var decideConfig = { trafficAllocation: [ { entityId: '10418551353', - endOfRange: 10000 - } + endOfRange: 10000, + }, ], audienceIds: [], variations: [ @@ -543,70 +535,73 @@ var decideConfig = { variables: [], featureEnabled: true, id: '10418551353', - key: 'variation_with_traffic' + key: 'variation_with_traffic', }, { variables: [], featureEnabled: false, id: '10418510624', - key: 'variation_no_traffic' - } + key: 'variation_no_traffic', + }, ], forcedVariations: {}, - id: '10420810910' - } + id: '10420810910', + }, ], audiences: [ { id: '13389141123', - conditions: '["and",["or",["or",{ "match": "exact", "name": "gender", "type": "custom_attribute", "value": "f"}]]]', - name: 'gender' + conditions: + '["and",["or",["or",{ "match": "exact", "name": "gender", "type": "custom_attribute", "value": "f"}]]]', + name: 'gender', }, { id: '13389130056', - conditions: '["and",["or",["or",{ "match": "exact","name": "country","type": "custom_attribute","value": "US"}]]]', - name: 'US' + conditions: + '["and",["or",["or",{ "match": "exact","name": "country","type": "custom_attribute","value": "US"}]]]', + name: 'US', }, { id: '12208130097', - conditions: '["and",["or",["or",{"match": "exact","name": "browser","type": "custom_attribute","value": "safari"}]]]', - name: 'safari' + conditions: + '["and",["or",["or",{"match": "exact","name": "browser","type": "custom_attribute","value": "safari"}]]]', + name: 'safari', }, { - id: "age_18", + id: 'age_18', conditions: '["and",["or",["or",{"match": "gt","name": "age","type": "custom_attribute","value": 18}]]]', - name: 'age_18' + name: 'age_18', }, { id: 'invalid_format', conditions: '[]', - name: 'invalid_format' + name: 'invalid_format', }, { id: 'invalid_condition', conditions: '["and",["or",["or",{"match": "gt","name": "age","type": "custom_attribute","value": "US"}]]]', - name: 'invalid_condition' + name: 'invalid_condition', }, { id: 'invalid_type', conditions: '["and",["or",["or",{"match": "gt","name": "age","type": "invalid","value": 18}]]]', - name: 'invalid_type' + name: 'invalid_type', }, { id: 'invalid_match', conditions: '["and",["or",["or",{"match": "invalid","name": "age","type": "custom_attribute","value": 18}]]]', - name: 'invalid_match' + name: 'invalid_match', }, { id: 'nil_value', conditions: '["and",["or",["or",{"match": "gt","name": "age","type": "custom_attribute"}]]]', - name: 'nil_value' + name: 'nil_value', }, { id: 'invalid_name', conditions: '["and",["or",["or",{"match": "gt","type": "custom_attribute","value": 18}]]]', - name: 'invalid_name' - } + name: 'invalid_name', + }, ], groups: [ { @@ -614,8 +609,8 @@ var decideConfig = { trafficAllocation: [ { entityId: '10390965532', - endOfRange: 10000 - } + endOfRange: 10000, + }, ], experiments: [ { @@ -625,8 +620,8 @@ var decideConfig = { trafficAllocation: [ { entityId: '10389752311', - endOfRange: 10000 - } + endOfRange: 10000, + }, ], audienceIds: [], variations: [ @@ -634,11 +629,11 @@ var decideConfig = { variables: [], featureEnabled: false, id: '10389752311', - key: 'a' - } + key: 'a', + }, ], forcedVariations: {}, - id: '10390965532' + id: '10390965532', }, { status: 'Running', @@ -647,8 +642,8 @@ var decideConfig = { trafficAllocation: [ { entityId: '10418524243', - endOfRange: 10000 - } + endOfRange: 10000, + }, ], audienceIds: [], variations: [ @@ -656,45 +651,40 @@ var decideConfig = { variables: [], featureEnabled: false, id: '10418524243', - key: 'a' - } + key: 'a', + }, ], forcedVariations: {}, - id: '10420843432' - } + id: '10420843432', + }, ], - id: '13142870430' - } + id: '13142870430', + }, ], attributes: [ { id: '10401066117', - key: 'gender' + key: 'gender', }, { id: '10401066170', - key: 'testvar' - } + key: 'testvar', + }, ], accountId: '10367498574', events: [ { - experimentIds: [ - '10420810910' - ], + experimentIds: ['10420810910'], id: '10404198134', - key: 'event1' + key: 'event1', }, { - experimentIds: [ - '10420810910', - '10390977673' - ], + experimentIds: ['10420810910', '10390977673'], id: '10404198135', - key: 'event_multiple_running_exp_attached' - } + key: 'event_multiple_running_exp_attached', + }, ], - revision: '241' + revision: '241', }; export var getParsedAudiences = [ @@ -705,11 +695,11 @@ export var getParsedAudiences = [ }, ]; -export var getTestProjectConfig = function () { +export var getTestProjectConfig = function() { return cloneDeep(config); }; -export var getTestDecideProjectConfig = function () { +export var getTestDecideProjectConfig = function() { return cloneDeep(decideConfig); }; @@ -1044,30 +1034,27 @@ var configWithFeatures = { key: 'test_experiment3', status: 'Running', layerId: '6', - audienceConditions: [ - "or", - "11160" - ], + audienceConditions: ['or', '11160'], audienceIds: ['11160'], id: '111134', forcedVariations: {}, trafficAllocation: [ { entityId: '222239', - endOfRange: 2500 + endOfRange: 2500, }, { entityId: '', - endOfRange: 5000 + endOfRange: 5000, }, { entityId: '', - endOfRange: 7500 + endOfRange: 7500, }, { entityId: '', - endOfRange: 10000 - } + endOfRange: 10000, + }, ], variations: [ { @@ -1075,33 +1062,30 @@ var configWithFeatures = { key: 'control', variables: [], featureEnabled: false, - } + }, ], }, { key: 'test_experiment4', status: 'Running', layerId: '7', - audienceConditions: [ - "or", - "11160" - ], + audienceConditions: ['or', '11160'], audienceIds: ['11160'], id: '111135', forcedVariations: {}, trafficAllocation: [ { entityId: '222240', - endOfRange: 5000 + endOfRange: 5000, }, { entityId: '', - endOfRange: 7500 + endOfRange: 7500, }, { entityId: '', - endOfRange: 10000 - } + endOfRange: 10000, + }, ], variations: [ { @@ -1109,29 +1093,26 @@ var configWithFeatures = { key: 'control', variables: [], featureEnabled: false, - } + }, ], }, { key: 'test_experiment5', status: 'Running', layerId: '8', - audienceConditions: [ - "or", - "11160" - ], + audienceConditions: ['or', '11160'], audienceIds: ['11160'], id: '111136', forcedVariations: {}, trafficAllocation: [ { entityId: '222241', - endOfRange: 7500 + endOfRange: 7500, }, { entityId: '', - endOfRange: 10000 - } + endOfRange: 10000, + }, ], variations: [ { @@ -1139,7 +1120,7 @@ var configWithFeatures = { key: 'control', variables: [], featureEnabled: false, - } + }, ], }, ], @@ -1159,7 +1140,7 @@ var configWithFeatures = { name: 'Test attribute users 3', conditions: '["and", ["or", ["or", {"match": "exact", "name": "experiment_attr", "type": "custom_attribute", "value": "group_experiment"}]]]', - } + }, ], revision: '35', groups: [ @@ -1315,10 +1296,7 @@ var configWithFeatures = { id: '42222', key: 'group_2_exp_1', status: 'Running', - audienceConditions: [ - "or", - "11160" - ], + audienceConditions: ['or', '11160'], audienceIds: ['11160'], layerId: '211183', variations: [ @@ -1332,25 +1310,22 @@ var configWithFeatures = { trafficAllocation: [ { entityId: '38901', - endOfRange: 10000 - } + endOfRange: 10000, + }, ], variationKeyMap: { var_1: { key: 'var_1', id: '38901', featureEnabled: false, - } - } + }, + }, }, { id: '42223', key: 'group_2_exp_2', status: 'Running', - audienceConditions: [ - "or", - "11160" - ], + audienceConditions: ['or', '11160'], audienceIds: ['11160'], layerId: '211184', variations: [ @@ -1364,18 +1339,15 @@ var configWithFeatures = { trafficAllocation: [ { entityId: '38905', - endOfRange: 10000 - } + endOfRange: 10000, + }, ], }, { id: '42224', key: 'group_2_exp_3', status: 'Running', - audienceConditions: [ - "or", - "11160" - ], + audienceConditions: ['or', '11160'], audienceIds: ['11160'], layerId: '211185', variations: [ @@ -1389,30 +1361,30 @@ var configWithFeatures = { trafficAllocation: [ { entityId: '38906', - endOfRange: 10000 - } + endOfRange: 10000, + }, ], - } + }, ], trafficAllocation: [ { entityId: '42222', - endOfRange: 2500 + endOfRange: 2500, }, { entityId: '42223', - endOfRange: 5000 + endOfRange: 5000, }, { entityId: '42224', - endOfRange: 7500 + endOfRange: 7500, }, { entityId: '', - endOfRange: 10000 + endOfRange: 10000, }, ], - } + }, ], attributes: [ { @@ -1457,9 +1429,9 @@ var configWithFeatures = { value: 'Hello audience', }, { - id: "8765345281230956", + id: '8765345281230956', value: '{ "count": 2, "message": "Hello audience" }', - } + }, ], }, ], @@ -1502,7 +1474,7 @@ var configWithFeatures = { { id: '8765345281230956', value: '{ "count": 1, "message": "Hello" }', - } + }, ], }, ], @@ -1638,7 +1610,7 @@ var configWithFeatures = { variables: [], }; -export var getTestProjectConfigWithFeatures = function () { +export var getTestProjectConfigWithFeatures = function() { return cloneDeep(configWithFeatures); }; @@ -1677,6 +1649,7 @@ export var datafileWithFeaturesExpectedData = { status: 'Not started', key: '599056', id: '599056', + isRollout: true, variationKeyMap: { 599057: { key: '599057', @@ -1741,6 +1714,7 @@ export var datafileWithFeaturesExpectedData = { ], key: '594031', id: '594031', + isRollout: true, variationKeyMap: { 594032: { variables: [ @@ -1813,6 +1787,7 @@ export var datafileWithFeaturesExpectedData = { ], key: '594037', id: '594037', + isRollout: true, variationKeyMap: { 594038: { variables: [ @@ -1886,6 +1861,7 @@ export var datafileWithFeaturesExpectedData = { ], key: '594060', id: '594060', + isRollout: true, variationKeyMap: { 594061: { variables: [ @@ -1950,6 +1926,7 @@ export var datafileWithFeaturesExpectedData = { ], key: '594066', id: '594066', + isRollout: true, variationKeyMap: { 594067: { variables: [ @@ -2005,7 +1982,7 @@ export var datafileWithFeaturesExpectedData = { 8765345281230956: { id: '8765345281230956', value: '{ "count": 2, "message": "Hello audience" }', - } + }, }, 594038: { 4919852825313280: { @@ -2027,7 +2004,7 @@ export var datafileWithFeaturesExpectedData = { 8765345281230956: { id: '8765345281230956', value: '{ "count": 1, "message": "Hello" }', - } + }, }, 594061: { 5060590313668608: { @@ -2330,7 +2307,7 @@ export var datafileWithFeaturesExpectedData = { type: 'json', key: 'button_info', id: '1547854156498475', - defaultValue: "{ \"num_buttons\": 0, \"text\": \"default value\"}" + defaultValue: '{ "num_buttons": 0, "text": "default value"}', }, ], experimentIds: ['594098'], @@ -2363,7 +2340,7 @@ export var datafileWithFeaturesExpectedData = { id: '6199684360044544', }, button_info: { - defaultValue: "{ \"num_buttons\": 0, \"text\": \"default value\"}", + defaultValue: '{ "num_buttons": 0, "text": "default value"}', id: '1547854156498475', key: 'button_info', type: 'json', @@ -2741,7 +2718,7 @@ var unsupportedVersionConfig = { projectId: '111001', }; -export var getUnsupportedVersionConfig = function () { +export var getUnsupportedVersionConfig = function() { return cloneDeep(unsupportedVersionConfig); }; @@ -3141,267 +3118,302 @@ var typedAudiencesConfig = { revision: '3', }; -export var getTypedAudiencesConfig = function () { +export var getTypedAudiencesConfig = function() { return cloneDeep(typedAudiencesConfig); }; var odpIntegratedConfigWithSegments = { - "version": "4", - "sendFlagDecisions": true, - "rollouts": [ + version: '4', + sendFlagDecisions: true, + rollouts: [ { - "experiments": [ + experiments: [ { - "audienceIds": ["13389130056"], - "forcedVariations": {}, - "id": "3332020515", - "key": "rollout-rule-1", - "layerId": "3319450668", - "status": "Running", - "trafficAllocation": [ + audienceIds: ['13389130056'], + forcedVariations: {}, + id: '3332020515', + key: 'rollout-rule-1', + layerId: '3319450668', + status: 'Running', + trafficAllocation: [ { - "endOfRange": 10000, - "entityId": "3324490633" - } + endOfRange: 10000, + entityId: '3324490633', + }, ], - "variations": [ + variations: [ { - "featureEnabled": true, - "id": "3324490633", - "key": "rollout-variation-on", - "variables": [] - } - ] + featureEnabled: true, + id: '3324490633', + key: 'rollout-variation-on', + variables: [], + }, + ], }, { - "audienceIds": [], - "forcedVariations": {}, - "id": "3332020556", - "key": "rollout-rule-2", - "layerId": "3319450668", - "status": "Running", - "trafficAllocation": [ + audienceIds: [], + forcedVariations: {}, + id: '3332020556', + key: 'rollout-rule-2', + layerId: '3319450668', + status: 'Running', + trafficAllocation: [ { - "endOfRange": 10000, - "entityId": "3324490644" - } + endOfRange: 10000, + entityId: '3324490644', + }, ], - "variations": [ + variations: [ { - "featureEnabled": false, - "id": "3324490644", - "key": "rollout-variation-off", - "variables": [] - } - ] - } + featureEnabled: false, + id: '3324490644', + key: 'rollout-variation-off', + variables: [], + }, + ], + }, ], - "id": "3319450668" - } + id: '3319450668', + }, ], - "anonymizeIP": true, - "botFiltering": true, - "projectId": "10431130345", - "variables": [], - "featureFlags": [ + anonymizeIP: true, + botFiltering: true, + projectId: '10431130345', + variables: [], + featureFlags: [ { - "experimentIds": ["10390977673"], - "id": "4482920077", - "key": "flag-segment", - "rolloutId": "3319450668", - "variables": [ + experimentIds: ['10390977673'], + id: '4482920077', + key: 'flag-segment', + rolloutId: '3319450668', + variables: [ { - "defaultValue": "42", - "id": "2687470095", - "key": "i_42", - "type": "integer" - } - ] - } + defaultValue: '42', + id: '2687470095', + key: 'i_42', + type: 'integer', + }, + ], + }, ], - "experiments": [ + experiments: [ { - "status": "Running", - "key": "experiment-segment", - "layerId": "10420273888", - "trafficAllocation": [ + status: 'Running', + key: 'experiment-segment', + layerId: '10420273888', + trafficAllocation: [ { - "entityId": "10389729780", - "endOfRange": 10000 - } + entityId: '10389729780', + endOfRange: 10000, + }, ], - "audienceIds": ["$opt_dummy_audience"], - "audienceConditions": ["or", "13389142234", "13389141123"], - "variations": [ + audienceIds: ['$opt_dummy_audience'], + audienceConditions: ['or', '13389142234', '13389141123'], + variations: [ { - "variables": [], - "featureEnabled": true, - "id": "10389729780", - "key": "variation-a" + variables: [], + featureEnabled: true, + id: '10389729780', + key: 'variation-a', }, { - "variables": [], - "id": "10416523121", - "key": "variation-b" - } + variables: [], + id: '10416523121', + key: 'variation-b', + }, ], - "forcedVariations": {}, - "id": "10390977673" - } + forcedVariations: {}, + id: '10390977673', + }, ], - "groups": [], - "integrations": [ + groups: [], + integrations: [ { - "key": "odp", - "host": "https://api.zaius.com/", - "publicKey": "W4WzcEs-ABgXorzY7h1LCQ" + key: 'odp', + host: 'https://api.zaius.com/', + publicKey: 'W4WzcEs-ABgXorzY7h1LCQ', + pixelUrl: 'https://jumbe.zaius.com/', }, { - "key": "odp", - "a": "1", - "b": "2", + key: 'odp', + host: 'https://api.zzzzaius.com/', + publicKey: 'W4WzcEs-ABgXorzssssY7h1LCQ', + pixelUrl: 'https://jumbe.zzzzaius.com/', }, { - "key": "x", - "test": "foobar" - } - ], - "typedAudiences": [ + key: 'odp', + a: '1', + b: '2', + }, { - "id": "13389142234", - "conditions": [ - "and", + key: 'x', + test: 'foobar', + }, + ], + typedAudiences: [ + { + id: '13389142234', + conditions: [ + 'and', [ - "or", + 'or', [ - "or", + 'or', { - "value": "odp-segment-1", - "type": "third_party_dimension", - "name": "odp.audiences", - "match": "qualified" - } - ] - ] + value: 'odp-segment-1', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + ], ], - "name": "odp-segment-1" + name: 'odp-segment-1', }, { - "id": "13389142234", - "conditions": [ - "and", + id: '13389142234', + conditions: [ + 'and', [ - "or", + 'or', [ - "or", + 'or', { - "value": "odp-segment-1", - "type": "third_party_dimension", - "name": "odp.audiences", - "match": "qualified" - } - ] - ] + value: 'odp-segment-1', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + ], ], - "name": "odp-segment-1" + name: 'odp-segment-1', }, { - "id": "13389130056", - "conditions": [ - "and", + id: '13389130056', + conditions: [ + 'and', [ - "or", + 'or', [ - "or", + 'or', { - "value": "odp-segment-2", - "type": "third_party_dimension", - "name": "odp.audiences", - "match": "qualified" + value: 'odp-segment-2', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', }, { - "value": "us", - "type": "custom_attribute", - "name": "country", - "match": "exact" - } + value: 'us', + type: 'custom_attribute', + name: 'country', + match: 'exact', + }, ], [ - "or", + 'or', { - "value": "odp-segment-3", - "type": "third_party_dimension", - "name": "odp.audiences", - "match": "qualified" - } - ] - ] + value: 'odp-segment-3', + type: 'third_party_dimension', + name: 'odp.audiences', + match: 'qualified', + }, + ], + ], ], - "name": "odp-segment-2" - } + name: 'odp-segment-2', + }, ], - "audiences": [ + audiences: [ { - "id": "13389141123", - "conditions": "[\"and\", [\"or\", [\"or\", {\"match\": \"gt\", \"name\": \"age\", \"type\": \"custom_attribute\", \"value\": 20}]]]", - "name": "adult" - } + id: '13389141123', + conditions: '["and", ["or", ["or", {"match": "gt", "name": "age", "type": "custom_attribute", "value": 20}]]]', + name: 'adult', + }, ], - "attributes": [ + attributes: [ { - "id": "10401066117", - "key": "gender" + id: '10401066117', + key: 'gender', }, { - "id": "10401066170", - "key": "testvar" - } + id: '10401066170', + key: 'testvar', + }, ], - "accountId": "10367498574", - "events": [], - "revision": "101" -} + accountId: '10367498574', + events: [], + revision: '101', +}; -export var getOdpIntegratedConfigWithSegments = function () { +export var getOdpIntegratedConfigWithSegments = function() { return cloneDeep(odpIntegratedConfigWithSegments); }; var odpIntegratedConfigWithoutSegments = { - "version": "4", - "rollouts": [], - "anonymizeIP": true, - "projectId": "10431130345", - "variables": [], - "featureFlags": [], - "experiments": [], - "audiences": [], - "groups": [], - "attributes": [], - "accountId": "10367498574", - "events": [], - "integrations": [ + version: '4', + rollouts: [], + anonymizeIP: true, + projectId: '10431130345', + variables: [], + featureFlags: [], + experiments: [], + audiences: [], + groups: [], + attributes: [], + accountId: '10367498574', + events: [], + integrations: [ { - "key": "odp", - "host": "https://api.zaius.com/", - "publicKey": "W4WzcEs-ABgXorzY7h1LCQ" + key: 'odp', + host: 'https://api.zaius.com/', + publicKey: 'W4WzcEs-ABgXorzY7h1LCQ', + pixelUrl: 'https://jumbe.zaius.com/', }, { - "key": "odp", - "a": "1", - "b": "2", + key: 'odp', + a: '1', + b: '2', }, { - "key": "x", - "test": "foobar" - } + key: 'x', + test: 'foobar', + }, ], - "revision": "100" -} + revision: '100', +}; -export var getOdpIntegratedConfigWithoutSegments = function () { +export var getOdpIntegratedConfigWithoutSegments = function() { return cloneDeep(odpIntegratedConfigWithoutSegments); }; +var odpIntegratedConfigWithoutKey = { + version: '4', + rollouts: [], + anonymizeIP: true, + projectId: '10431130345', + variables: [], + featureFlags: [], + experiments: [], + audiences: [], + groups: [], + attributes: [], + accountId: '10367498574', + events: [], + integrations: [ + { + host: 'https://api.zaius.com/', + publicKey: 'W4WzcEs-ABgXorzY7h1LCQ', + pixelUrl: 'https://jumbe.zaius.com/', + }, + ], + revision: '100', +}; + +export var getOdpIntegratedConfigWithoutKey = function() { + return cloneDeep(odpIntegratedConfigWithoutKey); +}; + export var typedAudiencesById = { 3468206642: { id: '3468206642', @@ -3536,21 +3548,21 @@ var mutexFeatureTestsConfig = { revision: '12', }; -export var getMutexFeatureTestsConfig = function () { +export var getMutexFeatureTestsConfig = function() { return cloneDeep(mutexFeatureTestsConfig); }; export var rolloutDecisionObj = { experiment: null, variation: null, - decisionSource: 'rollout', -} + decisionSource: 'rollout' as const, +}; export var featureTestDecisionObj = { experiment: { trafficAllocation: [ { endOfRange: 5000, entityId: '594096' }, - { endOfRange: 10000, entityId: '594097' } + { endOfRange: 10000, entityId: '594097' }, ], layerId: '594093', forcedVariations: {}, @@ -3561,12 +3573,14 @@ export var featureTestDecisionObj = { id: '594096', featureEnabled: true, variables: [], + variablesMap: {}, }, { key: 'control', id: '594097', featureEnabled: true, variables: [], + variablesMap: {} }, ], status: 'Running', @@ -3578,364 +3592,584 @@ export var featureTestDecisionObj = { id: '594096', featureEnabled: true, variables: [], + variablesMap: {} }, control: { key: 'control', id: '594097', featureEnabled: true, variables: [], + variablesMap: {} }, }, + audienceConditions: [] }, variation: { key: 'variation', id: '594096', featureEnabled: true, variables: [], + variablesMap: {} }, - decisionSource: 'feature-test', -} + decisionSource: 'feature-test' as const, +}; var similarRuleKeyConfig = { - version: "4", + version: '4', rollouts: [ { experiments: [ { - status: "Running", + status: 'Running', audienceConditions: [], audienceIds: [], variations: [ { variables: [], - id: "5452", - key: "on", - featureEnabled: true - } + id: '5452', + key: 'on', + featureEnabled: true, + }, ], forcedVariations: {}, - key: "targeted_delivery", - layerId: "9300000004981", + key: 'targeted_delivery', + layerId: '9300000004981', trafficAllocation: [ { - entityId: "5452", - endOfRange: 10000 - } + entityId: '5452', + endOfRange: 10000, + }, ], - id: "9300000004981" - }, { - status: "Running", + id: '9300000004981', + }, + { + status: 'Running', audienceConditions: [], audienceIds: [], variations: [ { variables: [], - id: "5451", - key: "off", - featureEnabled: false - } + id: '5451', + key: 'off', + featureEnabled: false, + }, ], forcedVariations: {}, - key: "default-rollout-2029-20301771717", - layerId: "default-layer-rollout-2029-20301771717", + key: 'default-rollout-2029-20301771717', + layerId: 'default-layer-rollout-2029-20301771717', trafficAllocation: [ { - entityId: "5451", - endOfRange: 10000 - } + entityId: '5451', + endOfRange: 10000, + }, ], - id: "default-rollout-2029-20301771717" - } + id: 'default-rollout-2029-20301771717', + }, ], - id: "rollout-2029-20301771717" - }, { + id: 'rollout-2029-20301771717', + }, + { experiments: [ { - status: "Running", + status: 'Running', audienceConditions: [], audienceIds: [], variations: [ { variables: [], - id: "5450", - key: "on", - featureEnabled: true - } + id: '5450', + key: 'on', + featureEnabled: true, + }, ], forcedVariations: {}, - key: "targeted_delivery", - layerId: "9300000004979", + key: 'targeted_delivery', + layerId: '9300000004979', trafficAllocation: [ { - entityId: "5450", - endOfRange: 10000 - } + entityId: '5450', + endOfRange: 10000, + }, ], - id: "9300000004979" - }, { - status: "Running", + id: '9300000004979', + }, + { + status: 'Running', audienceConditions: [], audienceIds: [], variations: [ { variables: [], - id: "5449", - key: "off", - featureEnabled: false - } + id: '5449', + key: 'off', + featureEnabled: false, + }, ], forcedVariations: {}, - key: "default-rollout-2028-20301771717", - layerId: "default-layer-rollout-2028-20301771717", + key: 'default-rollout-2028-20301771717', + layerId: 'default-layer-rollout-2028-20301771717', trafficAllocation: [ { - entityId: "5449", - endOfRange: 10000 - } + entityId: '5449', + endOfRange: 10000, + }, ], - id: "default-rollout-2028-20301771717" - } + id: 'default-rollout-2028-20301771717', + }, ], - id: "rollout-2028-20301771717" - }, { + id: 'rollout-2028-20301771717', + }, + { experiments: [ { - status: "Running", + status: 'Running', audienceConditions: [], audienceIds: [], variations: [ { variables: [], - id: "5448", - key: "on", - featureEnabled: true - } + id: '5448', + key: 'on', + featureEnabled: true, + }, ], forcedVariations: {}, - key: "targeted_delivery", - layerId: "9300000004977", + key: 'targeted_delivery', + layerId: '9300000004977', trafficAllocation: [ { - entityId: "5448", - endOfRange: 10000 - } + entityId: '5448', + endOfRange: 10000, + }, ], - id: "9300000004977" - }, { - status: "Running", + id: '9300000004977', + }, + { + status: 'Running', audienceConditions: [], audienceIds: [], variations: [ { variables: [], - id: "5447", - key: "off", - featureEnabled: false - } + id: '5447', + key: 'off', + featureEnabled: false, + }, ], forcedVariations: {}, - key: "default-rollout-2027-20301771717", - layerId: "default-layer-rollout-2027-20301771717", + key: 'default-rollout-2027-20301771717', + layerId: 'default-layer-rollout-2027-20301771717', trafficAllocation: [ { - entityId: "5447", - endOfRange: 10000 - } + entityId: '5447', + endOfRange: 10000, + }, ], - id: "default-rollout-2027-20301771717" - } + id: 'default-rollout-2027-20301771717', + }, ], - id: "rollout-2027-20301771717" - } + id: 'rollout-2027-20301771717', + }, ], typedAudiences: [], anonymizeIP: true, - projectId: "20286295225", + projectId: '20286295225', variables: [], featureFlags: [ { experimentIds: [], - rolloutId: "rollout-2029-20301771717", + rolloutId: 'rollout-2029-20301771717', variables: [], - id: "2029", - key: "flag_3" - }, { + id: '2029', + key: 'flag_3', + }, + { experimentIds: [], - rolloutId: "rollout-2028-20301771717", + rolloutId: 'rollout-2028-20301771717', variables: [], - id: "2028", - key: "flag_2" - }, { + id: '2028', + key: 'flag_2', + }, + { experimentIds: [], - rolloutId: "rollout-2027-20301771717", + rolloutId: 'rollout-2027-20301771717', variables: [], - id: "2027", - key: "flag_1" - } + id: '2027', + key: 'flag_1', + }, ], experiments: [], audiences: [ { - conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", - id: "$opt_dummy_audience", - name: "Optimizely-Generated Audience for Backwards Compatibility" - } + conditions: + '["or", {"match": "exact", "name": "$opt_dummy_attribute", "type": "custom_attribute", "value": "$opt_dummy_value"}]', + id: '$opt_dummy_audience', + name: 'Optimizely-Generated Audience for Backwards Compatibility', + }, ], groups: [], attributes: [], botFiltering: false, - accountId: "19947277778", + accountId: '19947277778', events: [], - revision: "11", - sendFlagDecisions: true -} + revision: '11', + sendFlagDecisions: true, +}; -export var getSimilarRuleKeyConfig = function () { +export var getSimilarRuleKeyConfig = function() { return cloneDeep(similarRuleKeyConfig); }; var similarExperimentKeysConfig = { - version: "4", + version: '4', rollouts: [], typedAudiences: [ { - id: "20415611520", + id: '20415611520', conditions: [ - "and", + 'and', [ - "or", + 'or', [ - "or", { + 'or', + { value: true, - type: "custom_attribute", - name: "hiddenLiveEnabled", - match: "exact" - } - ] - ] + type: 'custom_attribute', + name: 'hiddenLiveEnabled', + match: 'exact', + }, + ], + ], ], - name: "test1" - }, { - id: "20406066925", + name: 'test1', + }, + { + id: '20406066925', conditions: [ - "and", + 'and', [ - "or", + 'or', [ - "or", { + 'or', + { value: false, - type: "custom_attribute", - name: "hiddenLiveEnabled", - match: "exact" - } - ] - ] + type: 'custom_attribute', + name: 'hiddenLiveEnabled', + match: 'exact', + }, + ], + ], ], - name: "test2" - } + name: 'test2', + }, ], anonymizeIP: true, - projectId: "20430981610", + projectId: '20430981610', variables: [], featureFlags: [ { - experimentIds: ["9300000007569"], - rolloutId: "", + experimentIds: ['9300000007569'], + rolloutId: '', variables: [], - id: "3045", - key: "flag1" - }, { - experimentIds: ["9300000007573"], - rolloutId: "", + id: '3045', + key: 'flag1', + }, + { + experimentIds: ['9300000007573'], + rolloutId: '', variables: [], - id: "3046", - key: "flag2" - } + id: '3046', + key: 'flag2', + }, ], experiments: [ { - status: "Running", - audienceConditions: [ - "or", "20415611520" - ], - audienceIds: ["20415611520"], + status: 'Running', + audienceConditions: ['or', '20415611520'], + audienceIds: ['20415611520'], variations: [ { variables: [], - id: "8045", - key: "variation1", - featureEnabled: true - } + id: '8045', + key: 'variation1', + featureEnabled: true, + }, ], forcedVariations: {}, - key: "targeted_delivery", - layerId: "9300000007569", + key: 'targeted_delivery', + layerId: '9300000007569', trafficAllocation: [ { - entityId: "8045", - endOfRange: 10000 - } - ], - id: "9300000007569" - }, { - status: "Running", - audienceConditions: [ - "or", "20406066925" + entityId: '8045', + endOfRange: 10000, + }, ], - audienceIds: ["20406066925"], + id: '9300000007569', + }, + { + status: 'Running', + audienceConditions: ['or', '20406066925'], + audienceIds: ['20406066925'], variations: [ { variables: [], - id: "8048", - key: "variation2", - featureEnabled: true - } + id: '8048', + key: 'variation2', + featureEnabled: true, + }, ], forcedVariations: {}, - key: "targeted_delivery", - layerId: "9300000007573", + key: 'targeted_delivery', + layerId: '9300000007573', trafficAllocation: [ { - entityId: "8048", - endOfRange: 10000 - } + entityId: '8048', + endOfRange: 10000, + }, ], - id: "9300000007573" - } + id: '9300000007573', + }, ], audiences: [ { - id: "20415611520", - conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", - name: "test1" - }, { - id: "20406066925", - conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", - name: "test2" - }, { - conditions: "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", - id: "$opt_dummy_audience", - name: "Optimizely-Generated Audience for Backwards Compatibility" - } + id: '20415611520', + conditions: + '["or", {"match": "exact", "name": "$opt_dummy_attribute", "type": "custom_attribute", "value": "$opt_dummy_value"}]', + name: 'test1', + }, + { + id: '20406066925', + conditions: + '["or", {"match": "exact", "name": "$opt_dummy_attribute", "type": "custom_attribute", "value": "$opt_dummy_value"}]', + name: 'test2', + }, + { + conditions: + '["or", {"match": "exact", "name": "$opt_dummy_attribute", "type": "custom_attribute", "value": "$opt_dummy_value"}]', + id: '$opt_dummy_audience', + name: 'Optimizely-Generated Audience for Backwards Compatibility', + }, ], groups: [], attributes: [ { - id: "20408641883", - key: "hiddenLiveEnabled" - } + id: '20408641883', + key: 'hiddenLiveEnabled', + }, ], botFiltering: false, - accountId: "17882702980", + accountId: '17882702980', events: [], - revision: "25", - sendFlagDecisions: true -} + revision: '25', + sendFlagDecisions: true, +}; -export var getSimilarExperimentKeyConfig = function () { +export var getSimilarExperimentKeyConfig = function() { return cloneDeep(similarExperimentKeysConfig); }; +const duplicateExperimentKeyConfig = { + "accountId": "23793010390", + "projectId": "24812320344", + "revision": "24", + "attributes": [ + { + "id": "24778491463", + "key": "country" + }, + { + "id": "24802951640", + "key": "likes_donuts" + } + ], + "audiences": [ + { + "name": "mutext_feat", + "conditions": "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]", + "id": "24837020039" + }, + { + "id": "$opt_dummy_audience", + "name": "Optimizely-Generated Audience for Backwards Compatibility", + "conditions": "[\"or\", {\"match\": \"exact\", \"name\": \"$opt_dummy_attribute\", \"type\": \"custom_attribute\", \"value\": \"$opt_dummy_value\"}]" + } + ], + "version": "4", + "events": [], + "integrations": [], + "anonymizeIP": true, + "botFiltering": false, + "typedAudiences": [ + { + "name": "mutext_feat", + "conditions": [ + "and", + [ + "or", + [ + "or", + { + "match": "exact", + "name": "country", + "type": "custom_attribute", + "value": "US" + } + ], + [ + "or", + { + "match": "exact", + "name": "likes_donuts", + "type": "custom_attribute", + "value": true + } + ] + ] + ], + "id": "24837020039" + } + ], + "variables": [], + "environmentKey": "production", + "sdkKey": "BBhivmjEBF1KLK8HkMrvj", + "featureFlags": [ + { + "id": "101043", + "key": "mutext_feat2", + "rolloutId": "rollout-101043-24783691394", + "experimentIds": [ + "9300000361925" + ], + "variables": [] + }, + { + "id": "101044", + "key": "mutex_feat", + "rolloutId": "rollout-101044-24783691394", + "experimentIds": [ + "9300000365056" + ], + "variables": [] + } + ], + "rollouts": [ + { + "id": "rollout-101043-24783691394", + "experiments": [ + { + "id": "default-rollout-101043-24783691394", + "key": "default-rollout-101043-24783691394", + "status": "Running", + "layerId": "rollout-101043-24783691394", + "variations": [ + { + "id": "321340", + "key": "off", + "featureEnabled": false, + "variables": [] + } + ], + "trafficAllocation": [ + { + "entityId": "321340", + "endOfRange": 10000 + } + ], + "forcedVariations": {}, + "audienceIds": [], + "audienceConditions": [] + } + ] + }, + { + "id": "rollout-101044-24783691394", + "experiments": [ + { + "id": "default-rollout-101044-24783691394", + "key": "default-rollout-101044-24783691394", + "status": "Running", + "layerId": "rollout-101044-24783691394", + "variations": [ + { + "id": "321343", + "key": "off", + "featureEnabled": false, + "variables": [] + } + ], + "trafficAllocation": [ + { + "entityId": "321343", + "endOfRange": 10000 + } + ], + "forcedVariations": {}, + "audienceIds": [], + "audienceConditions": [] + } + ] + } + ], + "experiments": [ + { + "id": "9300000361925", + "key": "experiment_rule", + "status": "Running", + "layerId": "9300000284731", + "variations": [ + { + "id": "321342", + "key": "variation_1", + "featureEnabled": true, + "variables": [] + } + ], + "trafficAllocation": [ + { + "entityId": "321342", + "endOfRange": 10000 + } + ], + "forcedVariations": {}, + "audienceIds": [ + "24837020039" + ], + "audienceConditions": [ + "or", + "24837020039" + ] + }, + { + "id": "9300000365056", + "key": "experiment_rule", + "status": "Running", + "layerId": "9300000287826", + "variations": [ + { + "id": "321345", + "key": "variation_1", + "featureEnabled": true, + "variables": [] + } + ], + "trafficAllocation": [ + { + "entityId": "321345", + "endOfRange": 10000 + } + ], + "forcedVariations": {}, + "audienceIds": [], + "audienceConditions": [] + } + ], + "groups": [] +}; + +export const getDuplicateExperimentKeyConfig = function() { + return cloneDeep(duplicateExperimentKeyConfig); +}; + export default { getTestProjectConfig: getTestProjectConfig, getTestDecideProjectConfig: getTestDecideProjectConfig, @@ -3946,8 +4180,10 @@ export default { getTypedAudiencesConfig: getTypedAudiencesConfig, getOdpIntegratedConfigWithSegments: getOdpIntegratedConfigWithSegments, getOdpIntegratedConfigWithoutSegments: getOdpIntegratedConfigWithoutSegments, + getOdpIntegratedConfigWithoutKey: getOdpIntegratedConfigWithoutKey, typedAudiencesById: typedAudiencesById, getMutexFeatureTestsConfig: getMutexFeatureTestsConfig, getSimilarRuleKeyConfig: getSimilarRuleKeyConfig, - getSimilarExperimentKeyConfig: getSimilarExperimentKeyConfig + getSimilarExperimentKeyConfig: getSimilarExperimentKeyConfig, + getDuplicateExperimentKeyConfig: getDuplicateExperimentKeyConfig, }; diff --git a/lib/utils/attributes_validator/index.spec.ts b/lib/utils/attributes_validator/index.spec.ts new file mode 100644 index 000000000..645fa2113 --- /dev/null +++ b/lib/utils/attributes_validator/index.spec.ts @@ -0,0 +1,115 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect } from 'vitest'; +import * as attributesValidator from './'; +import { INVALID_ATTRIBUTES, UNDEFINED_ATTRIBUTE } from 'error_message'; +import { OptimizelyError } from '../../error/optimizly_error'; + +describe('validate', () => { + it('should validate the given attributes if attributes is an object', () => { + expect(attributesValidator.validate({ testAttribute: 'testValue' })).toBe(true); + }); + + it('should throw an error if attributes is an array', () => { + const attributesArray = ['notGonnaWork']; + + expect(() => attributesValidator.validate(attributesArray)).toThrow(OptimizelyError); + + try { + attributesValidator.validate(attributesArray); + } catch (err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_ATTRIBUTES); + } + }); + + it('should throw an error if attributes is null', () => { + expect(() => attributesValidator.validate(null)).toThrowError(OptimizelyError); + + try { + attributesValidator.validate(null); + } catch (err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_ATTRIBUTES); + } + }); + + it('should throw an error if attributes is a function', () => { + function invalidInput() { + console.log('This is an invalid input!'); + } + + expect(() => attributesValidator.validate(invalidInput)).toThrowError(OptimizelyError); + + try { + attributesValidator.validate(invalidInput); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_ATTRIBUTES); + } + }); + + it('should throw an error if attributes contains a key with an undefined value', () => { + const attributeKey = 'testAttribute'; + const attributes: Record<string, unknown> = {}; + attributes[attributeKey] = undefined; + + expect(() => attributesValidator.validate(attributes)).toThrowError(OptimizelyError); + + try { + attributesValidator.validate(attributes); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(UNDEFINED_ATTRIBUTE); + expect(err.params).toEqual([attributeKey]); + } + }); +}); + +describe('isAttributeValid', () => { + it('isAttributeValid returns true for valid values', () => { + const userAttributes: Record<string, unknown> = { + browser_type: 'Chrome', + is_firefox: false, + num_users: 10, + pi_value: 3.14, + '': 'javascript', + }; + + Object.keys(userAttributes).forEach(key => { + const value = userAttributes[key]; + + expect(attributesValidator.isAttributeValid(key, value)).toBe(true); + }); + }); + it('isAttributeValid returns false for invalid values', () => { + const userAttributes: Record<string, unknown> = { + null: null, + objects: { a: 'b' }, + array: [1, 2, 3], + infinity: Infinity, + negativeInfinity: -Infinity, + NaN: NaN, + }; + + Object.keys(userAttributes).forEach(key => { + const value = userAttributes[key]; + + expect(attributesValidator.isAttributeValid(key, value)).toBe(false); + }); + }); +}); diff --git a/packages/optimizely-sdk/lib/utils/attributes_validator/index.tests.js b/lib/utils/attributes_validator/index.tests.js similarity index 83% rename from packages/optimizely-sdk/lib/utils/attributes_validator/index.tests.js rename to lib/utils/attributes_validator/index.tests.js index d98e8fdb0..ecfc4bccb 100644 --- a/packages/optimizely-sdk/lib/utils/attributes_validator/index.tests.js +++ b/lib/utils/attributes_validator/index.tests.js @@ -17,7 +17,7 @@ import { assert } from 'chai'; import { sprintf } from '../../utils/fns'; import * as attributesValidator from './'; -import { ERROR_MESSAGES } from '../enums'; +import { INVALID_ATTRIBUTES, UNDEFINED_ATTRIBUTE } from 'error_message'; describe('lib/utils/attributes_validator', function() { describe('APIs', function() { @@ -28,24 +28,27 @@ describe('lib/utils/attributes_validator', function() { it('should throw an error if attributes is an array', function() { var attributesArray = ['notGonnaWork']; - assert.throws(function() { + const ex = assert.throws(function() { attributesValidator.validate(attributesArray); - }, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_ATTRIBUTES); }); it('should throw an error if attributes is null', function() { - assert.throws(function() { + const ex = assert.throws(function() { attributesValidator.validate(null); - }, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_ATTRIBUTES); }); it('should throw an error if attributes is a function', function() { function invalidInput() { console.log('This is an invalid input!'); } - assert.throws(function() { + const ex = assert.throws(function() { attributesValidator.validate(invalidInput); - }, sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, 'ATTRIBUTES_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_ATTRIBUTES); }); it('should throw an error if attributes contains a key with an undefined value', function() { @@ -53,9 +56,11 @@ describe('lib/utils/attributes_validator', function() { var attributes = {}; attributes[attributeKey] = undefined; - assert.throws(function() { + const ex = assert.throws(function() { attributesValidator.validate(attributes); - }, sprintf(ERROR_MESSAGES.UNDEFINED_ATTRIBUTE, 'ATTRIBUTES_VALIDATOR', attributeKey)); + }); + assert.equal(ex.baseMessage, UNDEFINED_ATTRIBUTE); + assert.deepEqual(ex.params, [attributeKey]); }); }); diff --git a/packages/optimizely-sdk/lib/utils/attributes_validator/index.ts b/lib/utils/attributes_validator/index.ts similarity index 85% rename from packages/optimizely-sdk/lib/utils/attributes_validator/index.ts rename to lib/utils/attributes_validator/index.ts index 5f33e85d4..08b50eb43 100644 --- a/packages/optimizely-sdk/lib/utils/attributes_validator/index.ts +++ b/lib/utils/attributes_validator/index.ts @@ -13,13 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { sprintf } from '../../utils/fns'; import { ObjectWithUnknownProperties } from '../../shared_types'; import fns from '../../utils/fns'; -import { ERROR_MESSAGES } from '../enums'; - -const MODULE_NAME = 'ATTRIBUTES_VALIDATOR'; +import { INVALID_ATTRIBUTES, UNDEFINED_ATTRIBUTE } from 'error_message'; +import { OptimizelyError } from '../../error/optimizly_error'; /** * Validates user's provided attributes @@ -32,12 +30,12 @@ export function validate(attributes: unknown): boolean { if (typeof attributes === 'object' && !Array.isArray(attributes) && attributes !== null) { Object.keys(attributes).forEach(function(key) { if (typeof (attributes as ObjectWithUnknownProperties)[key] === 'undefined') { - throw new Error(sprintf(ERROR_MESSAGES.UNDEFINED_ATTRIBUTE, MODULE_NAME, key)); + throw new OptimizelyError(UNDEFINED_ATTRIBUTE, key); } }); return true; } else { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_ATTRIBUTES, MODULE_NAME)); + throw new OptimizelyError(INVALID_ATTRIBUTES); } } diff --git a/lib/utils/cache/async_storage_cache.react_native.spec.ts b/lib/utils/cache/async_storage_cache.react_native.spec.ts new file mode 100644 index 000000000..f67fca7bf --- /dev/null +++ b/lib/utils/cache/async_storage_cache.react_native.spec.ts @@ -0,0 +1,96 @@ +/** + * Copyright 2022-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { vi, describe, it, expect } from 'vitest'; +import { AsyncStorageCache } from './async_storage_cache.react_native'; +import { getDefaultAsyncStorage } from '../import.react_native/@react-native-async-storage/async-storage'; + +vi.mock('@react-native-async-storage/async-storage'); + +type TestData = { + a: number; + b: string; + d: { e: boolean }; +}; + +describe('AsyncStorageCache', () => { + const asyncStorage = getDefaultAsyncStorage(); + + it('should store a stringified value in async storage', async () => { + const cache = new AsyncStorageCache<TestData>(); + + const data = { a: 1, b: '2', d: { e: true } }; + await cache.set('key', data); + + expect(await asyncStorage.getItem('key')).toBe(JSON.stringify(data)); + expect(await cache.get('key')).toEqual(data); + }); + + it('should return undefined if get is called for a nonexistent key', async () => { + const cache = new AsyncStorageCache<string>(); + + expect(await cache.get('nonexistent')).toBeUndefined(); + }); + + it('should return the value if get is called for an existing key', async () => { + const cache = new AsyncStorageCache<string>(); + await cache.set('key', 'value'); + + expect(await cache.get('key')).toBe('value'); + }); + + it('should return the value after json parsing if get is called for an existing key', async () => { + const cache = new AsyncStorageCache<TestData>(); + const data = { a: 1, b: '2', d: { e: true } }; + await cache.set('key', data); + + expect(await cache.get('key')).toEqual(data); + }); + + it('should remove the key from async storage when remove is called', async () => { + const cache = new AsyncStorageCache<string>(); + await cache.set('key', 'value'); + await cache.remove('key'); + + expect(await asyncStorage.getItem('key')).toBeNull(); + }); + + it('should remove all keys from async storage when clear is called', async () => { + const cache = new AsyncStorageCache<string>(); + await cache.set('key1', 'value1'); + await cache.set('key2', 'value2'); + + expect((await asyncStorage.getAllKeys()).length).toBe(2); + cache.clear(); + expect((await asyncStorage.getAllKeys()).length).toBe(0); + }); + + it('should return all keys when getKeys is called', async () => { + const cache = new AsyncStorageCache<string>(); + await cache.set('key1', 'value1'); + await cache.set('key2', 'value2'); + + expect(await cache.getKeys()).toEqual(['key1', 'key2']); + }); + + it('should return an array of values for an array of keys when getBatched is called', async () => { + const cache = new AsyncStorageCache<string>(); + await cache.set('key1', 'value1'); + await cache.set('key2', 'value2'); + + expect(await cache.getBatched(['key1', 'key2'])).toEqual(['value1', 'value2']); + }); +}); diff --git a/lib/utils/cache/async_storage_cache.react_native.ts b/lib/utils/cache/async_storage_cache.react_native.ts new file mode 100644 index 000000000..e5e76024e --- /dev/null +++ b/lib/utils/cache/async_storage_cache.react_native.ts @@ -0,0 +1,50 @@ +/** + * Copyright 2022-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Maybe } from "../type"; +import { AsyncStore } from "./store"; +import { getDefaultAsyncStorage } from "../import.react_native/@react-native-async-storage/async-storage"; + +export class AsyncStorageCache<V> implements AsyncStore<V> { + public readonly operation = 'async'; + private asyncStorage = getDefaultAsyncStorage(); + + async get(key: string): Promise<V | undefined> { + const value = await this.asyncStorage.getItem(key); + return value ? JSON.parse(value) : undefined; + } + + async remove(key: string): Promise<unknown> { + return this.asyncStorage.removeItem(key); + } + + async set(key: string, val: V): Promise<unknown> { + return this.asyncStorage.setItem(key, JSON.stringify(val)); + } + + async clear(): Promise<unknown> { + return this.asyncStorage.clear(); + } + + async getKeys(): Promise<string[]> { + return [... await this.asyncStorage.getAllKeys()]; + } + + async getBatched(keys: string[]): Promise<Maybe<V>[]> { + const items = await this.asyncStorage.multiGet(keys); + return items.map(([key, value]) => value ? JSON.parse(value) : undefined); + } +} diff --git a/lib/utils/cache/cache.spec.ts b/lib/utils/cache/cache.spec.ts new file mode 100644 index 000000000..687176d59 --- /dev/null +++ b/lib/utils/cache/cache.spec.ts @@ -0,0 +1,170 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import { transformCache, SyncCacheWithRemove, AsyncCacheWithRemove, CacheWithRemove } from './cache'; +import { getMockSyncCache, getMockAsyncCache } from '../../tests/mock/mock_cache'; + +const numberToString = (value: number): string => value.toString(); +const stringToNumber = (value: string): number => parseInt(value, 10); + +describe('transformCache', () => { + describe('sync cache operations', () => { + let mockCache: SyncCacheWithRemove<string>; + let transformedCache: CacheWithRemove<number>; + + beforeEach(() => { + mockCache = getMockSyncCache<string>(); + transformedCache = transformCache(mockCache, stringToNumber, numberToString); + }); + + it('should transform and save values using transformSet', () => { + const value = 42; + const key = 'test-key'; + + transformedCache.save(key, value); + + expect(mockCache.lookup(key)).toBe('42'); + }); + + it('should lookup and transform values using transformGet when value exists', () => { + const key = 'test-key'; + const storedValue = '123'; + + mockCache.save(key, storedValue); + + const result = transformedCache.lookup(key); + expect(result).toBe(123); + }); + + it('should return undefined when lookup value does not exist', () => { + const key = 'non-existent-key'; + + const result = transformedCache.lookup(key); + expect(result).toBeUndefined(); + }); + + it('should return the saved value on lookup', () => { + const key = 'sample-key'; + const value = 100; + + transformedCache.save(key, value); + const result = transformedCache.lookup(key); + expect(result).toBe(value); + }); + + it('should reset the cache when reset is called', () => { + transformedCache.save('key1', 42); + transformedCache.save('key2', 1729); + + transformedCache.reset(); + + expect(mockCache.lookup('key1')).toBeUndefined(); + expect(mockCache.lookup('key2')).toBeUndefined(); + expect(transformedCache.lookup('key1')).toBeUndefined(); + expect(transformedCache.lookup('key2')).toBeUndefined(); + }); + + it('should remove the key from the cache when remove is called', () => { + const key = 'test-key'; + transformedCache.save(key, 99); + + expect(transformedCache.lookup(key)).toBe(99); + + transformedCache.remove(key); + expect(mockCache.lookup(key)).toBeUndefined(); + expect(transformedCache.lookup(key)).toBeUndefined(); + }); + + it('should preserve original cache operation type', () => { + expect(transformedCache.operation).toBe('sync'); + }); + }); + + describe('async cache operations', () => { + let mockAsyncCache: AsyncCacheWithRemove<string>; + let transformedAsyncCache: CacheWithRemove<number>; + + beforeEach(() => { + mockAsyncCache = getMockAsyncCache<string>(); + transformedAsyncCache = transformCache(mockAsyncCache, stringToNumber, numberToString); + }); + + it('should transform and save values using transformSet', async () => { + const value = 42; + const key = 'test-key'; + + await transformedAsyncCache.save(key, value); + + const result = await mockAsyncCache.lookup(key); + expect(result).toBe('42'); + }); + + it('should lookup and transform values using transformGet when value exists', async () => { + const key = 'test-key'; + const storedValue = '123'; + + await mockAsyncCache.save(key, storedValue); + + const result = await transformedAsyncCache.lookup(key); + expect(result).toBe(123); + }); + + it('should return undefined when lookup value does not exist', async () => { + const key = 'non-existent-key'; + + const result = await transformedAsyncCache.lookup(key); + expect(result).toBeUndefined(); + }); + + it('should return the saved value on lookup', async () => { + const key = 'sample-key'; + const value = 100; + + await transformedAsyncCache.save(key, value); + const result = await transformedAsyncCache.lookup(key); + expect(result).toBe(value); + }); + + it('should reset the cache when reset is called', async () => { + await transformedAsyncCache.save('key1', 42); + await transformedAsyncCache.save('key2', 1729); + + await transformedAsyncCache.reset(); + + await expect(mockAsyncCache.lookup('key1')).resolves.toBeUndefined(); + await expect(mockAsyncCache.lookup('key2')).resolves.toBeUndefined(); + + await expect(transformedAsyncCache.lookup('key1')).resolves.toBeUndefined(); + await expect(transformedAsyncCache.lookup('key2')).resolves.toBeUndefined(); + }); + + it('should remove the key from the cache when remove is called', async () => { + const key = 'test-key'; + await transformedAsyncCache.save(key, 99); + + await expect(transformedAsyncCache.lookup(key)).resolves.toBe(99); + + await transformedAsyncCache.remove(key); + + await expect(mockAsyncCache.lookup(key)).resolves.toBeUndefined(); + await expect(transformedAsyncCache.lookup(key)).resolves.toBeUndefined(); + }); + + it('should preserve original cache operation type', () => { + expect(transformedAsyncCache.operation).toBe('async'); + }); + }); +}); \ No newline at end of file diff --git a/lib/utils/cache/cache.ts b/lib/utils/cache/cache.ts new file mode 100644 index 000000000..685b43a7b --- /dev/null +++ b/lib/utils/cache/cache.ts @@ -0,0 +1,70 @@ +/** + * Copyright 2022-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { OpType, OpValue } from '../../utils/type'; +import { Transformer } from '../../utils/type'; +export interface OpCache<OP extends OpType, V> { + operation: OP; + save(key: string, value: V): OpValue<OP, unknown>; + lookup(key: string): OpValue<OP, V | undefined>; + reset(): OpValue<OP, unknown>; +} + +export type SyncCache<V> = OpCache<'sync', V>; +export type AsyncCache<V> = OpCache<'async', V>; + +export type Cache<V> = SyncCache<V> | AsyncCache<V>; + +export interface OpCacheWithRemove<OP extends OpType, V> extends OpCache<OP, V> { + remove(key: string): OpValue<OP, unknown>; +} + +export type SyncCacheWithRemove<V> = OpCacheWithRemove<'sync', V>; +export type AsyncCacheWithRemove<V> = OpCacheWithRemove<'async', V>; +export type CacheWithRemove<V> = SyncCacheWithRemove<V> | AsyncCacheWithRemove<V>; + +export const transformCache = <U, V> ( + cache: CacheWithRemove<U>, + transformGet: Transformer<U, V>, + transformSet: Transformer<V, U>, +): CacheWithRemove<V> => { + const transform = <U, V>(value: U | undefined, transformer: Transformer<U, V>): V | undefined => { + if (value === undefined) { + return undefined; + } + return transformer(value); + } + + const lookup: any = (key: string) => { + if (cache.operation === 'sync') { + return transform(cache.lookup(key), transformGet); + } + + return cache.lookup(key).then((v) => transform(v, transformGet)); + } + + const save: any = (key: string, value: V) => { + return cache.save(key, transformSet(value)); + } + + const transformedCache = { + lookup, + save, + }; + + Object.setPrototypeOf(transformedCache, cache); + + return transformedCache as CacheWithRemove<V>; +}; diff --git a/lib/utils/cache/in_memory_lru_cache.spec.ts b/lib/utils/cache/in_memory_lru_cache.spec.ts new file mode 100644 index 000000000..81c1e4a96 --- /dev/null +++ b/lib/utils/cache/in_memory_lru_cache.spec.ts @@ -0,0 +1,106 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { expect, describe, it } from 'vitest'; +import { InMemoryLruCache } from './in_memory_lru_cache'; +import { wait } from '../../tests/testUtils'; + +describe('InMemoryLruCache', () => { + it('should save and get values correctly', () => { + const cache = new InMemoryLruCache<number>(2); + cache.save('a', 1); + cache.save('b', 2); + expect(cache.lookup('a')).toBe(1); + expect(cache.lookup('b')).toBe(2); + }); + + it('should return undefined for non-existent keys', () => { + const cache = new InMemoryLruCache<number>(2); + expect(cache.lookup('a')).toBe(undefined); + }); + + it('should return all keys in cache when getKeys is called', () => { + const cache = new InMemoryLruCache<number>(20); + cache.save('a', 1); + cache.save('b', 2); + cache.save('c', 3); + cache.save('d', 4); + expect(cache.getKeys()).toEqual(expect.arrayContaining(['d', 'c', 'b', 'a'])); + }); + it('should evict least recently used keys when full', () => { + const cache = new InMemoryLruCache<number>(3); + cache.save('a', 1); + cache.save('b', 2); + cache.save('c', 3); + + expect(cache.lookup('b')).toBe(2); + expect(cache.lookup('c')).toBe(3); + expect(cache.lookup('a')).toBe(1); + expect(cache.getKeys()).toEqual(expect.arrayContaining(['a', 'c', 'b'])); + + // key use order is now a c b. next insert should evict b + cache.save('d', 4); + expect(cache.lookup('b')).toBe(undefined); + expect(cache.getKeys()).toEqual(expect.arrayContaining(['d', 'a', 'c'])); + + // key use order is now d a c. setting c should put it at the front + cache.save('c', 5); + + // key use order is now c d a. next insert should evict a + cache.save('e', 6); + expect(cache.lookup('a')).toBe(undefined); + expect(cache.getKeys()).toEqual(expect.arrayContaining(['e', 'c', 'd'])); + + // key use order is now e c d. reading d should put it at the front + expect(cache.lookup('d')).toBe(4); + + // key use order is now d e c. next insert should evict c + cache.save('f', 7); + expect(cache.lookup('c')).toBe(undefined); + expect(cache.getKeys()).toEqual(expect.arrayContaining(['f', 'd', 'e'])); + }); + + it('should not return expired values when get is called', async () => { + const cache = new InMemoryLruCache<number>(2, 100); + cache.save('a', 1); + cache.save('b', 2); + expect(cache.lookup('a')).toBe(1); + expect(cache.lookup('b')).toBe(2); + + await wait(150); + expect(cache.lookup('a')).toBe(undefined); + expect(cache.lookup('b')).toBe(undefined); + }); + + it('should remove values correctly', () => { + const cache = new InMemoryLruCache<number>(2); + cache.save('a', 1); + cache.save('b', 2); + cache.save('c', 3); + cache.remove('a'); + expect(cache.lookup('a')).toBe(undefined); + expect(cache.lookup('b')).toBe(2); + expect(cache.lookup('c')).toBe(3); + }); + + it('should clear all values correctly', () => { + const cache = new InMemoryLruCache<number>(2); + cache.save('a', 1); + cache.save('b', 2); + cache.reset(); + expect(cache.lookup('a')).toBe(undefined); + expect(cache.lookup('b')).toBe(undefined); + }); +}); diff --git a/lib/utils/cache/in_memory_lru_cache.ts b/lib/utils/cache/in_memory_lru_cache.ts new file mode 100644 index 000000000..6ed92d1fd --- /dev/null +++ b/lib/utils/cache/in_memory_lru_cache.ts @@ -0,0 +1,74 @@ +/** + * Copyright 2022-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Maybe } from "../type"; +import { SyncCacheWithRemove } from "./cache"; + +type CacheElement<V> = { + value: V; + expiresAt?: number; +}; + +export class InMemoryLruCache<V> implements SyncCacheWithRemove<V> { + public operation = 'sync' as const; + private data: Map<string, CacheElement<V>> = new Map(); + private maxSize: number; + private ttl?: number; + + constructor(maxSize: number, ttl?: number) { + this.maxSize = maxSize; + this.ttl = ttl; + } + + lookup(key: string): Maybe<V> { + const element = this.data.get(key); + if (!element) return undefined; + this.data.delete(key); + + if (element.expiresAt && element.expiresAt <= Date.now()) { + return undefined; + } + + this.data.set(key, element); + return element.value; + } + + save(key: string, value: V): void { + this.data.delete(key); + + if (this.data.size === this.maxSize) { + const firstMapEntryKey = this.data.keys().next().value; + this.data.delete(firstMapEntryKey!); + } + + this.data.set(key, { + value, + expiresAt: this.ttl ? Date.now() + this.ttl : undefined, + }); + } + + remove(key: string): void { + this.data.delete(key); + } + + reset(): void { + this.data.clear(); + } + + getKeys(): string[] { + return Array.from(this.data.keys()); + } +} diff --git a/lib/utils/cache/local_storage_cache.browser.spec.ts b/lib/utils/cache/local_storage_cache.browser.spec.ts new file mode 100644 index 000000000..37e0735ba --- /dev/null +++ b/lib/utils/cache/local_storage_cache.browser.spec.ts @@ -0,0 +1,85 @@ +/** + * Copyright 2022-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, beforeEach } from 'vitest'; +import { LocalStorageCache } from './local_storage_cache.browser'; + +type TestData = { + a: number; + b: string; + d: { e: boolean }; +} + +describe('LocalStorageCache', () => { + beforeEach(() => { + localStorage.clear(); + }); + + it('should store a stringified value in local storage', () => { + const cache = new LocalStorageCache<TestData>(); + const data = { a: 1, b: '2', d: { e: true } }; + cache.set('key', data); + expect(localStorage.getItem('key')).toBe(JSON.stringify(data)); + }); + + it('should return undefined if get is called for a nonexistent key', () => { + const cache = new LocalStorageCache<string>(); + expect(cache.get('nonexistent')).toBeUndefined(); + }); + + it('should return the value if get is called for an existing key', () => { + const cache = new LocalStorageCache<string>(); + cache.set('key', 'value'); + expect(cache.get('key')).toBe('value'); + }); + + it('should return the value after json parsing if get is called for an existing key', () => { + const cache = new LocalStorageCache<TestData>(); + const data = { a: 1, b: '2', d: { e: true } }; + cache.set('key', data); + expect(cache.get('key')).toEqual(data); + }); + + it('should remove the key from local storage when remove is called', () => { + const cache = new LocalStorageCache<string>(); + cache.set('key', 'value'); + cache.remove('key'); + expect(localStorage.getItem('key')).toBeNull(); + }); + + it('should remove all keys from local storage when clear is called', () => { + const cache = new LocalStorageCache<string>(); + cache.set('key1', 'value1'); + cache.set('key2', 'value2'); + expect(localStorage.length).toBe(2); + cache.clear(); + expect(localStorage.length).toBe(0); + }); + + it('should return all keys when getKeys is called', () => { + const cache = new LocalStorageCache<string>(); + cache.set('key1', 'value1'); + cache.set('key2', 'value2'); + expect(cache.getKeys()).toEqual(['key1', 'key2']); + }); + + it('should return an array of values for an array of keys when getBatched is called', () => { + const cache = new LocalStorageCache<string>(); + cache.set('key1', 'value1'); + cache.set('key2', 'value2'); + expect(cache.getBatched(['key1', 'key2'])).toEqual(['value1', 'value2']); + }); +}); diff --git a/lib/utils/cache/local_storage_cache.browser.ts b/lib/utils/cache/local_storage_cache.browser.ts new file mode 100644 index 000000000..b16d77571 --- /dev/null +++ b/lib/utils/cache/local_storage_cache.browser.ts @@ -0,0 +1,54 @@ +/** + * Copyright 2022-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Maybe } from "../type"; +import { SyncStore } from "./store"; + +export class LocalStorageCache<V> implements SyncStore<V> { + public readonly operation = 'sync'; + + public set(key: string, value: V): void { + localStorage.setItem(key, JSON.stringify(value)); + } + + public get(key: string): Maybe<V> { + const value = localStorage.getItem(key); + return value ? JSON.parse(value) : undefined; + } + + public remove(key: string): void { + localStorage.removeItem(key); + } + + public clear(): void { + localStorage.clear(); + } + + public getKeys(): string[] { + const keys: string[] = []; + for(let i = 0; i < localStorage.length; i++) { + const key = localStorage.key(i); + if (key) { + keys.push(key); + } + } + return keys; + } + + getBatched(keys: string[]): Maybe<V>[] { + return keys.map((k) => this.get(k)); + } +} diff --git a/lib/utils/cache/store.spec.ts b/lib/utils/cache/store.spec.ts new file mode 100644 index 000000000..a99226844 --- /dev/null +++ b/lib/utils/cache/store.spec.ts @@ -0,0 +1,291 @@ +/** + * Copyright 2022-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect } from 'vitest'; +import { SyncPrefixStore, AsyncPrefixStore } from './store'; +import { getMockSyncCache, getMockAsyncCache } from '../../tests/mock/mock_cache'; + +describe('SyncPrefixStore', () => { + describe('set', () => { + it('should add prefix to key when setting in the underlying cache', () => { + const cache = getMockSyncCache<string>(); + const prefixCache = new SyncPrefixStore(cache, 'prefix:', (v) => v, (v) => v); + prefixCache.set('key', 'value'); + expect(cache.get('prefix:key')).toEqual('value'); + }); + + it('should transform value when setting in the underlying cache', () => { + const cache = getMockSyncCache<string>(); + const prefixCache = new SyncPrefixStore(cache, 'prefix:', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + prefixCache.set('key', 'value'); + expect(cache.get('prefix:key')).toEqual('VALUE'); + }); + + it('should work correctly with empty prefix', () => { + const cache = getMockSyncCache<string>(); + const prefixCache = new SyncPrefixStore(cache, '', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + prefixCache.set('key', 'value'); + expect(cache.get('key')).toEqual('VALUE'); + }); + }); + + describe('get', () => { + it('should remove prefix from key when getting from the underlying cache', () => { + const cache = getMockSyncCache<string>(); + cache.set('prefix:key', 'value'); + const prefixCache = new SyncPrefixStore(cache, 'prefix:', (v) => v, (v) => v); + expect(prefixCache.get('key')).toEqual('value'); + }); + + it('should transform value after getting from the underlying cache', () => { + const cache = getMockSyncCache<string>(); + const prefixCache = new SyncPrefixStore(cache, 'prefix:', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + cache.set('prefix:key', 'VALUE'); + expect(prefixCache.get('key')).toEqual('value'); + }); + + + it('should work correctly with empty prefix', () => { + const cache = getMockSyncCache<string>(); + const prefixCache = new SyncPrefixStore(cache, '', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + cache.set('key', 'VALUE'); + expect(prefixCache.get('key')).toEqual('value'); + }); + }); + + describe('remove', () => { + it('should remove the correct value from the underlying cache', () => { + const cache = getMockSyncCache<string>(); + cache.set('prefix:key', 'value'); + cache.set('key', 'value'); + const prefixCache = new SyncPrefixStore(cache, 'prefix:', (v) => v, (v) => v); + prefixCache.remove('key'); + expect(cache.get('prefix:key')).toBeUndefined(); + expect(cache.get('key')).toEqual('value'); + }); + + it('should work with empty prefix', () => { + const cache = getMockSyncCache<string>(); + cache.set('key', 'value'); + const prefixCache = new SyncPrefixStore(cache, '', (v) => v, (v) => v); + prefixCache.remove('key'); + expect(cache.get('key')).toBeUndefined(); + }); + }); + + describe('getKeys', () => { + it('should return keys with correct prefix', () => { + const cache = getMockSyncCache<string>(); + cache.set('key1', 'value1'); + cache.set('key2', 'value2'); + cache.set('prefix:key3', 'value1'); + cache.set('prefix:key4', 'value2'); + + const prefixCache = new SyncPrefixStore(cache, 'prefix:', (v) => v, (v) => v); + + const keys = prefixCache.getKeys(); + expect(keys).toEqual(expect.arrayContaining(['key3', 'key4'])); + }); + + it('should work with empty prefix', () => { + const cache = getMockSyncCache<string>(); + cache.set('key1', 'value1'); + cache.set('key2', 'value2'); + + const prefixCache = new SyncPrefixStore(cache, '', (v) => v, (v) => v); + + const keys = prefixCache.getKeys(); + expect(keys).toEqual(expect.arrayContaining(['key1', 'key2'])); + }); + }); + + describe('getBatched', () => { + it('should return values with correct prefix', () => { + const cache = getMockSyncCache<string>(); + cache.set('key1', 'value1'); + cache.set('key2', 'value2'); + cache.set('key3', 'value3'); + cache.set('prefix:key1', 'prefix:value1'); + cache.set('prefix:key2', 'prefix:value2'); + + const prefixCache = new SyncPrefixStore(cache, 'prefix:', (v) => v, (v) => v); + + const values = prefixCache.getBatched(['key1', 'key2', 'key3']); + expect(values).toEqual(expect.arrayContaining(['prefix:value1', 'prefix:value2', undefined])); + }); + + it('should transform values after getting from the underlying cache', () => { + const cache = getMockSyncCache<string>(); + cache.set('key1', 'VALUE1'); + cache.set('key2', 'VALUE2'); + cache.set('key3', 'VALUE3'); + cache.set('prefix:key1', 'PREFIX:VALUE1'); + cache.set('prefix:key2', 'PREFIX:VALUE2'); + + const prefixCache = new SyncPrefixStore(cache, 'prefix:', (v) => v.toLocaleLowerCase(), (v) => v.toUpperCase()); + + const values = prefixCache.getBatched(['key1', 'key2', 'key3']); + expect(values).toEqual(expect.arrayContaining(['prefix:value1', 'prefix:value2', undefined])); + }); + + it('should work with empty prefix', () => { + const cache = getMockSyncCache<string>(); + cache.set('key1', 'value1'); + cache.set('key2', 'value2'); + + const prefixCache = new SyncPrefixStore(cache, '', (v) => v, (v) => v); + + const values = prefixCache.getBatched(['key1', 'key2']); + expect(values).toEqual(expect.arrayContaining(['value1', 'value2'])); + }); + }); +}); + +describe('AsyncPrefixStore', () => { + describe('set', () => { + it('should add prefix to key when setting in the underlying cache', async () => { + const cache = getMockAsyncCache<string>(); + const prefixCache = new AsyncPrefixStore(cache, 'prefix:', (v) => v, (v) => v); + await prefixCache.set('key', 'value'); + expect(await cache.get('prefix:key')).toEqual('value'); + }); + + it('should transform value when setting in the underlying cache', async () => { + const cache = getMockAsyncCache<string>(); + const prefixCache = new AsyncPrefixStore(cache, 'prefix:', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + await prefixCache.set('key', 'value'); + expect(await cache.get('prefix:key')).toEqual('VALUE'); + }); + + it('should work correctly with empty prefix', async () => { + const cache = getMockAsyncCache<string>(); + const prefixCache = new AsyncPrefixStore(cache, '', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + await prefixCache.set('key', 'value'); + expect(await cache.get('key')).toEqual('VALUE'); + }); + }); + + describe('get', () => { + it('should remove prefix from key when getting from the underlying cache', async () => { + const cache = getMockAsyncCache<string>(); + await cache.set('prefix:key', 'value'); + const prefixCache = new AsyncPrefixStore(cache, 'prefix:', (v) => v, (v) => v); + expect(await prefixCache.get('key')).toEqual('value'); + }); + + it('should transform value after getting from the underlying cache', async () => { + const cache = getMockAsyncCache<string>(); + const prefixCache = new AsyncPrefixStore(cache, 'prefix:', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + await cache.set('prefix:key', 'VALUE'); + expect(await prefixCache.get('key')).toEqual('value'); + }); + + + it('should work correctly with empty prefix', async () => { + const cache = getMockAsyncCache<string>(); + const prefixCache = new AsyncPrefixStore(cache, '', (v) => v.toLowerCase(), (v) => v.toUpperCase()); + await cache.set('key', 'VALUE'); + expect(await prefixCache.get('key')).toEqual('value'); + }); + }); + + describe('remove', () => { + it('should remove the correct value from the underlying cache', async () => { + const cache = getMockAsyncCache<string>(); + cache.set('prefix:key', 'value'); + cache.set('key', 'value'); + const prefixCache = new AsyncPrefixStore(cache, 'prefix:', (v) => v, (v) => v); + await prefixCache.remove('key'); + expect(await cache.get('prefix:key')).toBeUndefined(); + expect(await cache.get('key')).toEqual('value'); + }); + + it('should work with empty prefix', async () => { + const cache = getMockAsyncCache<string>(); + await cache.set('key', 'value'); + const prefixCache = new AsyncPrefixStore(cache, '', (v) => v, (v) => v); + await prefixCache.remove('key'); + expect(await cache.get('key')).toBeUndefined(); + }); + }); + + describe('getKeys', () => { + it('should return keys with correct prefix', async () => { + const cache = getMockAsyncCache<string>(); + await cache.set('key1', 'value1'); + await cache.set('key2', 'value2'); + await cache.set('prefix:key3', 'value1'); + await cache.set('prefix:key4', 'value2'); + + const prefixCache = new AsyncPrefixStore(cache, 'prefix:', (v) => v, (v) => v); + + const keys = await prefixCache.getKeys(); + expect(keys).toEqual(expect.arrayContaining(['key3', 'key4'])); + }); + + it('should work with empty prefix', async () => { + const cache = getMockAsyncCache<string>(); + await cache.set('key1', 'value1'); + await cache.set('key2', 'value2'); + + const prefixCache = new AsyncPrefixStore(cache, '', (v) => v, (v) => v); + + const keys = await prefixCache.getKeys(); + expect(keys).toEqual(expect.arrayContaining(['key1', 'key2'])); + }); + }); + + describe('getBatched', () => { + it('should return values with correct prefix', async () => { + const cache = getMockAsyncCache<string>(); + await cache.set('key1', 'value1'); + await cache.set('key2', 'value2'); + await cache.set('key3', 'value3'); + await cache.set('prefix:key1', 'prefix:value1'); + await cache.set('prefix:key2', 'prefix:value2'); + + const prefixCache = new AsyncPrefixStore(cache, 'prefix:', (v) => v, (v) => v); + + const values = await prefixCache.getBatched(['key1', 'key2', 'key3']); + expect(values).toEqual(expect.arrayContaining(['prefix:value1', 'prefix:value2', undefined])); + }); + + it('should transform values after getting from the underlying cache', async () => { + const cache = getMockAsyncCache<string>(); + await cache.set('key1', 'VALUE1'); + await cache.set('key2', 'VALUE2'); + await cache.set('key3', 'VALUE3'); + await cache.set('prefix:key1', 'PREFIX:VALUE1'); + await cache.set('prefix:key2', 'PREFIX:VALUE2'); + + const prefixCache = new AsyncPrefixStore(cache, 'prefix:', (v) => v.toLocaleLowerCase(), (v) => v.toUpperCase()); + + const values = await prefixCache.getBatched(['key1', 'key2', 'key3']); + expect(values).toEqual(expect.arrayContaining(['prefix:value1', 'prefix:value2', undefined])); + }); + + it('should work with empty prefix', async () => { + const cache = getMockAsyncCache<string>(); + await cache.set('key1', 'value1'); + await cache.set('key2', 'value2'); + + const prefixCache = new AsyncPrefixStore(cache, '', (v) => v, (v) => v); + + const values = await prefixCache.getBatched(['key1', 'key2']); + expect(values).toEqual(expect.arrayContaining(['value1', 'value2'])); + }); + }); +}); \ No newline at end of file diff --git a/lib/utils/cache/store.ts b/lib/utils/cache/store.ts new file mode 100644 index 000000000..c13852f65 --- /dev/null +++ b/lib/utils/cache/store.ts @@ -0,0 +1,174 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Transformer } from '../../utils/type'; +import { Maybe } from '../../utils/type'; +import { OpType, OpValue } from '../../utils/type'; + +export interface OpStore<OP extends OpType, V> { + operation: OP; + set(key: string, value: V): OpValue<OP, unknown>; + get(key: string): OpValue<OP, Maybe<V>>; + remove(key: string): OpValue<OP, unknown>; + getKeys(): OpValue<OP, string[]>; +} + +export type SyncStore<V> = OpStore<'sync', V>; +export type AsyncStore<V> = OpStore<'async', V>; +export type Store<V> = SyncStore<V> | AsyncStore<V>; + +export abstract class SyncStoreWithBatchedGet<V> implements SyncStore<V> { + operation = 'sync' as const; + abstract set(key: string, value: V): unknown; + abstract get(key: string): Maybe<V>; + abstract remove(key: string): unknown; + abstract getKeys(): string[]; + abstract getBatched(keys: string[]): Maybe<V>[]; +} + +export abstract class AsyncStoreWithBatchedGet<V> implements AsyncStore<V> { + operation = 'async' as const; + abstract set(key: string, value: V): Promise<unknown>; + abstract get(key: string): Promise<Maybe<V>>; + abstract remove(key: string): Promise<unknown>; + abstract getKeys(): Promise<string[]>; + abstract getBatched(keys: string[]): Promise<Maybe<V>[]>; +} + +export const getBatchedSync = <V>(store: SyncStore<V>, keys: string[]): Maybe<V>[] => { + if (store instanceof SyncStoreWithBatchedGet) { + return store.getBatched(keys); + } + return keys.map((key) => store.get(key)); +}; + +export const getBatchedAsync = <V>(store: AsyncStore<V>, keys: string[]): Promise<Maybe<V>[]> => { + if (store instanceof AsyncStoreWithBatchedGet) { + return store.getBatched(keys); + } + return Promise.all(keys.map((key) => store.get(key))); +}; + +export class SyncPrefixStore<U, V> extends SyncStoreWithBatchedGet<V> implements SyncStore<V> { + private store: SyncStore<U>; + private prefix: string; + private transformGet: Transformer<U, V>; + private transformSet: Transformer<V, U>; + + public readonly operation = 'sync'; + + constructor( + store: SyncStore<U>, + prefix: string, + transformGet: Transformer<U, V>, + transformSet: Transformer<V, U> + ) { + super(); + this.store = store; + this.prefix = prefix; + this.transformGet = transformGet; + this.transformSet = transformSet; + } + + private addPrefix(key: string): string { + return `${this.prefix}${key}`; + } + + private removePrefix(key: string): string { + return key.substring(this.prefix.length); + } + + set(key: string, value: V): unknown { + return this.store.set(this.addPrefix(key), this.transformSet(value)); + } + + get(key: string): V | undefined { + const value = this.store.get(this.addPrefix(key)); + return value ? this.transformGet(value) : undefined; + } + + remove(key: string): unknown { + return this.store.remove(this.addPrefix(key)); + } + + private getInternalKeys(): string[] { + return this.store.getKeys().filter((key) => key.startsWith(this.prefix)); + } + + getKeys(): string[] { + return this.getInternalKeys().map((key) => this.removePrefix(key)); + } + + getBatched(keys: string[]): Maybe<V>[] { + return getBatchedSync(this.store, keys.map((key) => this.addPrefix(key))) + .map((value) => value ? this.transformGet(value) : undefined); + } +} + +export class AsyncPrefixStore<U, V> implements AsyncStore<V> { + private cache: AsyncStore<U>; + private prefix: string; + private transformGet: Transformer<U, V>; + private transformSet: Transformer<V, U>; + + public readonly operation = 'async'; + + constructor( + cache: AsyncStore<U>, + prefix: string, + transformGet: Transformer<U, V>, + transformSet: Transformer<V, U> + ) { + this.cache = cache; + this.prefix = prefix; + this.transformGet = transformGet; + this.transformSet = transformSet; + } + + private addPrefix(key: string): string { + return `${this.prefix}${key}`; + } + + private removePrefix(key: string): string { + return key.substring(this.prefix.length); + } + + set(key: string, value: V): Promise<unknown> { + return this.cache.set(this.addPrefix(key), this.transformSet(value)); + } + + async get(key: string): Promise<V | undefined> { + const value = await this.cache.get(this.addPrefix(key)); + return value ? this.transformGet(value) : undefined; + } + + remove(key: string): Promise<unknown> { + return this.cache.remove(this.addPrefix(key)); + } + + private async getInternalKeys(): Promise<string[]> { + return this.cache.getKeys().then((keys) => keys.filter((key) => key.startsWith(this.prefix))); + } + + async getKeys(): Promise<string[]> { + return this.getInternalKeys().then((keys) => keys.map((key) => this.removePrefix(key))); + } + + async getBatched(keys: string[]): Promise<Maybe<V>[]> { + const values = await getBatchedAsync(this.cache, keys.map((key) => this.addPrefix(key))); + return values.map((value) => value ? this.transformGet(value) : undefined); + } +} diff --git a/lib/utils/cache/store_validator.ts b/lib/utils/cache/store_validator.ts new file mode 100644 index 000000000..949bb25c3 --- /dev/null +++ b/lib/utils/cache/store_validator.ts @@ -0,0 +1,36 @@ + +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +export const INVALID_STORE = 'Invalid store'; +export const INVALID_STORE_METHOD = 'Invalid store method %s'; + +// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types +export const validateStore = (store: any): void => { + const errors = []; + if (!store || typeof store !== 'object') { + throw new Error(INVALID_STORE); + } + + for (const method of ['set', 'get', 'remove', 'getKeys']) { + if (typeof store[method] !== 'function') { + errors.push(INVALID_STORE_METHOD.replace('%s', method)); + } + } + + if (errors.length > 0) { + throw new Error(errors.join(', ')); + } +} diff --git a/lib/utils/config_validator/index.spec.ts b/lib/utils/config_validator/index.spec.ts new file mode 100644 index 000000000..c8496ecc4 --- /dev/null +++ b/lib/utils/config_validator/index.spec.ts @@ -0,0 +1,65 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect } from 'vitest'; +import configValidator from './'; +import testData from '../../tests/test_data'; +import { INVALID_DATAFILE_MALFORMED, INVALID_DATAFILE_VERSION, NO_DATAFILE_SPECIFIED } from 'error_message'; +import { OptimizelyError } from '../../error/optimizly_error'; + +describe('validate', () => { + it('should complain if datafile is not provided', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(() => configValidator.validateDatafile()).toThrow(OptimizelyError); + + try { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + configValidator.validateDatafile(); + } catch (err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(NO_DATAFILE_SPECIFIED); + } + }); + + it('should complain if datafile is malformed', () => { + expect(() => configValidator.validateDatafile('abc')).toThrow( OptimizelyError); + + try { + configValidator.validateDatafile('abc'); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_DATAFILE_MALFORMED); + } + }); + + it('should complain if datafile version is not supported', () => { + expect(() => configValidator.validateDatafile(JSON.stringify(testData.getUnsupportedVersionConfig())).toThrow(OptimizelyError)); + + try { + configValidator.validateDatafile(JSON.stringify(testData.getUnsupportedVersionConfig())); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_DATAFILE_VERSION); + expect(err.params).toEqual(['5']); + } + }); + + it('should not complain if datafile is valid', () => { + expect(() => configValidator.validateDatafile(JSON.stringify(testData.getTestProjectConfig())).not.toThrowError()); + }); +}); diff --git a/packages/optimizely-sdk/lib/utils/config_validator/index.tests.js b/lib/utils/config_validator/index.tests.js similarity index 60% rename from packages/optimizely-sdk/lib/utils/config_validator/index.tests.js rename to lib/utils/config_validator/index.tests.js index 9ff0a32c5..2680a07da 100644 --- a/packages/optimizely-sdk/lib/utils/config_validator/index.tests.js +++ b/lib/utils/config_validator/index.tests.js @@ -17,52 +17,66 @@ import { assert } from 'chai'; import { sprintf } from '../../utils/fns'; import configValidator from './'; -import { ERROR_MESSAGES } from '../enums'; import testData from '../../tests/test_data'; +import { + INVALID_DATAFILE_MALFORMED, + INVALID_DATAFILE_VERSION, + INVALID_ERROR_HANDLER, + INVALID_EVENT_DISPATCHER, + INVALID_LOGGER, + NO_DATAFILE_SPECIFIED, +} from 'error_message'; describe('lib/utils/config_validator', function() { describe('APIs', function() { describe('validate', function() { - it('should complain if the provided error handler is invalid', function() { - assert.throws(function() { + it.skip('should complain if the provided error handler is invalid', function() { + const ex = assert.throws(function() { configValidator.validate({ errorHandler: {}, }); - }, sprintf(ERROR_MESSAGES.INVALID_ERROR_HANDLER, 'CONFIG_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_ERROR_HANDLER); }); - it('should complain if the provided event dispatcher is invalid', function() { - assert.throws(function() { + it.skip('should complain if the provided event dispatcher is invalid', function() { + const ex = assert.throws(function() { configValidator.validate({ eventDispatcher: {}, }); - }, sprintf(ERROR_MESSAGES.INVALID_EVENT_DISPATCHER, 'CONFIG_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_EVENT_DISPATCHER); }); - it('should complain if the provided logger is invalid', function() { - assert.throws(function() { + it.skip('should complain if the provided logger is invalid', function() { + const ex = assert.throws(function() { configValidator.validate({ logger: {}, }); - }, sprintf(ERROR_MESSAGES.INVALID_LOGGER, 'CONFIG_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_LOGGER); }); it('should complain if datafile is not provided', function() { - assert.throws(function() { + const ex = assert.throws(function() { configValidator.validateDatafile(); - }, sprintf(ERROR_MESSAGES.NO_DATAFILE_SPECIFIED, 'CONFIG_VALIDATOR')); + }); + assert.equal(ex.baseMessage, NO_DATAFILE_SPECIFIED); }); it('should complain if datafile is malformed', function() { - assert.throws(function() { + const ex = assert.throws(function() { configValidator.validateDatafile('abc'); - }, sprintf(ERROR_MESSAGES.INVALID_DATAFILE_MALFORMED, 'CONFIG_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_DATAFILE_MALFORMED); }); it('should complain if datafile version is not supported', function() { - assert.throws(function() { + const ex = assert.throws(function() { configValidator.validateDatafile(JSON.stringify(testData.getUnsupportedVersionConfig())); - }, sprintf(ERROR_MESSAGES.INVALID_DATAFILE_VERSION, 'CONFIG_VALIDATOR', '5')); + }); + assert.equal(ex.baseMessage, INVALID_DATAFILE_VERSION); + assert.deepEqual(ex.params, ['5']); }); it('should not complain if datafile is valid', function() { diff --git a/lib/utils/config_validator/index.ts b/lib/utils/config_validator/index.ts new file mode 100644 index 000000000..49c927f49 --- /dev/null +++ b/lib/utils/config_validator/index.ts @@ -0,0 +1,63 @@ +/** + * Copyright 2016, 2018-2020, 2022, 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + DATAFILE_VERSIONS, +} from '../enums'; +import { + INVALID_DATAFILE_MALFORMED, + INVALID_DATAFILE_VERSION, + NO_DATAFILE_SPECIFIED, +} from 'error_message'; +import { OptimizelyError } from '../../error/optimizly_error'; + +const SUPPORTED_VERSIONS = [DATAFILE_VERSIONS.V2, DATAFILE_VERSIONS.V3, DATAFILE_VERSIONS.V4]; + +/** + * Validates the datafile + * @param {Object|string} datafile + * @return {Object} The datafile object if the datafile is valid + * @throws If the datafile is not valid for any of the following reasons: + - The datafile string is undefined + - The datafile string cannot be parsed as a JSON object + - The datafile version is not supported + */ +// eslint-disable-next-line +export const validateDatafile = function(datafile: unknown): any { + if (!datafile) { + throw new OptimizelyError(NO_DATAFILE_SPECIFIED); + } + if (typeof datafile === 'string') { + // Attempt to parse the datafile string + try { + datafile = JSON.parse(datafile); + } catch (ex) { + throw new OptimizelyError(INVALID_DATAFILE_MALFORMED); + } + } + if (typeof datafile === 'object' && !Array.isArray(datafile) && datafile !== null) { + if (SUPPORTED_VERSIONS.indexOf(datafile['version' as keyof unknown]) === -1) { + throw new OptimizelyError(INVALID_DATAFILE_VERSION, datafile['version' as keyof unknown]); + } + } else { + throw new OptimizelyError(INVALID_DATAFILE_MALFORMED); + } + + return datafile; +}; + +export default { + validateDatafile: validateDatafile, +} diff --git a/lib/utils/enums/index.ts b/lib/utils/enums/index.ts new file mode 100644 index 000000000..fd76d47ec --- /dev/null +++ b/lib/utils/enums/index.ts @@ -0,0 +1,108 @@ +/** + * Copyright 2016-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Contains global enums used throughout the library + */ +export const LOG_LEVEL = { + NOTSET: 0, + DEBUG: 1, + INFO: 2, + WARNING: 3, + ERROR: 4, +}; + + +export const enum RESERVED_EVENT_KEYWORDS { + REVENUE = 'revenue', + VALUE = 'value', +} + +export const CONTROL_ATTRIBUTES = { + BOT_FILTERING: '$opt_bot_filtering', + BUCKETING_ID: '$opt_bucketing_id', + STICKY_BUCKETING_KEY: '$opt_experiment_bucket_map', + USER_AGENT: '$opt_user_agent', +}; + +export const JAVASCRIPT_CLIENT_ENGINE = 'javascript-sdk'; +export const NODE_CLIENT_ENGINE = 'node-sdk'; +export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; +export const CLIENT_VERSION = '6.2.0'; + +/* + * Represents the source of a decision for feature management. When a feature + * is accessed through isFeatureEnabled or getVariableValue APIs, the decision + * source is used to decide whether to dispatch an impression event to + * Optimizely. + */ +export const DECISION_SOURCES = { + FEATURE_TEST: 'feature-test', + ROLLOUT: 'rollout', + EXPERIMENT: 'experiment', + HOLDOUT: 'holdout', +} as const; + +export type DecisionSource = typeof DECISION_SOURCES[keyof typeof DECISION_SOURCES]; + +export const AUDIENCE_EVALUATION_TYPES = { + RULE: 'rule', + EXPERIMENT: 'experiment', +}; + +/* + * Possible types of variables attached to features + */ +export const FEATURE_VARIABLE_TYPES = { + BOOLEAN: 'boolean', + DOUBLE: 'double', + INTEGER: 'integer', + STRING: 'string', + JSON: 'json', +}; + +/* + * Supported datafile versions + */ +export const DATAFILE_VERSIONS = { + V2: '2', + V3: '3', + V4: '4', +}; + +/* + * Pre-Release and Build symbols + */ +export const enum VERSION_TYPE { + PRE_RELEASE_VERSION_DELIMITER = '-', + BUILD_VERSION_DELIMITER = '+', +} + +export const DECISION_MESSAGES = { + SDK_NOT_READY: 'Optimizely SDK not configured properly yet.', + FLAG_KEY_INVALID: 'No flag was found for key "%s".', + VARIABLE_VALUE_INVALID: 'Variable value for key "%s" is invalid or wrong type.', +}; + +/** + * Default milliseconds before request timeout + */ +export const REQUEST_TIMEOUT_MS = 60 * 1000; // 1 minute + +export const DEFAULT_CMAB_CACHE_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes +export const DEFAULT_CMAB_CACHE_SIZE = 10_000; +export const DEFAULT_CMAB_RETRIES = 1; +export const DEFAULT_CMAB_BACKOFF_MS = 100; // 100 milliseconds diff --git a/lib/utils/event_emitter/event_emitter.spec.ts b/lib/utils/event_emitter/event_emitter.spec.ts new file mode 100644 index 000000000..fb5cfe441 --- /dev/null +++ b/lib/utils/event_emitter/event_emitter.spec.ts @@ -0,0 +1,101 @@ +/** + * Copyright 2024 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { it, vi, expect } from 'vitest'; + +import { EventEmitter } from './event_emitter'; + +it('should call all registered listeners correctly on emit event', () => { + const emitter = new EventEmitter<{ foo: number, bar: string, baz: boolean}>(); + const fooListener1 = vi.fn(); + const fooListener2 = vi.fn(); + + emitter.on('foo', fooListener1); + emitter.on('foo', fooListener2); + + const barListener1 = vi.fn(); + const barListener2 = vi.fn(); + + emitter.on('bar', barListener1); + emitter.on('bar', barListener2); + + const bazListener = vi.fn(); + emitter.on('baz', bazListener); + + emitter.emit('foo', 1); + emitter.emit('bar', 'hello'); + + expect(fooListener1).toHaveBeenCalledOnce(); + expect(fooListener1).toHaveBeenCalledWith(1); + expect(fooListener2).toHaveBeenCalledOnce(); + expect(fooListener2).toHaveBeenCalledWith(1); + expect(barListener1).toHaveBeenCalledOnce(); + expect(barListener1).toHaveBeenCalledWith('hello'); + expect(barListener2).toHaveBeenCalledOnce(); + expect(barListener2).toHaveBeenCalledWith('hello'); + + expect(bazListener).not.toHaveBeenCalled(); +}); + +it('should remove listeners correctly when the function returned from on is called', () => { + const emitter = new EventEmitter<{ foo: number, bar: string }>(); + const fooListener1 = vi.fn(); + const fooListener2 = vi.fn(); + + const dispose = emitter.on('foo', fooListener1); + emitter.on('foo', fooListener2); + + const barListener1 = vi.fn(); + const barListener2 = vi.fn(); + + emitter.on('bar', barListener1); + emitter.on('bar', barListener2); + + dispose(); + emitter.emit('foo', 1); + emitter.emit('bar', 'hello'); + + expect(fooListener1).not.toHaveBeenCalled(); + expect(fooListener2).toHaveBeenCalledOnce(); + expect(fooListener2).toHaveBeenCalledWith(1); + expect(barListener1).toHaveBeenCalledWith('hello'); + expect(barListener1).toHaveBeenCalledWith('hello'); +}) + +it('should remove all listeners when removeAllListeners() is called', () => { + const emitter = new EventEmitter<{ foo: number, bar: string, baz: boolean}>(); + const fooListener1 = vi.fn(); + const fooListener2 = vi.fn(); + + emitter.on('foo', fooListener1); + emitter.on('foo', fooListener2); + + const barListener1 = vi.fn(); + const barListener2 = vi.fn(); + + emitter.on('bar', barListener1); + emitter.on('bar', barListener2); + + emitter.removeAllListeners(); + + emitter.emit('foo', 1); + emitter.emit('bar', 'hello'); + + expect(fooListener1).not.toHaveBeenCalled(); + expect(fooListener2).not.toHaveBeenCalled(); + expect(barListener1).not.toHaveBeenCalled(); + expect(barListener2).not.toHaveBeenCalled(); +}); diff --git a/lib/utils/event_emitter/event_emitter.ts b/lib/utils/event_emitter/event_emitter.ts new file mode 100644 index 000000000..6bfa57f8d --- /dev/null +++ b/lib/utils/event_emitter/event_emitter.ts @@ -0,0 +1,57 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Fn } from "../type"; + +type Consumer<T> = (arg: T) => void; + +type Listeners<T> = { + [Key in keyof T]?: Map<number, Consumer<T[Key]>>; +}; + +export class EventEmitter<T> { + private id = 0; + private listeners: Listeners<T> = {} as Listeners<T>; + + on<E extends keyof T>(eventName: E, listener: Consumer<T[E]>): Fn { + if (!this.listeners[eventName]) { + this.listeners[eventName] = new Map(); + } + + const curId = this.id++; + this.listeners[eventName]?.set(curId, listener); + return () => { + this.listeners[eventName]?.delete(curId); + } + } + + emit<E extends keyof T>(eventName: E, data: T[E]): void { + const listeners = this.listeners[eventName]; + if (listeners) { + listeners.forEach(listener => { + listener(data); + }); + } + } + + removeListeners<E extends keyof T>(eventName: E): void { + this.listeners[eventName]?.clear(); + } + + removeAllListeners(): void { + this.listeners = {}; + } +} diff --git a/lib/utils/event_tag_utils/index.spec.ts b/lib/utils/event_tag_utils/index.spec.ts new file mode 100644 index 000000000..a1208b601 --- /dev/null +++ b/lib/utils/event_tag_utils/index.spec.ts @@ -0,0 +1,88 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, beforeEach } from 'vitest'; +import * as eventTagUtils from './'; +import { + FAILED_TO_PARSE_REVENUE, + PARSED_REVENUE_VALUE, + PARSED_NUMERIC_VALUE, + FAILED_TO_PARSE_VALUE, +} from 'log_message'; +import { getMockLogger } from '../../tests/mock/mock_logger'; +import { LoggerFacade } from '../../logging/logger'; + +describe('getRevenueValue', () => { + let logger: LoggerFacade; + + beforeEach(() => { + logger = getMockLogger(); + }); + + it('should return the parseed integer for a valid revenue value', () => { + let parsedRevenueValue = eventTagUtils.getRevenueValue({ revenue: '1337' }, logger); + + expect(parsedRevenueValue).toBe(1337); + expect(logger.info).toHaveBeenCalledWith(PARSED_REVENUE_VALUE, 1337); + + parsedRevenueValue = eventTagUtils.getRevenueValue({ revenue: '13.37' }, logger); + + expect(parsedRevenueValue).toBe(13); + }); + + it('should return null and log a message for invalid value', () => { + const parsedRevenueValue = eventTagUtils.getRevenueValue({ revenue: 'invalid' }, logger); + + expect(parsedRevenueValue).toBe(null); + expect(logger.info).toHaveBeenCalledWith(FAILED_TO_PARSE_REVENUE, 'invalid'); + }); + + it('should return null if the revenue value is not present in the event tags', () => { + const parsedRevenueValue = eventTagUtils.getRevenueValue({ not_revenue: '1337' }, logger); + + expect(parsedRevenueValue).toBe(null); + }); +}); + +describe('getEventValue', () => { + let logger: LoggerFacade; + + beforeEach(() => { + logger = getMockLogger(); + }); + + it('should return the parsed integer for a valid numeric value', () => { + let parsedEventValue = eventTagUtils.getEventValue({ value: '1337' }, logger); + + expect(parsedEventValue).toBe(1337); + expect(logger.info).toHaveBeenCalledWith(PARSED_NUMERIC_VALUE, 1337); + + parsedEventValue = eventTagUtils.getEventValue({ value: '13.37' }, logger); + expect(parsedEventValue).toBe(13.37); + }); + + it('should return null and log a message for invalid value', () => { + const parsedNumericValue = eventTagUtils.getEventValue({ value: 'invalid' }, logger); + + expect(parsedNumericValue).toBe(null); + expect(logger.info).toHaveBeenCalledWith(FAILED_TO_PARSE_VALUE, 'invalid'); + }); + + it('should return null if the value is not present in the event tags', () => { + const parsedNumericValue = eventTagUtils.getEventValue({ not_value: '13.37' }, logger); + + expect(parsedNumericValue).toBe(null); + }); +}) diff --git a/packages/optimizely-sdk/lib/utils/event_tag_utils/index.tests.js b/lib/utils/event_tag_utils/index.tests.js similarity index 80% rename from packages/optimizely-sdk/lib/utils/event_tag_utils/index.tests.js rename to lib/utils/event_tag_utils/index.tests.js index 5f7fc2bcc..f1f81a1fb 100644 --- a/packages/optimizely-sdk/lib/utils/event_tag_utils/index.tests.js +++ b/lib/utils/event_tag_utils/index.tests.js @@ -18,15 +18,23 @@ import { assert } from 'chai'; import { sprintf } from '../../utils/fns'; import * as eventTagUtils from './'; +import { FAILED_TO_PARSE_REVENUE, PARSED_REVENUE_VALUE, PARSED_NUMERIC_VALUE, FAILED_TO_PARSE_VALUE } from 'log_message'; var buildLogMessageFromArgs = args => sprintf(args[1], ...args.splice(2)); +var createLogger = () => ({ + debug: () => {}, + info: () => {}, + warn: () => {}, + error: () => {}, + child: () => createLogger(), +}) + describe('lib/utils/event_tag_utils', function() { var mockLogger; beforeEach(function() { - mockLogger = { - log: sinon.stub(), - }; + mockLogger = createLogger(); + sinon.stub(mockLogger, 'info'); }); describe('APIs', function() { @@ -41,8 +49,9 @@ describe('lib/utils/event_tag_utils', function() { ); assert.strictEqual(parsedRevenueValue, 1337); - var logMessage = buildLogMessageFromArgs(mockLogger.log.args[0]); - assert.strictEqual(logMessage, 'EVENT_TAG_UTILS: Parsed revenue value "1337" from event tags.'); + + assert.strictEqual(mockLogger.info.args[0][0], PARSED_REVENUE_VALUE); + assert.strictEqual(mockLogger.info.args[0][1], 1337); // test out a float parsedRevenueValue = eventTagUtils.getRevenueValue( @@ -67,8 +76,8 @@ describe('lib/utils/event_tag_utils', function() { assert.strictEqual(parsedRevenueValue, null); - var logMessage = buildLogMessageFromArgs(mockLogger.log.args[0]); - assert.strictEqual(logMessage, 'EVENT_TAG_UTILS: Failed to parse revenue value "invalid" from event tags.'); + assert.strictEqual(mockLogger.info.args[0][0], FAILED_TO_PARSE_REVENUE); + assert.strictEqual(mockLogger.info.args[0][1], 'invalid'); }); }); @@ -97,8 +106,9 @@ describe('lib/utils/event_tag_utils', function() { ); assert.strictEqual(parsedEventValue, 1337); - var logMessage = buildLogMessageFromArgs(mockLogger.log.args[0]); - assert.strictEqual(logMessage, 'EVENT_TAG_UTILS: Parsed event value "1337" from event tags.'); + + assert.strictEqual(mockLogger.info.args[0][0], PARSED_NUMERIC_VALUE); + assert.strictEqual(mockLogger.info.args[0][1], 1337); // test out a float parsedEventValue = eventTagUtils.getEventValue( @@ -123,8 +133,8 @@ describe('lib/utils/event_tag_utils', function() { assert.strictEqual(parsedEventValue, null); - var logMessage = buildLogMessageFromArgs(mockLogger.log.args[0]); - assert.strictEqual(logMessage, 'EVENT_TAG_UTILS: Failed to parse event value "invalid" from event tags.'); + assert.strictEqual(mockLogger.info.args[0][0], FAILED_TO_PARSE_VALUE); + assert.strictEqual(mockLogger.info.args[0][1], 'invalid'); }); }); diff --git a/lib/utils/event_tag_utils/index.ts b/lib/utils/event_tag_utils/index.ts new file mode 100644 index 000000000..d50292a39 --- /dev/null +++ b/lib/utils/event_tag_utils/index.ts @@ -0,0 +1,83 @@ +/** + * Copyright 2017, 2019-2020, 2022-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + FAILED_TO_PARSE_REVENUE, + FAILED_TO_PARSE_VALUE, + PARSED_NUMERIC_VALUE, + PARSED_REVENUE_VALUE, +} from 'log_message'; +import { LoggerFacade } from '../../logging/logger'; + +import { RESERVED_EVENT_KEYWORDS } from '../enums'; +import { EventTags } from '../../shared_types'; + +/** + * Provides utility method for parsing event tag values + */ +const REVENUE_EVENT_METRIC_NAME = RESERVED_EVENT_KEYWORDS.REVENUE; +const VALUE_EVENT_METRIC_NAME = RESERVED_EVENT_KEYWORDS.VALUE; + +/** + * Grab the revenue value from the event tags. "revenue" is a reserved keyword. + * @param {EventTags} eventTags + * @param {LoggerFacade} logger + * @return {number|null} + */ +export function getRevenueValue(eventTags: EventTags, logger?: LoggerFacade): number | null { + const rawValue = eventTags[REVENUE_EVENT_METRIC_NAME]; + + if (rawValue == null) { + // null or undefined event values + return null; + } + + const parsedRevenueValue = typeof rawValue === 'string' ? parseInt(rawValue) : Math.trunc(rawValue); + + if (isFinite(parsedRevenueValue)) { + logger?.info(PARSED_REVENUE_VALUE, parsedRevenueValue); + return parsedRevenueValue; + } else { + // NaN, +/- infinity values + logger?.info(FAILED_TO_PARSE_REVENUE, rawValue); + return null; + } +} + +/** + * Grab the event value from the event tags. "value" is a reserved keyword. + * @param {EventTags} eventTags + * @param {LoggerFacade} logger + * @return {number|null} + */ +export function getEventValue(eventTags: EventTags, logger?: LoggerFacade): number | null { + const rawValue = eventTags[VALUE_EVENT_METRIC_NAME]; + + if (rawValue == null) { + // null or undefined event values + return null; + } + + const parsedEventValue = typeof rawValue === 'string' ? parseFloat(rawValue) : rawValue; + + if (isFinite(parsedEventValue)) { + logger?.info(PARSED_NUMERIC_VALUE, parsedEventValue); + return parsedEventValue; + } else { + // NaN, +/- infinity values + logger?.info(FAILED_TO_PARSE_VALUE, rawValue); + return null; + } +} diff --git a/lib/utils/event_tags_validator/index.spec.ts b/lib/utils/event_tags_validator/index.spec.ts new file mode 100644 index 000000000..1b372ff0a --- /dev/null +++ b/lib/utils/event_tags_validator/index.spec.ts @@ -0,0 +1,63 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, beforeEach } from 'vitest'; +import { validate } from '.'; +import { OptimizelyError } from '../../error/optimizly_error'; +import { INVALID_EVENT_TAGS } from 'error_message'; + +describe('validate', () => { + it('should validate the given event tags if event tag is an object', () => { + expect(validate({ testAttribute: 'testValue' })).toBe(true); + }); + + it('should throw an error if event tags is an array', () => { + const eventTagsArray = ['notGonnaWork']; + + expect(() => validate(eventTagsArray)).toThrow(OptimizelyError) + + try { + validate(eventTagsArray); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_EVENT_TAGS); + } + }); + + it('should throw an error if event tags is null', () => { + expect(() => validate(null)).toThrow(OptimizelyError); + + try { + validate(null); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_EVENT_TAGS); + } + }); + + it('should throw an error if event tags is a function', () => { + function invalidInput() { + console.log('This is an invalid input!'); + } + expect(() => validate(invalidInput)).toThrow(OptimizelyError); + + try { + validate(invalidInput); + } catch(err) { + expect(err).toBeInstanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_EVENT_TAGS); + } + }); +}); diff --git a/packages/optimizely-sdk/lib/utils/event_tags_validator/index.tests.js b/lib/utils/event_tags_validator/index.tests.js similarity index 77% rename from packages/optimizely-sdk/lib/utils/event_tags_validator/index.tests.js rename to lib/utils/event_tags_validator/index.tests.js index 45dc75123..c18585d50 100644 --- a/packages/optimizely-sdk/lib/utils/event_tags_validator/index.tests.js +++ b/lib/utils/event_tags_validator/index.tests.js @@ -14,10 +14,9 @@ * limitations under the License. */ import { assert } from 'chai'; -import { sprintf } from '../../utils/fns'; import { validate } from './'; -import { ERROR_MESSAGES } from'../enums'; +import { INVALID_EVENT_TAGS } from 'error_message'; describe('lib/utils/event_tags_validator', function() { describe('APIs', function() { @@ -28,24 +27,27 @@ describe('lib/utils/event_tags_validator', function() { it('should throw an error if event tags is an array', function() { var eventTagsArray = ['notGonnaWork']; - assert.throws(function() { + const ex = assert.throws(function() { validate(eventTagsArray); - }, sprintf(ERROR_MESSAGES.INVALID_EVENT_TAGS, 'EVENT_TAGS_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_EVENT_TAGS); }); it('should throw an error if event tags is null', function() { - assert.throws(function() { + const ex = assert.throws(function() { validate(null); - }, sprintf(ERROR_MESSAGES.INVALID_EVENT_TAGS, 'EVENT_TAGS_VALIDATOR')); + }) + assert.equal(ex.baseMessage, INVALID_EVENT_TAGS); }); it('should throw an error if event tags is a function', function() { function invalidInput() { console.log('This is an invalid input!'); } - assert.throws(function() { + const ex = assert.throws(function() { validate(invalidInput); - }, sprintf(ERROR_MESSAGES.INVALID_EVENT_TAGS, 'EVENT_TAGS_VALIDATOR')); + }); + assert.equal(ex.baseMessage, INVALID_EVENT_TAGS); }); }); }); diff --git a/packages/optimizely-sdk/lib/utils/event_tags_validator/index.ts b/lib/utils/event_tags_validator/index.ts similarity index 83% rename from packages/optimizely-sdk/lib/utils/event_tags_validator/index.ts rename to lib/utils/event_tags_validator/index.ts index f2294dda0..421321f69 100644 --- a/packages/optimizely-sdk/lib/utils/event_tags_validator/index.ts +++ b/lib/utils/event_tags_validator/index.ts @@ -17,11 +17,8 @@ /** * Provides utility method for validating that event tags user has provided are valid */ -import { sprintf } from '../../utils/fns'; - -import { ERROR_MESSAGES } from '../enums'; - -const MODULE_NAME = 'EVENT_TAGS_VALIDATOR'; +import { OptimizelyError } from '../../error/optimizly_error'; +import { INVALID_EVENT_TAGS } from 'error_message'; /** * Validates user's provided event tags @@ -33,6 +30,6 @@ export function validate(eventTags: unknown): boolean { if (typeof eventTags === 'object' && !Array.isArray(eventTags) && eventTags !== null) { return true; } else { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_EVENT_TAGS, MODULE_NAME)); + throw new OptimizelyError(INVALID_EVENT_TAGS); } } diff --git a/lib/utils/executor/backoff_retry_runner.spec.ts b/lib/utils/executor/backoff_retry_runner.spec.ts new file mode 100644 index 000000000..a1dd1f3a3 --- /dev/null +++ b/lib/utils/executor/backoff_retry_runner.spec.ts @@ -0,0 +1,139 @@ +import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { runWithRetry } from './backoff_retry_runner'; +import { advanceTimersByTime } from '../../tests/testUtils'; + +const exhaustMicrotasks = async (loop = 100) => { + for(let i = 0; i < loop; i++) { + await Promise.resolve(); + } +} + +describe('runWithRetry', () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('should return the result of the task if it succeeds in first try', async () => { + const task = async () => 1; + const { result } = runWithRetry(task); + expect(await result).toBe(1); + }); + + it('should retry the task if it fails', async () => { + let count = 0; + const task = async () => { + count++; + if (count === 1) { + throw new Error('error'); + } + return 1; + }; + const { result } = runWithRetry(task); + + await exhaustMicrotasks(); + await advanceTimersByTime(0); + + expect(await result).toBe(1); + }); + + it('should retry the task up to the maxRetries before failing', async () => { + let count = 0; + const task = async () => { + count++; + throw new Error('error'); + }; + const { result } = runWithRetry(task, undefined, 5); + + for(let i = 0; i < 5; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(0); + } + + try { + await result; + } catch (e) { + expect(count).toBe(6); + } + }); + + it('should retry idefinitely if maxRetries is undefined', async () => { + let count = 0; + const task = async () => { + count++; + if (count < 500) { + throw new Error('error'); + } + return 1; + }; + + const { result } = runWithRetry(task); + + for(let i = 0; i < 500; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(0); + } + expect(await result).toBe(1); + expect(count).toBe(500); + }); + + it('should use the backoff controller to delay retries', async () => { + const task = vi.fn().mockImplementation(async () => { + throw new Error('error'); + }); + + const delays = [7, 13, 19, 20, 27]; + + let backoffCount = 0; + const backoff = { + backoff: () => { + return delays[backoffCount++]; + }, + reset: () => {}, + }; + + const { result } = runWithRetry(task, backoff, 5); + result.catch(() => {}); + + expect(task).toHaveBeenCalledTimes(1); + + for(let i = 1; i <= 5; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(delays[i - 1] - 1); + expect(task).toHaveBeenCalledTimes(i); + await advanceTimersByTime(1); + expect(task).toHaveBeenCalledTimes(i + 1); + } + }); + + it('should cancel the retry if the cancel function is called', async () => { + let count = 0; + const task = async () => { + count++; + throw new Error('error'); + }; + + const { result, cancelRetry } = runWithRetry(task, undefined, 100); + + for(let i = 0; i < 5; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(0); + } + + cancelRetry(); + + for(let i = 0; i < 100; i++) { + await exhaustMicrotasks(); + await advanceTimersByTime(0); + } + + try { + await result; + } catch (e) { + expect(count).toBe(6); + } + }); +}); diff --git a/lib/utils/executor/backoff_retry_runner.ts b/lib/utils/executor/backoff_retry_runner.ts new file mode 100644 index 000000000..f0b185a99 --- /dev/null +++ b/lib/utils/executor/backoff_retry_runner.ts @@ -0,0 +1,54 @@ +import { OptimizelyError } from "../../error/optimizly_error"; +import { RETRY_CANCELLED } from "error_message"; +import { resolvablePromise, ResolvablePromise } from "../promise/resolvablePromise"; +import { BackoffController } from "../repeater/repeater"; +import { AsyncProducer, Fn } from "../type"; + +export type RunResult<T> = { + result: Promise<T>; + cancelRetry: Fn; +}; + +type CancelSignal = { + cancelled: boolean; +} + +const runTask = <T>( + task: AsyncProducer<T>, + returnPromise: ResolvablePromise<T>, + cancelSignal: CancelSignal, + backoff?: BackoffController, + retryRemaining?: number, +): void => { + task().then((res) => { + returnPromise.resolve(res); + }).catch((e) => { + if (retryRemaining === 0) { + returnPromise.reject(e); + return; + } + if (cancelSignal.cancelled) { + returnPromise.reject(new OptimizelyError(RETRY_CANCELLED)); + return; + } + const delay = backoff?.backoff() ?? 0; + setTimeout(() => { + retryRemaining = retryRemaining === undefined ? undefined : retryRemaining - 1; + runTask(task, returnPromise, cancelSignal, backoff, retryRemaining); + }, delay); + }); +} + +export const runWithRetry = <T>( + task: AsyncProducer<T>, + backoff?: BackoffController, + maxRetries?: number +): RunResult<T> => { + const returnPromise = resolvablePromise<T>(); + const cancelSignal = { cancelled: false }; + const cancelRetry = () => { + cancelSignal.cancelled = true; + } + runTask(task, returnPromise, cancelSignal, backoff, maxRetries); + return { cancelRetry, result: returnPromise.promise }; +} diff --git a/lib/utils/executor/serial_runner.spec.ts b/lib/utils/executor/serial_runner.spec.ts new file mode 100644 index 000000000..6456735a9 --- /dev/null +++ b/lib/utils/executor/serial_runner.spec.ts @@ -0,0 +1,143 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect, beforeEach } from 'vitest'; + +import { SerialRunner } from './serial_runner'; +import { resolvablePromise } from '../promise/resolvablePromise'; +import { exhaustMicrotasks } from '../../tests/testUtils'; + +describe('SerialRunner', () => { + let serialRunner: SerialRunner; + + beforeEach(() => { + serialRunner = new SerialRunner(); + }); + + it('should return result from a single async function', async () => { + const fn = () => Promise.resolve('result'); + + const result = await serialRunner.run(fn); + + expect(result).toBe('result'); + }); + + it('should reject with same error when the passed function rejects', async () => { + const error = new Error('test error'); + const fn = () => Promise.reject(error); + + await expect(serialRunner.run(fn)).rejects.toThrow(error); + }); + + it('should execute multiple async functions in order', async () => { + const executionOrder: number[] = []; + const promises = [resolvablePromise(), resolvablePromise(), resolvablePromise()]; + + const createTask = (id: number) => async () => { + executionOrder.push(id); + await promises[id]; + return id; + }; + + const results = [serialRunner.run(createTask(0)), serialRunner.run(createTask(1)), serialRunner.run(createTask(2))]; + + // only first task should have started + await exhaustMicrotasks(); + expect(executionOrder).toEqual([0]); + + // Resolve first task - second should start + promises[0].resolve(''); + await exhaustMicrotasks(); + expect(executionOrder).toEqual([0, 1]); + + // Resolve second task - third should start + promises[1].resolve(''); + await exhaustMicrotasks(); + expect(executionOrder).toEqual([0, 1, 2]); + + // Resolve third task - all done + promises[2].resolve(''); + + // Verify all results are correct + expect(await results[0]).toBe(0); + expect(await results[1]).toBe(1); + expect(await results[2]).toBe(2); + }); + + it('should continue execution even if one function throws an error', async () => { + const executionOrder: number[] = []; + const promises = [resolvablePromise(), resolvablePromise(), resolvablePromise()]; + + const createTask = (id: number) => async () => { + executionOrder.push(id); + await promises[id]; + return id; + }; + + const results = [serialRunner.run(createTask(0)), serialRunner.run(createTask(1)), serialRunner.run(createTask(2))]; + + // only first task should have started + await exhaustMicrotasks(); + expect(executionOrder).toEqual([0]); + + // reject first task - second should still start + promises[0].reject(new Error('first error')); + await exhaustMicrotasks(); + expect(executionOrder).toEqual([0, 1]); + + // reject second task - third should still start + promises[1].reject(new Error('second error')); + await exhaustMicrotasks(); + expect(executionOrder).toEqual([0, 1, 2]); + + // Resolve third task - all done + promises[2].resolve(''); + + // Verify results - first and third succeed, second fails + await expect(results[0]).rejects.toThrow('first error'); + await expect(results[1]).rejects.toThrow('second error'); + await expect(results[2]).resolves.toBe(2); + }); + + it('should handle functions that return different types', async () => { + const numberFn = () => Promise.resolve(42); + const stringFn = () => Promise.resolve('hello'); + const objectFn = () => Promise.resolve({ key: 'value' }); + const arrayFn = () => Promise.resolve([1, 2, 3]); + const booleanFn = () => Promise.resolve(true); + const nullFn = () => Promise.resolve(null); + const undefinedFn = () => Promise.resolve(undefined); + + const results = await Promise.all([ + serialRunner.run(numberFn), + serialRunner.run(stringFn), + serialRunner.run(objectFn), + serialRunner.run(arrayFn), + serialRunner.run(booleanFn), + serialRunner.run(nullFn), + serialRunner.run(undefinedFn), + ]); + + expect(results).toEqual([42, 'hello', { key: 'value' }, [1, 2, 3], true, null, undefined]); + }); + + it('should handle empty function that returns undefined', async () => { + const emptyFn = () => Promise.resolve(undefined); + + const result = await serialRunner.run(emptyFn); + + expect(result).toBeUndefined(); + }); +}); \ No newline at end of file diff --git a/lib/utils/executor/serial_runner.ts b/lib/utils/executor/serial_runner.ts new file mode 100644 index 000000000..243cae0b1 --- /dev/null +++ b/lib/utils/executor/serial_runner.ts @@ -0,0 +1,36 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AsyncProducer } from "../type"; + +class SerialRunner { + private waitPromise: Promise<unknown> = Promise.resolve(); + + // each call to serialize adds a new function to the end of the promise chain + // the function is called when the previous promise resolves + // if the function throws, the error is caught and ignored to allow the chain to continue + // the result of the function is returned as a promise + // if multiple calls to serialize are made, they will be executed in order + // even if some of them throw errors + + run<T>(fn: AsyncProducer<T>): Promise<T> { + const resultPromise = this.waitPromise.then(fn); + this.waitPromise = resultPromise.catch(() => {}); + return resultPromise; + } +} + +export { SerialRunner }; diff --git a/packages/optimizely-sdk/tests/utils.spec.ts b/lib/utils/fns/index.spec.ts similarity index 57% rename from packages/optimizely-sdk/tests/utils.spec.ts rename to lib/utils/fns/index.spec.ts index a54188673..1eec0d746 100644 --- a/packages/optimizely-sdk/tests/utils.spec.ts +++ b/lib/utils/fns/index.spec.ts @@ -1,23 +1,8 @@ -/// <reference types="jest" /> -import { isValidEnum, groupBy, objectEntries, objectValues, find, keyByUtil, sprintf } from '../lib/utils/fns' +import { describe, it, expect } from 'vitest'; -describe('utils', () => { - describe('isValidEnum', () => { - enum myEnum { - FOO = 0, - BAR = 1, - } - - it('should return false when not valid', () => { - expect(isValidEnum(myEnum, 2)).toBe(false) - }) - - it('should return true when valid', () => { - expect(isValidEnum(myEnum, 1)).toBe(true) - expect(isValidEnum(myEnum, myEnum.FOO)).toBe(true) - }) - }) +import { groupBy, objectEntries, objectValues, find, sprintf, keyBy, assignBy } from '.' +describe('utils', () => { describe('groupBy', () => { it('should group values by some key function', () => { const input = [ @@ -83,12 +68,84 @@ describe('utils', () => { { key: 'baz', firstName: 'james', lastName: 'foxy' }, ] - expect(keyByUtil(input, item => item.key)).toEqual({ + expect(keyBy(input, 'key')).toEqual({ + foo: { key: 'foo', firstName: 'jordan', lastName: 'foo' }, + bar: { key: 'bar', firstName: 'jordan', lastName: 'bar' }, + baz: { key: 'baz', firstName: 'james', lastName: 'foxy' }, + }) + }) + }) + + describe('assignBy', () => { + it('should assign array elements to an object using the specified key', () => { + const input = [ + { key: 'foo', firstName: 'jordan', lastName: 'foo' }, + { key: 'bar', firstName: 'jordan', lastName: 'bar' }, + { key: 'baz', firstName: 'james', lastName: 'foxy' }, + ] + const base = {} + + assignBy(input, 'key', base) + + expect(base).toEqual({ foo: { key: 'foo', firstName: 'jordan', lastName: 'foo' }, bar: { key: 'bar', firstName: 'jordan', lastName: 'bar' }, baz: { key: 'baz', firstName: 'james', lastName: 'foxy' }, }) }) + + it('should append to an existing object', () => { + const input = [ + { key: 'foo', firstName: 'jordan', lastName: 'foo' }, + { key: 'bar', firstName: 'jordan', lastName: 'bar' }, + ] + const base: any = { existing: 'value' } + + assignBy(input, 'key', base) + + expect(base).toEqual({ + existing: 'value', + foo: { key: 'foo', firstName: 'jordan', lastName: 'foo' }, + bar: { key: 'bar', firstName: 'jordan', lastName: 'bar' }, + }) + }) + + it('should handle empty array', () => { + const base: any = { existing: 'value' } + + assignBy([], 'key', base) + + expect(base).toEqual({ existing: 'value' }) + }) + + it('should handle null/undefined array', () => { + const base: any = { existing: 'value' } + + assignBy(null as any, 'key', base) + expect(base).toEqual({ existing: 'value' }) + + assignBy(undefined as any, 'key', base) + expect(base).toEqual({ existing: 'value' }) + }) + + it('should override existing values with the same key', () => { + const input = [ + { key: 'foo', firstName: 'jordan', lastName: 'updated' }, + { key: 'bar', firstName: 'james', lastName: 'new' }, + ] + const base: any = { + foo: { key: 'foo', firstName: 'john', lastName: 'original' }, + existing: 'value' + } + + assignBy(input, 'key', base) + + expect(base).toEqual({ + existing: 'value', + foo: { key: 'foo', firstName: 'jordan', lastName: 'updated' }, + bar: { key: 'bar', firstName: 'james', lastName: 'new' }, + }) + }) }) describe('sprintf', () => { diff --git a/packages/optimizely-sdk/lib/utils/fns/index.tests.js b/lib/utils/fns/index.tests.js similarity index 81% rename from packages/optimizely-sdk/lib/utils/fns/index.tests.js rename to lib/utils/fns/index.tests.js index 0d07bdc16..f2be54fea 100644 --- a/packages/optimizely-sdk/lib/utils/fns/index.tests.js +++ b/lib/utils/fns/index.tests.js @@ -84,22 +84,5 @@ describe('lib/utils/fns', function() { assert.isFalse(fns.isNumber(null)); }); }); - - describe('assign', function() { - it('should return empty object when target is not provided', function() { - assert.deepEqual(fns.assign(), {}); - }); - - it('should copy correctly when Object.assign is available in environment', function() { - assert.deepEqual(fns.assign({ a: 'a'}, {b: 'b'}), { a: 'a', b: 'b' }); - }); - - it('should copy correctly when Object.assign is not available in environment', function() { - var originalAssign = Object.assign; - Object.assign = null; - assert.deepEqual(fns.assign({ a: 'a'}, {b: 'b'}, {c: 'c'}), { a: 'a', b: 'b', c: 'c' }); - Object.assign = originalAssign; - }); - }); }); }); diff --git a/lib/utils/fns/index.ts b/lib/utils/fns/index.ts new file mode 100644 index 000000000..5b07b3aad --- /dev/null +++ b/lib/utils/fns/index.ts @@ -0,0 +1,132 @@ +/** + * Copyright 2017, 2019-2020, 2022-2023, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { v4 } from 'uuid'; + +const MAX_SAFE_INTEGER_LIMIT = Math.pow(2, 53); + +export function currentTimestamp(): number { + return Math.round(new Date().getTime()); +} + +export function isSafeInteger(number: unknown): boolean { + return typeof number == 'number' && Math.abs(number) <= MAX_SAFE_INTEGER_LIMIT; +} + +export function keyBy<K>(arr: K[], key: string): Record<string, K> { + if (!arr) return {}; + + const base: Record<string, K> = {}; + assignBy(arr, key, base); + return base; +} + +export function assignBy<K>(arr: K[], key: string, base: Record<string, K>): void { + if (!arr) return; + + arr.forEach((e) => { + base[(e as any)[key]] = e; + }); +} + + +function isNumber(value: unknown): boolean { + return typeof value === 'number'; +} + +export function uuid(): string { + return v4(); +} + +export type Omit<T, K> = Pick<T, Exclude<keyof T, K>>; + +export function getTimestamp(): number { + return new Date().getTime(); +} + +export function groupBy<K>(arr: K[], grouperFn: (item: K) => string): Array<K[]> { + const grouper: { [key: string]: K[] } = {}; + + arr.forEach(item => { + const key = grouperFn(item); + grouper[key] = grouper[key] || []; + grouper[key].push(item); + }); + + return objectValues(grouper); +} + +export function objectValues<K>(obj: { [key: string]: K }): K[] { + return Object.keys(obj).map(key => obj[key]); +} + +export function objectEntries<K>(obj: { [key: string]: K }): [string, K][] { + return Object.keys(obj).map(key => [key, obj[key]]); +} + +export function find<K>(arr: K[], cond: (arg: K) => boolean): K | undefined { + let found; + + for (const item of arr) { + if (cond(item)) { + found = item; + break; + } + } + + return found; +} + +// TODO[OASIS-6649]: Don't use any type +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function sprintf(format: string, ...args: any[]): string { + let i = 0; + return format.replace(/%s/g, function() { + const arg = args[i++]; + const type = typeof arg; + if (type === 'function') { + return arg(); + } else if (type === 'string') { + return arg; + } else { + return String(arg); + } + }); +} + +/** + * Checks two string arrays for equality. + * @param arrayA First Array to be compared against. + * @param arrayB Second Array to be compared against. + * @returns {boolean} True if both arrays are equal, otherwise returns false. + */ +export function checkArrayEquality(arrayA: string[], arrayB: string[]): boolean { + return arrayA.length === arrayB.length && arrayA.every((item, index) => item === arrayB[index]); +} + +export default { + checkArrayEquality, + currentTimestamp, + isSafeInteger, + keyBy, + uuid, + isNumber, + getTimestamp, + groupBy, + objectValues, + objectEntries, + find, + sprintf, +}; diff --git a/packages/optimizely-sdk/lib/utils/http_request_handler/http.ts b/lib/utils/http_request_handler/http.ts similarity index 79% rename from packages/optimizely-sdk/lib/utils/http_request_handler/http.ts rename to lib/utils/http_request_handler/http.ts index 98c1cedc6..ca7e63ae3 100644 --- a/packages/optimizely-sdk/lib/utils/http_request_handler/http.ts +++ b/lib/utils/http_request_handler/http.ts @@ -1,5 +1,5 @@ /** - * Copyright 2019-2020, 2022 Optimizely + * Copyright 2019-2020, 2022, 2024 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ export interface Headers { * Simplified Response object containing only needed information */ export interface Response { - statusCode?: number; + statusCode: number; body: string; headers: Headers; } @@ -35,13 +35,14 @@ export interface Response { */ export interface AbortableRequest { abort(): void; - responsePromise: Promise<Response>; } +export type HttpMethod = 'GET' | 'HEAD' | 'POST' | 'PUT' | 'DELETE' | 'CONNECT' | 'OPTIONS' | 'TRACE' | 'PATCH'; + /** * Client that handles sending requests and receiving responses */ export interface RequestHandler { - makeRequest(requestUrl: string, headers: Headers, method: string, data?: string): AbortableRequest; + makeRequest(requestUrl: string, headers: Headers, method: HttpMethod, data?: string): AbortableRequest; } diff --git a/lib/utils/http_request_handler/http_util.ts b/lib/utils/http_request_handler/http_util.ts new file mode 100644 index 000000000..c38217a40 --- /dev/null +++ b/lib/utils/http_request_handler/http_util.ts @@ -0,0 +1,4 @@ + +export const isSuccessStatusCode = (statusCode: number): boolean => { + return statusCode >= 200 && statusCode < 400; +} diff --git a/packages/optimizely-sdk/tests/browserRequestHandler.spec.ts b/lib/utils/http_request_handler/request_handler.browser.spec.ts similarity index 89% rename from packages/optimizely-sdk/tests/browserRequestHandler.spec.ts rename to lib/utils/http_request_handler/request_handler.browser.spec.ts index 24624bebe..68a8a5bb7 100644 --- a/packages/optimizely-sdk/tests/browserRequestHandler.spec.ts +++ b/lib/utils/http_request_handler/request_handler.browser.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022 Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,11 +14,11 @@ * limitations under the License. */ -/// <reference types="jest" /> +import { describe, beforeEach, afterEach, it, expect, vi } from 'vitest'; import { FakeXMLHttpRequest, FakeXMLHttpRequestStatic, fakeXhr } from 'nise'; -import { BrowserRequestHandler } from '../lib/utils/http_request_handler/browser_request_handler'; -import { NoOpLogger } from '../lib/plugins/logger'; +import { BrowserRequestHandler } from './request_handler.browser'; +import { getMockLogger } from '../../tests/mock/mock_logger'; describe('BrowserRequestHandler', () => { const host = 'https://endpoint.example.com/api/query'; @@ -34,7 +34,7 @@ describe('BrowserRequestHandler', () => { xhrs = []; mockXHR = fakeXhr.useFakeXMLHttpRequest(); mockXHR.onCreate = (request): number => xhrs.push(request); - browserRequestHandler = new BrowserRequestHandler(new NoOpLogger()); + browserRequestHandler = new BrowserRequestHandler({ logger: getMockLogger() }); }); afterEach(() => { @@ -132,10 +132,10 @@ describe('BrowserRequestHandler', () => { it('should set a timeout on the request object', () => { const timeout = 60000; - const onCreateMock = jest.fn(); + const onCreateMock = vi.fn(); mockXHR.onCreate = onCreateMock; - new BrowserRequestHandler(new NoOpLogger(), timeout).makeRequest(host, {}, 'get'); + new BrowserRequestHandler({ logger: getMockLogger(), timeout }).makeRequest(host, {}, 'get'); expect(onCreateMock).toBeCalledTimes(1); expect(onCreateMock.mock.calls[0][0].timeout).toBe(timeout); diff --git a/packages/optimizely-sdk/lib/utils/http_request_handler/browser_request_handler.ts b/lib/utils/http_request_handler/request_handler.browser.ts similarity index 84% rename from packages/optimizely-sdk/lib/utils/http_request_handler/browser_request_handler.ts rename to lib/utils/http_request_handler/request_handler.browser.ts index 3a2fe73f0..340dcca33 100644 --- a/packages/optimizely-sdk/lib/utils/http_request_handler/browser_request_handler.ts +++ b/lib/utils/http_request_handler/request_handler.browser.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022 Optimizely + * Copyright 2022, 2024 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,19 +15,21 @@ */ import { AbortableRequest, Headers, RequestHandler, Response } from './http'; -import { LogHandler, LogLevel } from '../../modules/logging'; +import { LoggerFacade, LogLevel } from '../../logging/logger'; import { REQUEST_TIMEOUT_MS } from '../enums'; +import { REQUEST_ERROR, REQUEST_TIMEOUT, UNABLE_TO_PARSE_AND_SKIPPED_HEADER } from 'error_message'; +import { OptimizelyError } from '../../error/optimizly_error'; /** * Handles sending requests and receiving responses over HTTP via XMLHttpRequest */ export class BrowserRequestHandler implements RequestHandler { - private readonly logger: LogHandler; - private readonly timeout: number; + private logger?: LoggerFacade; + private timeout: number; - public constructor(logger: LogHandler, timeout: number = REQUEST_TIMEOUT_MS) { - this.logger = logger; - this.timeout = timeout; + public constructor(opt: { logger?: LoggerFacade, timeout?: number } = {}) { + this.logger = opt.logger; + this.timeout = opt.timeout ?? REQUEST_TIMEOUT_MS; } /** @@ -50,7 +52,7 @@ export class BrowserRequestHandler implements RequestHandler { if (request.readyState === XMLHttpRequest.DONE) { const statusCode = request.status; if (statusCode === 0) { - reject(new Error('Request error')); + reject(new OptimizelyError(REQUEST_ERROR)); return; } @@ -67,7 +69,7 @@ export class BrowserRequestHandler implements RequestHandler { request.timeout = this.timeout; request.ontimeout = (): void => { - this.logger.log(LogLevel.WARNING, 'Request timed out'); + this.logger?.warn(REQUEST_TIMEOUT); }; request.send(data); @@ -122,7 +124,7 @@ export class BrowserRequestHandler implements RequestHandler { } } } catch { - this.logger.log(LogLevel.WARNING, `Unable to parse & skipped header item '${headerLine}'`); + this.logger?.warn(UNABLE_TO_PARSE_AND_SKIPPED_HEADER, headerLine); } }); return headers; diff --git a/packages/optimizely-sdk/tests/nodeRequestHandler.spec.ts b/lib/utils/http_request_handler/request_handler.node.spec.ts similarity index 87% rename from packages/optimizely-sdk/tests/nodeRequestHandler.spec.ts rename to lib/utils/http_request_handler/request_handler.node.spec.ts index 149cc7270..1865df88b 100644 --- a/packages/optimizely-sdk/tests/nodeRequestHandler.spec.ts +++ b/lib/utils/http_request_handler/request_handler.node.spec.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022 Optimizely + * Copyright 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,12 +14,12 @@ * limitations under the License. */ -/// <reference types="jest" /> +import { describe, beforeEach, afterEach, beforeAll, afterAll, it, vi, expect } from 'vitest'; import nock from 'nock'; import zlib from 'zlib'; -import { NodeRequestHandler } from '../lib/utils/http_request_handler/node_request_handler'; -import { NoOpLogger } from '../lib/plugins/logger'; +import { NodeRequestHandler } from './request_handler.node'; +import { getMockLogger } from '../../tests/mock/mock_logger'; beforeAll(() => { nock.disableNetConnect(); @@ -37,7 +37,7 @@ describe('NodeRequestHandler', () => { let nodeRequestHandler: NodeRequestHandler; beforeEach(() => { - nodeRequestHandler = new NodeRequestHandler(new NoOpLogger()); + nodeRequestHandler = new NodeRequestHandler({ logger: getMockLogger() }); }); afterEach(async () => { @@ -195,20 +195,20 @@ describe('NodeRequestHandler', () => { describe('timeout', () => { beforeEach(() => { - jest.useFakeTimers(); + vi.useFakeTimers(); }); afterEach(() => { - jest.clearAllTimers(); + vi.clearAllTimers(); }); - it.only('should reject the response promise and abort the request when the response is not received before the timeout', async () => { + it('should reject the response promise and abort the request when the response is not received before the timeout', async () => { const scope = nock(host) .get(path) .delay({ head: 2000, body: 2000 }) .reply(200, body); - const abortEventListener = jest.fn(); + const abortEventListener = vi.fn(); // eslint-disable-next-line @typescript-eslint/no-explicit-any let emittedReq: any; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -218,11 +218,11 @@ describe('NodeRequestHandler', () => { }; scope.on('request', requestListener); - const request = new NodeRequestHandler(new NoOpLogger(), 100).makeRequest(`${host}${path}`, {}, 'get'); + const request = new NodeRequestHandler({ logger: getMockLogger(), timeout: 100 }).makeRequest(`${host}${path}`, {}, 'get'); - jest.advanceTimersByTime(60000); - jest.runAllTimers(); // <- explicitly tell jest to run all setTimeout, setInterval - jest.runAllTicks(); // <- explicitly tell jest to run all Promise callback + vi.advanceTimersByTime(60000); + vi.runAllTimers(); // <- explicitly tell vi to run all setTimeout, setInterval + vi.runAllTicks(); // <- explicitly tell vi to run all Promise callback await expect(request.responsePromise).rejects.toThrow(); expect(abortEventListener).toBeCalledTimes(1); diff --git a/packages/optimizely-sdk/lib/utils/http_request_handler/node_request_handler.ts b/lib/utils/http_request_handler/request_handler.node.ts similarity index 72% rename from packages/optimizely-sdk/lib/utils/http_request_handler/node_request_handler.ts rename to lib/utils/http_request_handler/request_handler.node.ts index 3de55a9e7..16af94caf 100644 --- a/packages/optimizely-sdk/lib/utils/http_request_handler/node_request_handler.ts +++ b/lib/utils/http_request_handler/request_handler.node.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022 Optimizely + * Copyright 2022-2023 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,25 +13,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import http from 'http'; import https from 'https'; import url from 'url'; import { AbortableRequest, Headers, RequestHandler, Response } from './http'; import decompressResponse from 'decompress-response'; -import { LogHandler } from '../../modules/logging'; +import { LoggerFacade } from '../../logging/logger'; import { REQUEST_TIMEOUT_MS } from '../enums'; +import { NO_STATUS_CODE_IN_RESPONSE, REQUEST_ERROR, REQUEST_TIMEOUT, UNSUPPORTED_PROTOCOL } from 'error_message'; +import { OptimizelyError } from '../../error/optimizly_error'; /** * Handles sending requests and receiving responses over HTTP via NodeJS http module */ export class NodeRequestHandler implements RequestHandler { - private readonly logger: LogHandler; + private readonly logger?: LoggerFacade; private readonly timeout: number; - public constructor(logger: LogHandler, timeout: number = REQUEST_TIMEOUT_MS) { - this.logger = logger; - this.timeout = timeout; + constructor(opt: { logger?: LoggerFacade; timeout?: number } = {}) { + this.logger = opt.logger; + this.timeout = opt.timeout ?? REQUEST_TIMEOUT_MS; } /** @@ -42,14 +43,13 @@ export class NodeRequestHandler implements RequestHandler { * @param data? stringified version of data to POST, PUT, etc * @returns AbortableRequest contains both the response Promise and capability to abort() */ - public makeRequest(requestUrl: string, headers: Headers, method: string, data?: string): AbortableRequest { + makeRequest(requestUrl: string, headers: Headers, method: string, data?: string): AbortableRequest { const parsedUrl = url.parse(requestUrl); if (parsedUrl.protocol !== 'https:') { return { - responsePromise: Promise.reject(new Error(`Unsupported protocol: ${parsedUrl.protocol}`)), - abort: () => { - }, + responsePromise: Promise.reject(new OptimizelyError(UNSUPPORTED_PROTOCOL, parsedUrl.protocol)), + abort: () => {}, }; } @@ -59,20 +59,18 @@ export class NodeRequestHandler implements RequestHandler { headers: { ...headers, 'accept-encoding': 'gzip,deflate', + 'content-length': String(data?.length || 0) }, timeout: this.timeout, }); - const responsePromise = this.getResponseFromRequest(request); + const abortableRequest = this.getAbortableRequestFromRequest(request); if (data) { request.write(data); } request.end(); - return { - abort: () => request.destroy(), - responsePromise, - }; + return abortableRequest; } /** @@ -119,13 +117,21 @@ export class NodeRequestHandler implements RequestHandler { * Sends a built request handling response, errors, and events around the transmission * @param request Request to send * @private - * @returns Response Promise-wrapped, simplified response object + * @returns AbortableRequest with simplified response promise */ - private getResponseFromRequest(request: http.ClientRequest): Promise<Response> { - return new Promise((resolve, reject) => { + private getAbortableRequestFromRequest(request: http.ClientRequest): AbortableRequest { + let aborted = false; + + const abort = () => { + aborted = true; + request.destroy(); + }; + + const responsePromise: Promise<Response> = new Promise((resolve, reject) => { request.on('timeout', () => { + aborted = true; request.destroy(); - reject(new Error('Request timed out')); + reject(new OptimizelyError(REQUEST_TIMEOUT)); }); // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -135,12 +141,12 @@ export class NodeRequestHandler implements RequestHandler { } else if (typeof err === 'string') { reject(new Error(err)); } else { - reject(new Error('Request error')); + reject(new OptimizelyError(REQUEST_ERROR)); } }); request.once('response', (incomingMessage: http.IncomingMessage) => { - if (request.destroyed) { + if (aborted) { return; } @@ -150,13 +156,18 @@ export class NodeRequestHandler implements RequestHandler { let responseData = ''; response.on('data', (chunk: string) => { - if (!request.destroyed) { + if (!aborted) { responseData += chunk; } }); response.on('end', () => { - if (request.destroyed) { + if (aborted) { + return; + } + + if (!incomingMessage.statusCode) { + reject(new OptimizelyError(NO_STATUS_CODE_IN_RESPONSE)); return; } @@ -168,5 +179,7 @@ export class NodeRequestHandler implements RequestHandler { }); }); }); + + return { abort, responsePromise }; } } diff --git a/packages/optimizely-sdk/lib/plugins/event_processor/index.ts b/lib/utils/http_request_handler/request_handler_validator.ts similarity index 51% rename from packages/optimizely-sdk/lib/plugins/event_processor/index.ts rename to lib/utils/http_request_handler/request_handler_validator.ts index b515b7b63..a9df4cc7c 100644 --- a/packages/optimizely-sdk/lib/plugins/event_processor/index.ts +++ b/lib/utils/http_request_handler/request_handler_validator.ts @@ -1,11 +1,11 @@ /** - * Copyright 2020, 2022, Optimizely + * Copyright 2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,13 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { RequestHandler } from './http'; -import { LogTierV1EventProcessor, LocalStoragePendingEventsDispatcher } from '../../../lib/modules/event_processor'; +export const INVALID_REQUEST_HANDLER = 'Invalid request handler'; -export function createEventProcessor( - ...args: ConstructorParameters<typeof LogTierV1EventProcessor> -): LogTierV1EventProcessor { - return new LogTierV1EventProcessor(...args); -} +export const validateRequestHandler = (requestHandler: RequestHandler): void => { + if (!requestHandler || typeof requestHandler !== 'object') { + throw new Error(INVALID_REQUEST_HANDLER); + } -export default { createEventProcessor, LocalStoragePendingEventsDispatcher }; + if (typeof requestHandler.makeRequest !== 'function') { + throw new Error(INVALID_REQUEST_HANDLER); + } +} diff --git a/lib/utils/id_generator/index.ts b/lib/utils/id_generator/index.ts new file mode 100644 index 000000000..5f3c72387 --- /dev/null +++ b/lib/utils/id_generator/index.ts @@ -0,0 +1,31 @@ +/** + * Copyright 2022-2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const idSuffixBase = 10_000; + +export class IdGenerator { + private idSuffixOffset = 0; + + // getId returns an Id that generally increases with each call. + // only exceptions are when idSuffix rotates back to 0 within the same millisecond + // or when the clock goes back + getId(): string { + const idSuffix = idSuffixBase + this.idSuffixOffset; + this.idSuffixOffset = (this.idSuffixOffset + 1) % idSuffixBase; + const timestamp = Date.now(); + return `${timestamp}${idSuffix}`; + } +} diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/config.ts b/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts similarity index 51% rename from packages/optimizely-sdk/lib/modules/datafile-manager/config.ts rename to lib/utils/import.react_native/@react-native-async-storage/async-storage.ts index 18de89eff..4a2fb77ed 100644 --- a/packages/optimizely-sdk/lib/modules/datafile-manager/config.ts +++ b/lib/utils/import.react_native/@react-native-async-storage/async-storage.ts @@ -1,5 +1,5 @@ /** - * Copyright 2022, Optimizely + * Copyright 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +14,15 @@ * limitations under the License. */ -export const DEFAULT_UPDATE_INTERVAL = 5 * 60 * 1000; // 5 minutes +import type { AsyncStorageStatic } from '@react-native-async-storage/async-storage' -export const MIN_UPDATE_INTERVAL = 1000; +export const MODULE_NOT_FOUND_REACT_NATIVE_ASYNC_STORAGE = 'Module not found: @react-native-async-storage/async-storage'; -export const DEFAULT_URL_TEMPLATE = `https://cdn.optimizely.com/datafiles/%s.json`; - -export const DEFAULT_AUTHENTICATED_URL_TEMPLATE = `https://config.optimizely.com/datafiles/auth/%s.json`; - -export const BACKOFF_BASE_WAIT_SECONDS_BY_ERROR_COUNT = [0, 8, 16, 32, 64, 128, 256, 512]; - -export const REQUEST_TIMEOUT_MS = 60 * 1000; // 1 minute +export const getDefaultAsyncStorage = (): AsyncStorageStatic => { + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + return require('@react-native-async-storage/async-storage').default; + } catch (e) { + throw new Error(MODULE_NOT_FOUND_REACT_NATIVE_ASYNC_STORAGE); + } +}; diff --git a/lib/utils/json_schema_validator/index.spec.ts b/lib/utils/json_schema_validator/index.spec.ts new file mode 100644 index 000000000..20af5b51d --- /dev/null +++ b/lib/utils/json_schema_validator/index.spec.ts @@ -0,0 +1,49 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect } from 'vitest'; +import { validate } from '.'; +import testData from '../../tests/test_data'; +import { NO_JSON_PROVIDED, INVALID_DATAFILE } from 'error_message'; + +describe('validate', () => { + it('should throw an error if the object is not valid', () => { + expect(() => validate({})).toThrow(); + + try { + validate({}); + } catch (err) { + expect(err.baseMessage).toBe(INVALID_DATAFILE); + } + }); + + it('should throw an error if no json object is passed in', () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + expect(() => validate()).toThrow(); + + try { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + validate(); + } catch (err) { + expect(err.baseMessage).toBe(NO_JSON_PROVIDED); + } + }); + + it('should validate specified Optimizely datafile', () => { + expect(validate(testData.getTestProjectConfig())).toBe(true); + }); +}); diff --git a/packages/optimizely-sdk/lib/utils/json_schema_validator/index.tests.js b/lib/utils/json_schema_validator/index.tests.js similarity index 79% rename from packages/optimizely-sdk/lib/utils/json_schema_validator/index.tests.js rename to lib/utils/json_schema_validator/index.tests.js index 597ce15b7..cace62047 100644 --- a/packages/optimizely-sdk/lib/utils/json_schema_validator/index.tests.js +++ b/lib/utils/json_schema_validator/index.tests.js @@ -1,5 +1,5 @@ /** - * Copyright 2016-2020, 2022, Optimizely + * Copyright 2016-2020, 2022, 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,12 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { sprintf } from '../fns'; import { assert } from 'chai'; import { validate } from './'; -import { ERROR_MESSAGES } from '../enums'; -import testData from '../../tests/test_data.js'; +import testData from '../../tests/test_data'; +import { NO_JSON_PROVIDED } from 'error_message'; describe('lib/utils/json_schema_validator', function() { @@ -31,9 +30,10 @@ describe('lib/utils/json_schema_validator', function() { }); it('should throw an error if no json object is passed in', function() { - assert.throws(function() { + const ex = assert.throws(function() { validate(); - }, sprintf(ERROR_MESSAGES.NO_JSON_PROVIDED, 'JSON_SCHEMA_VALIDATOR (Project Config JSON Schema)')); + }); + assert.equal(ex.baseMessage, NO_JSON_PROVIDED); }); it('should validate specified Optimizely datafile', function() { diff --git a/packages/optimizely-sdk/lib/utils/json_schema_validator/index.ts b/lib/utils/json_schema_validator/index.ts similarity index 64% rename from packages/optimizely-sdk/lib/utils/json_schema_validator/index.ts rename to lib/utils/json_schema_validator/index.ts index 96c3dc485..42fe19f11 100644 --- a/packages/optimizely-sdk/lib/utils/json_schema_validator/index.ts +++ b/lib/utils/json_schema_validator/index.ts @@ -1,5 +1,5 @@ /** - * Copyright 2016-2017, 2020, 2022 Optimizely + * Copyright 2016-2017, 2020, 2022, 2024 Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,13 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { sprintf } from '../fns'; import { JSONSchema4, validate as jsonSchemaValidator } from 'json-schema'; -import { ERROR_MESSAGES } from '../enums'; -import schema from '../../core/project_config/project_config_schema'; - -const MODULE_NAME = 'JSON_SCHEMA_VALIDATOR'; +import schema from '../../project_config/project_config_schema'; +import { INVALID_DATAFILE, INVALID_JSON, NO_JSON_PROVIDED } from 'error_message'; +import { OptimizelyError } from '../../error/optimizly_error'; /** * Validate the given json object against the specified schema @@ -28,11 +26,13 @@ const MODULE_NAME = 'JSON_SCHEMA_VALIDATOR'; * @param {boolean} shouldThrowOnError Should validation throw if invalid JSON object * @return {boolean} true if the given object is valid; throws or false if invalid */ -export function validate(jsonObject: unknown, validationSchema: JSONSchema4 = schema, shouldThrowOnError = true): boolean { - const moduleTitle = `${MODULE_NAME} (${validationSchema.title})`; - +export function validate( + jsonObject: unknown, + validationSchema: JSONSchema4 = schema, + shouldThrowOnError = true +): boolean { if (typeof jsonObject !== 'object' || jsonObject === null) { - throw new Error(sprintf(ERROR_MESSAGES.NO_JSON_PROVIDED, moduleTitle)); + throw new OptimizelyError(NO_JSON_PROVIDED); } const result = jsonSchemaValidator(jsonObject, validationSchema); @@ -45,10 +45,10 @@ export function validate(jsonObject: unknown, validationSchema: JSONSchema4 = sc } if (Array.isArray(result.errors)) { - throw new Error( - sprintf(ERROR_MESSAGES.INVALID_DATAFILE, moduleTitle, result.errors[0].property, result.errors[0].message), + throw new OptimizelyError( + INVALID_DATAFILE, result.errors[0].property, result.errors[0].message ); } - throw new Error(sprintf(ERROR_MESSAGES.INVALID_JSON, moduleTitle)); + throw new OptimizelyError(INVALID_JSON); } diff --git a/lib/utils/microtask/index.spec.ts b/lib/utils/microtask/index.spec.ts new file mode 100644 index 000000000..8d5fd9622 --- /dev/null +++ b/lib/utils/microtask/index.spec.ts @@ -0,0 +1,43 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, vi } from 'vitest'; +import { scheduleMicrotask } from '.'; + +describe('scheduleMicrotask', () => { + it('should use queueMicrotask if available', async () => { + expect(typeof globalThis.queueMicrotask).toEqual('function'); + const cb = vi.fn(); + scheduleMicrotask(cb); + await Promise.resolve(); + expect(cb).toHaveBeenCalledTimes(1); + }); + + it('should polyfill if queueMicrotask is not available', async () => { + const originalQueueMicrotask = globalThis.queueMicrotask; + globalThis.queueMicrotask = undefined as any; // as any to pacify TS + + expect(globalThis.queueMicrotask).toBeUndefined(); + + const cb = vi.fn(); + scheduleMicrotask(cb); + await Promise.resolve(); + expect(cb).toHaveBeenCalledTimes(1); + + expect(globalThis.queueMicrotask).toBeUndefined(); + globalThis.queueMicrotask = originalQueueMicrotask; + }); +}); diff --git a/packages/event-processor/__mocks__/@react-native-community/netinfo.ts b/lib/utils/microtask/index.ts similarity index 63% rename from packages/event-processor/__mocks__/@react-native-community/netinfo.ts rename to lib/utils/microtask/index.ts index 4a007aab7..02e2c474e 100644 --- a/packages/event-processor/__mocks__/@react-native-community/netinfo.ts +++ b/lib/utils/microtask/index.ts @@ -1,11 +1,11 @@ /** - * Copyright 2020, Optimizely + * Copyright 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -13,12 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -let localCallback: any -export function addEventListener(callback: any) { - localCallback = callback -} +type Callback = () => void; -export function triggerInternetState(isInternetReachable: boolean) { - localCallback({ isInternetReachable }) +export const scheduleMicrotask = (callback: Callback): void => { + if (typeof queueMicrotask === 'function') { + queueMicrotask(callback); + } else { + Promise.resolve().then(callback); + } } diff --git a/lib/utils/promise/operation_value.ts b/lib/utils/promise/operation_value.ts new file mode 100644 index 000000000..7f7aa3779 --- /dev/null +++ b/lib/utils/promise/operation_value.ts @@ -0,0 +1,50 @@ +import { PROMISE_NOT_ALLOWED } from '../../message/error_message'; +import { OptimizelyError } from '../../error/optimizly_error'; +import { OpType, OpValue } from '../type'; + + +const isPromise = (val: any): boolean => { + return val && typeof val.then === 'function'; +} + +/** + * A class that wraps a value that can be either a synchronous value or a promise and provides + * a promise like interface. This class is used to handle both synchronous and asynchronous values + * in a uniform way. + */ +export class Value<OP extends OpType, V> { + constructor(public op: OP, public val: OpValue<OP, V>) {} + + get(): OpValue<OP, V> { + return this.val; + } + + then<NV>(fn: (v: V) => Value<OP, NV>): Value<OP, NV> { + if (this.op === 'sync') { + const newVal = fn(this.val as V); + return Value.of(this.op, newVal.get() as NV); + } + return Value.of(this.op, (this.val as Promise<V>).then(fn) as Promise<NV>); + } + + static all = <OP extends OpType, V>(op: OP, vals: Value<OP, V>[]): Value<OP, V[]> => { + if (op === 'sync') { + const values = vals.map(v => v.get() as V); + return Value.of(op, values); + } + + const promises = vals.map(v => v.get() as Promise<V>); + return Value.of(op, Promise.all(promises)); + } + + static of<OP extends OpType, V>(op: OP, val: V | Promise<V>): Value<OP, V> { + if (op === 'sync') { + if (isPromise(val)) { + throw new OptimizelyError(PROMISE_NOT_ALLOWED); + } + return new Value(op, val as OpValue<OP, V>); + } + + return new Value(op, Promise.resolve(val) as OpValue<OP, V>); + } +} diff --git a/lib/utils/promise/resolvablePromise.ts b/lib/utils/promise/resolvablePromise.ts new file mode 100644 index 000000000..354df2b7d --- /dev/null +++ b/lib/utils/promise/resolvablePromise.ts @@ -0,0 +1,34 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const noop = () => {}; + +export type ResolvablePromise<T> = { + promise: Promise<T>; + resolve: (value: T | PromiseLike<T>) => void; + reject: (reason?: any) => void; + then: Promise<T>['then']; +}; + +export function resolvablePromise<T>(): ResolvablePromise<T> { + let resolve: (value: T | PromiseLike<T>) => void = noop; + let reject: (reason?: any) => void = noop; + const promise = new Promise<T>((res, rej) => { + resolve = res; + reject = rej; + }); + return { promise, resolve, reject, then: promise.then.bind(promise) }; +} diff --git a/lib/utils/repeater/repeater.spec.ts b/lib/utils/repeater/repeater.spec.ts new file mode 100644 index 000000000..b992e0e85 --- /dev/null +++ b/lib/utils/repeater/repeater.spec.ts @@ -0,0 +1,300 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { expect, vi, it, beforeEach, afterEach, describe } from 'vitest'; +import { ConstantBackoff, ExponentialBackoff, IntervalRepeater } from './repeater'; +import { advanceTimersByTime } from '../../tests/testUtils'; +import { resolvablePromise } from '../promise/resolvablePromise'; + +describe("ExponentialBackoff", () => { + it("should return the base with jitter on the first call", () => { + const exponentialBackoff = new ExponentialBackoff(5000, 10000, 1000); + const time = exponentialBackoff.backoff(); + + expect(time).toBeGreaterThanOrEqual(5000); + expect(time).toBeLessThanOrEqual(6000); + }); + + it('should use a random jitter within the specified limit', () => { + const exponentialBackoff1 = new ExponentialBackoff(5000, 10000, 1000); + const exponentialBackoff2 = new ExponentialBackoff(5000, 10000, 1000); + + const time = exponentialBackoff1.backoff(); + const time2 = exponentialBackoff2.backoff(); + + expect(time).toBeGreaterThanOrEqual(5000); + expect(time).toBeLessThanOrEqual(6000); + + expect(time2).toBeGreaterThanOrEqual(5000); + expect(time2).toBeLessThanOrEqual(6000); + + expect(time).not.toEqual(time2); + }); + + it("should double the time when backoff() is called", () => { + const exponentialBackoff = new ExponentialBackoff(5000, 20000, 1000); + const time = exponentialBackoff.backoff(); + + expect(time).toBeGreaterThanOrEqual(5000); + expect(time).toBeLessThanOrEqual(6000); + + const time2 = exponentialBackoff.backoff(); + expect(time2).toBeGreaterThanOrEqual(10000); + expect(time2).toBeLessThanOrEqual(11000); + + const time3 = exponentialBackoff.backoff(); + expect(time3).toBeGreaterThanOrEqual(20000); + expect(time3).toBeLessThanOrEqual(21000); + }); + + it('should not exceed the max time', () => { + const exponentialBackoff = new ExponentialBackoff(5000, 10000, 1000); + const time = exponentialBackoff.backoff(); + expect(time).toBeGreaterThanOrEqual(5000); + expect(time).toBeLessThanOrEqual(6000); + + const time2 = exponentialBackoff.backoff(); + expect(time2).toBeGreaterThanOrEqual(10000); + expect(time2).toBeLessThanOrEqual(11000); + + const time3 = exponentialBackoff.backoff(); + expect(time3).toBeGreaterThanOrEqual(10000); + expect(time3).toBeLessThanOrEqual(11000); + }); + + it('should reset the backoff time when reset() is called', () => { + const exponentialBackoff = new ExponentialBackoff(5000, 10000, 1000); + const time = exponentialBackoff.backoff(); + expect(time).toBeGreaterThanOrEqual(5000); + expect(time).toBeLessThanOrEqual(6000); + + exponentialBackoff.reset(); + const time2 = exponentialBackoff.backoff(); + expect(time2).toBeGreaterThanOrEqual(5000); + expect(time2).toBeLessThanOrEqual(6000); + }); +}); + + +describe("ConstantBackoff", () => { + it("should always return the same backoff time", () => { + const constantBackoff = new ConstantBackoff(3000); + for(let i = 0; i < 5; i++) { + const time = constantBackoff.backoff(); + expect(time).toEqual(3000); + } + + // Reset to verify it still returns the same value + constantBackoff.reset(); + for(let i = 0; i < 5; i++) { + const time = constantBackoff.backoff(); + expect(time).toEqual(3000); + } + }); +}); + +describe("IntervalRepeater", () => { + beforeEach(() => { + vi.useFakeTimers(); + }); + + afterEach(() => { + vi.useRealTimers(); + }); + + it('should call the handler at the specified interval', async() => { + const handler = vi.fn().mockResolvedValue(undefined); + + const intervalRepeater = new IntervalRepeater(2000); + intervalRepeater.setTask(handler); + + intervalRepeater.start(); + + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(1); + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(2); + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(3); + }); + + it('should call the handler with correct failureCount value', async() => { + const handler = vi.fn().mockRejectedValueOnce(new Error()) + .mockRejectedValueOnce(new Error()) + .mockRejectedValueOnce(new Error()) + .mockResolvedValueOnce(undefined) + .mockRejectedValueOnce(new Error()) + .mockResolvedValueOnce(undefined); + + const intervalRepeater = new IntervalRepeater(2000); + intervalRepeater.setTask(handler); + + intervalRepeater.start(); + + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(1); + expect(handler.mock.calls[0][0]).toBe(0); + + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(2); + expect(handler.mock.calls[1][0]).toBe(1); + + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(3); + expect(handler.mock.calls[2][0]).toBe(2); + + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(4); + expect(handler.mock.calls[3][0]).toBe(3); + + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(5); + expect(handler.mock.calls[4][0]).toBe(0); + + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(6); + expect(handler.mock.calls[5][0]).toBe(1); + }); + + it('should backoff when the handler fails if backoffController is provided', async() => { + const handler = vi.fn().mockRejectedValue(new Error()); + + const backoffController = { + backoff: vi.fn().mockReturnValue(1100), + reset: vi.fn(), + }; + + const intervalRepeater = new IntervalRepeater(30000, backoffController); + intervalRepeater.setTask(handler); + + intervalRepeater.start(); + + await advanceTimersByTime(30000); + expect(handler).toHaveBeenCalledTimes(1); + expect(backoffController.backoff).toHaveBeenCalledTimes(1); + + await advanceTimersByTime(1100); + expect(handler).toHaveBeenCalledTimes(2); + expect(backoffController.backoff).toHaveBeenCalledTimes(2); + + await advanceTimersByTime(1100); + expect(handler).toHaveBeenCalledTimes(3); + expect(backoffController.backoff).toHaveBeenCalledTimes(3); + }); + + it('should use the regular interval when the handler fails if backoffController is not provided', async() => { + const handler = vi.fn().mockRejectedValue(new Error()); + + const intervalRepeater = new IntervalRepeater(30000); + intervalRepeater.setTask(handler); + + intervalRepeater.start(); + + await advanceTimersByTime(30000); + expect(handler).toHaveBeenCalledTimes(1); + + await advanceTimersByTime(10000); + expect(handler).toHaveBeenCalledTimes(1); + await advanceTimersByTime(20000); + expect(handler).toHaveBeenCalledTimes(2); + }); + + it('should reset the backoffController after handler success', async () => { + const handler = vi.fn().mockRejectedValueOnce(new Error) + .mockRejectedValueOnce(new Error()) + .mockResolvedValueOnce(undefined) + .mockResolvedValueOnce(undefined); + + const backoffController = { + + backoff: vi.fn().mockReturnValue(1100), + reset: vi.fn(), + }; + + const intervalRepeater = new IntervalRepeater(30000, backoffController); + intervalRepeater.setTask(handler); + + intervalRepeater.start(); + + await advanceTimersByTime(30000); + expect(handler).toHaveBeenCalledTimes(1); + expect(backoffController.backoff).toHaveBeenCalledTimes(1); + + await advanceTimersByTime(1100); + expect(handler).toHaveBeenCalledTimes(2); + expect(backoffController.backoff).toHaveBeenCalledTimes(2); + + await advanceTimersByTime(1100); + expect(handler).toHaveBeenCalledTimes(3); + + expect(backoffController.backoff).toHaveBeenCalledTimes(2); // backoff should not be called again + expect(backoffController.reset).toHaveBeenCalledTimes(1); // reset should be called once + + await advanceTimersByTime(1100); + expect(handler).toHaveBeenCalledTimes(3); // handler should be called after 30000ms + await advanceTimersByTime(30000 - 1100); + expect(handler).toHaveBeenCalledTimes(4); // handler should be called after 30000ms + }); + + + it('should wait for handler promise to resolve before scheduling another tick', async() => { + const ret = resolvablePromise(); + const handler = vi.fn().mockReturnValue(ret); + + const intervalRepeater = new IntervalRepeater(2000); + intervalRepeater.setTask(handler); + + intervalRepeater.start(); + + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(1); + + // should not schedule another call cause promise is pending + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(1); + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(1); + + ret.resolve(undefined); + await ret.promise; + + // Advance the timers to the next tick + await advanceTimersByTime(2000); + // The handler should be called again after the promise has resolved + expect(handler).toHaveBeenCalledTimes(2); + }); + + it('should not call the handler after stop is called', async() => { + const ret = resolvablePromise(); + const handler = vi.fn().mockReturnValue(ret); + + const intervalRepeater = new IntervalRepeater(2000); + intervalRepeater.setTask(handler); + + intervalRepeater.start(); + + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(1); + + intervalRepeater.stop(); + + ret.resolve(undefined); + await ret.promise; + + await advanceTimersByTime(2000); + await advanceTimersByTime(2000); + expect(handler).toHaveBeenCalledTimes(1); + }); +}); diff --git a/lib/utils/repeater/repeater.ts b/lib/utils/repeater/repeater.ts new file mode 100644 index 000000000..829702729 --- /dev/null +++ b/lib/utils/repeater/repeater.ts @@ -0,0 +1,156 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { AsyncTransformer } from "../type"; +import { scheduleMicrotask } from "../microtask"; + +// A repeater will invoke the task repeatedly. The time at which the task is invoked +// is determined by the implementation. +// The task is a function that takes a number as an argument and returns a promise. +// The number argument is the number of times the task previously failed consecutively. +// If the retuned promise resolves, the repeater will assume the task succeeded, +// and will reset the failure count. If the promise is rejected, the repeater will +// assume the task failed and will increase the current consecutive failure count. +export interface Repeater { + // If immediateExecution is true, the first exection of + // the task will be immediate but asynchronous. + start(immediateExecution?: boolean): void; + stop(): void; + reset(): void; + setTask(task: AsyncTransformer<number, unknown>): void; + isRunning(): boolean; +} + +export interface BackoffController { + backoff(): number; + reset(): void; +} + +export class ExponentialBackoff implements BackoffController { + private base: number; + private max: number; + private current: number; + private maxJitter: number; + + constructor(base: number, max: number, maxJitter: number) { + this.base = base; + this.max = max; + this.maxJitter = maxJitter; + this.current = base; + } + + backoff(): number { + const ret = this.current + this.maxJitter * Math.random(); + this.current = Math.min(this.current * 2, this.max); + return ret; + } + + reset(): void { + this.current = this.base; + } +} + +export class ConstantBackoff implements BackoffController { + private value: number; + + constructor(value: number) { + this.value = value; + } + + backoff(): number { + return this.value; + } + + reset(): void { + } +} + +// IntervalRepeater is a Repeater that invokes the task at a fixed interval +// after the completion of the previous task invocation. If a backoff controller +// is provided, the repeater will use the backoff controller to determine the +// time between invocations after a failure instead. It will reset the backoffController +// on success. + +export class IntervalRepeater implements Repeater { + private timeoutId?: NodeJS.Timeout; + private task?: AsyncTransformer<number, void>; + private interval: number; + private failureCount = 0; + private backoffController?: BackoffController; + private running = false; + + constructor(interval: number, backoffController?: BackoffController) { + this.interval = interval; + this.backoffController = backoffController; + } + + isRunning(): boolean { + return this.running; + } + + private handleSuccess() { + this.failureCount = 0; + this.backoffController?.reset(); + this.setTimer(this.interval); + } + + private handleFailure() { + this.failureCount++; + const time = this.backoffController?.backoff() ?? this.interval; + this.setTimer(time); + } + + private setTimer(timeout: number) { + if (!this.running){ + return; + } + this.timeoutId = setTimeout(this.executeTask.bind(this), timeout); + } + + private executeTask() { + if (!this.task) { + return; + } + this.task(this.failureCount).then( + this.handleSuccess.bind(this), + this.handleFailure.bind(this) + ); + } + + start(immediateExecution?: boolean): void { + this.running = true; + if(immediateExecution) { + scheduleMicrotask(this.executeTask.bind(this)); + } else { + this.setTimer(this.interval); + } + } + + stop(): void { + this.running = false; + clearInterval(this.timeoutId); + } + + reset(): void { + this.failureCount = 0; + this.backoffController?.reset(); + this.stop(); + } + + setTask(task: AsyncTransformer<number, void>): void { + this.task = task; + } +} diff --git a/lib/utils/semantic_version/index.spec.ts b/lib/utils/semantic_version/index.spec.ts new file mode 100644 index 000000000..15dbbdbb9 --- /dev/null +++ b/lib/utils/semantic_version/index.spec.ts @@ -0,0 +1,112 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect } from 'vitest'; +import * as semanticVersion from '.'; + +describe('compareVersion', () => { + it('should return 0 if user version and target version are equal', () => { + const versions = [ + ['2.0.1', '2.0.1'], + ['2.9.9-beta', '2.9.9-beta'], + ['2.1', '2.1.0'], + ['2', '2.12'], + ['2.9', '2.9.1'], + ['2.9+beta', '2.9+beta'], + ['2.9.9+beta', '2.9.9+beta'], + ['2.9.9+beta-alpha', '2.9.9+beta-alpha'], + ['2.2.3', '2.2.3+beta'], + ]; + + versions.forEach(([targetVersion, userVersion]) => { + const result = semanticVersion.compareVersion(targetVersion, userVersion); + + expect(result).toBe(0); + }) + }); + + it('should return 1 when user version is greater than target version', () => { + const versions = [ + ['2.0.0', '2.0.1'], + ['2.0', '3.0.1'], + ['2.0.0', '2.1'], + ['2.1.2-beta', '2.1.2-release'], + ['2.1.3-beta1', '2.1.3-beta2'], + ['2.9.9-beta', '2.9.9'], + ['2.9.9+beta', '2.9.9'], + ['2.0.0', '2.1'], + ['3.7.0-prerelease+build', '3.7.0-prerelease+rc'], + ['2.2.3-beta-beta1', '2.2.3-beta-beta2'], + ['2.2.3-beta+beta1', '2.2.3-beta+beta2'], + ['2.2.3+beta2-beta1', '2.2.3+beta3-beta2'], + ['2.2.3+beta', '2.2.3'], + ]; + + versions.forEach(([targetVersion, userVersion]) => { + const result = semanticVersion.compareVersion(targetVersion, userVersion); + + expect(result).toBe(1); + }) + }); + + it('should return -1 when user version is less than target version', () => { + const versions = [ + ['2.0.1', '2.0.0'], + ['3.0', '2.0.1'], + ['2.3', '2.0.1'], + ['2.3.5', '2.3.1'], + ['2.9.8', '2.9'], + ['3.1', '3'], + ['2.1.2-release', '2.1.2-beta'], + ['2.9.9+beta', '2.9.9-beta'], + ['3.7.0+build3.7.0-prerelease+build', '3.7.0-prerelease'], + ['2.1.3-beta-beta2', '2.1.3-beta'], + ['2.1.3-beta1+beta3', '2.1.3-beta1+beta2'], + ['2.1.3', '2.1.3-beta'], + ]; + + versions.forEach(([targetVersion, userVersion]) => { + const result = semanticVersion.compareVersion(targetVersion, userVersion); + + expect(result).toBe(-1); + }) + }); + + it('should return null when user version is invalid', () => { + const versions = [ + '-', + '.', + '..', + '+', + '+test', + ' ', + '2 .3. 0', + '2.', + '.2.2', + '3.7.2.2', + '3.x', + ',', + '+build-prerelease', + '2..2', + ]; + const targetVersion = '2.1.0'; + + versions.forEach((userVersion) => { + const result = semanticVersion.compareVersion(targetVersion, userVersion); + + expect(result).toBe(null); + }) + }); +}); diff --git a/packages/optimizely-sdk/lib/utils/semantic_version/index.tests.js b/lib/utils/semantic_version/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/utils/semantic_version/index.tests.js rename to lib/utils/semantic_version/index.tests.js diff --git a/packages/optimizely-sdk/lib/utils/semantic_version/index.ts b/lib/utils/semantic_version/index.ts similarity index 88% rename from packages/optimizely-sdk/lib/utils/semantic_version/index.ts rename to lib/utils/semantic_version/index.ts index e8ef11c7f..56fad06a5 100644 --- a/packages/optimizely-sdk/lib/utils/semantic_version/index.ts +++ b/lib/utils/semantic_version/index.ts @@ -13,11 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { getLogger } from '../../modules/logging'; -import { VERSION_TYPE, LOG_MESSAGES } from '../enums'; - -const MODULE_NAME = 'SEMANTIC VERSION'; -const logger = getLogger(); +import { UNKNOWN_MATCH_TYPE } from 'error_message'; +import { LoggerFacade } from '../../logging/logger'; +import { VERSION_TYPE } from '../enums'; /** * Evaluate if provided string is number only @@ -87,13 +85,13 @@ function hasWhiteSpaces(version: string): boolean { * @return {boolean} The array of version split into smaller parts i.e major, minor, patch etc * null if given version is in invalid format */ -function splitVersion(version: string): string[] | null { +function splitVersion(version: string, logger?: LoggerFacade): string[] | null { let targetPrefix = version; let targetSuffix = ''; // check that version shouldn't have white space if (hasWhiteSpaces(version)) { - logger.warn(LOG_MESSAGES.UNKNOWN_MATCH_TYPE, MODULE_NAME, version); + logger?.warn(UNKNOWN_MATCH_TYPE, version); return null; } //check for pre release e.g. 1.0.0-alpha where 'alpha' is a pre release @@ -113,18 +111,18 @@ function splitVersion(version: string): string[] | null { const dotCount = targetPrefix.split('.').length - 1; if (dotCount > 2) { - logger.warn(LOG_MESSAGES.UNKNOWN_MATCH_TYPE, MODULE_NAME, version); + logger?.warn(UNKNOWN_MATCH_TYPE, version); return null; } const targetVersionParts = targetPrefix.split('.'); if (targetVersionParts.length != dotCount + 1) { - logger.warn(LOG_MESSAGES.UNKNOWN_MATCH_TYPE, MODULE_NAME, version); + logger?.warn(UNKNOWN_MATCH_TYPE, version); return null; } for (const part of targetVersionParts) { if (!isNumber(part)) { - logger.warn(LOG_MESSAGES.UNKNOWN_MATCH_TYPE, MODULE_NAME, version); + logger?.warn(UNKNOWN_MATCH_TYPE, version); return null; } } @@ -145,9 +143,9 @@ function splitVersion(version: string): string[] | null { * -1 if user version is less than condition version * null if invalid user or condition version is provided */ -export function compareVersion(conditionsVersion: string, userProvidedVersion: string): number | null { - const userVersionParts = splitVersion(userProvidedVersion); - const conditionsVersionParts = splitVersion(conditionsVersion); +export function compareVersion(conditionsVersion: string, userProvidedVersion: string, logger?: LoggerFacade): number | null { + const userVersionParts = splitVersion(userProvidedVersion, logger); + const conditionsVersionParts = splitVersion(conditionsVersion, logger); if (!userVersionParts || !conditionsVersionParts) { return null; diff --git a/lib/utils/string_value_validator/index.spec.ts b/lib/utils/string_value_validator/index.spec.ts new file mode 100644 index 000000000..a9c7f6a91 --- /dev/null +++ b/lib/utils/string_value_validator/index.spec.ts @@ -0,0 +1,32 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect } from 'vitest'; +import { validate } from './'; + +describe('validate', () => { + it('should validate the given value is valid string', () => { + expect(validate('validStringValue')).toBe(true); + }); + + it('should return false if given value is invalid string', () => { + expect(validate(null)).toBe(false); + expect(validate(undefined)).toBe(false); + expect(validate('')).toBe(false); + expect(validate(5)).toBe(false); + expect(validate(true)).toBe(false); + expect(validate([])).toBe(false); + }); +}); diff --git a/packages/optimizely-sdk/lib/utils/string_value_validator/index.tests.js b/lib/utils/string_value_validator/index.tests.js similarity index 100% rename from packages/optimizely-sdk/lib/utils/string_value_validator/index.tests.js rename to lib/utils/string_value_validator/index.tests.js diff --git a/packages/optimizely-sdk/lib/utils/string_value_validator/index.ts b/lib/utils/string_value_validator/index.ts similarity index 100% rename from packages/optimizely-sdk/lib/utils/string_value_validator/index.ts rename to lib/utils/string_value_validator/index.ts diff --git a/lib/utils/type.ts b/lib/utils/type.ts new file mode 100644 index 000000000..c60f85d60 --- /dev/null +++ b/lib/utils/type.ts @@ -0,0 +1,39 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type Fn = () => unknown; +export type AsyncFn = () => Promise<unknown>; +export type AsyncTransformer<A, B> = (arg: A) => Promise<B>; +export type Transformer<A, B> = (arg: A) => B; + +export type Consumer<T> = (arg: T) => void; +export type AsyncComsumer<T> = (arg: T) => Promise<void>; + +export type Producer<T> = () => T; +export type AsyncProducer<T> = () => Promise<T>; + +export type Maybe<T> = T | undefined; + +export type Either<A, B> = { type: 'left', value: A } | { type: 'right', value: B }; + +export type OpType = 'sync' | 'async'; +export type OpValue<O extends OpType, V> = O extends 'sync' ? V : Promise<V>; + +export type OrNull<T> = T | null; + +export type Nullable<T, K extends keyof T> = { + [P in keyof T]: P extends K ? OrNull<T[P]> : T[P]; +} diff --git a/lib/utils/user_profile_service_validator/index.spec.ts b/lib/utils/user_profile_service_validator/index.spec.ts new file mode 100644 index 000000000..98a47ef60 --- /dev/null +++ b/lib/utils/user_profile_service_validator/index.spec.ts @@ -0,0 +1,96 @@ +/** + * Copyright 2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { describe, it, expect } from 'vitest'; +import { validate } from './'; +import { INVALID_USER_PROFILE_SERVICE } from 'error_message'; +import { OptimizelyError } from '../../error/optimizly_error'; + +describe('validate', () => { + it("should throw if the instance does not provide a 'lookup' function", () => { + const missingLookupFunction = { + save: function() {}, + }; + + expect(() => validate(missingLookupFunction)).toThrowError(OptimizelyError); + + try { + validate(missingLookupFunction); + } catch (err) { + expect(err).instanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_USER_PROFILE_SERVICE); + expect(err.params).toEqual(["Missing function 'lookup'"]); + } + }); + + it("should throw if 'lookup' is not a function", () => { + const lookupNotFunction = { + save: function() {}, + lookup: 'notGonnaWork', + }; + + expect(() => validate(lookupNotFunction)).toThrowError(OptimizelyError); + + try { + validate(lookupNotFunction); + } catch (err) { + expect(err).instanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_USER_PROFILE_SERVICE); + expect(err.params).toEqual(["Missing function 'lookup'"]); + } + }); + + it("should throw if the instance does not provide a 'save' function", () => { + const missingSaveFunction = { + lookup: function() {}, + }; + + expect(() => validate(missingSaveFunction)).toThrowError(OptimizelyError); + + try { + validate(missingSaveFunction); + } catch (err) { + expect(err).instanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_USER_PROFILE_SERVICE); + expect(err.params).toEqual(["Missing function 'save'"]); + } + }); + + it("should throw if 'save' is not a function", () => { + const saveNotFunction = { + lookup: function() {}, + save: 'notGonnaWork', + }; + + expect(() => validate(saveNotFunction)).toThrowError(OptimizelyError); + + try { + validate(saveNotFunction); + } catch (err) { + expect(err).instanceOf(OptimizelyError); + expect(err.baseMessage).toBe(INVALID_USER_PROFILE_SERVICE); + expect(err.params).toEqual(["Missing function 'save'"]); + } + }); + + it('should return true if the instance is valid', () => { + const validInstance = { + save: function() {}, + lookup: function() {}, + }; + + expect(validate(validInstance)).toBe(true); + }); +}); diff --git a/packages/optimizely-sdk/lib/utils/user_profile_service_validator/index.tests.js b/lib/utils/user_profile_service_validator/index.tests.js similarity index 71% rename from packages/optimizely-sdk/lib/utils/user_profile_service_validator/index.tests.js rename to lib/utils/user_profile_service_validator/index.tests.js index 1237a0894..d5ad136e8 100644 --- a/packages/optimizely-sdk/lib/utils/user_profile_service_validator/index.tests.js +++ b/lib/utils/user_profile_service_validator/index.tests.js @@ -13,12 +13,10 @@ * See the License for the specific language governing permissions and * * limitations under the License. * ***************************************************************************/ - import { assert } from 'chai'; -import { sprintf } from '../../utils/fns'; import { validate } from './'; -import { ERROR_MESSAGES } from '../enums'; +import { INVALID_USER_PROFILE_SERVICE } from 'error_message'; describe('lib/utils/user_profile_service_validator', function() { describe('APIs', function() { @@ -27,13 +25,11 @@ describe('lib/utils/user_profile_service_validator', function() { var missingLookupFunction = { save: function() {}, }; - assert.throws(function() { + const ex = assert.throws(function() { validate(missingLookupFunction); - }, sprintf( - ERROR_MESSAGES.INVALID_USER_PROFILE_SERVICE, - 'USER_PROFILE_SERVICE_VALIDATOR', - "Missing function 'lookup'" - )); + }); + assert.equal(ex.baseMessage, INVALID_USER_PROFILE_SERVICE); + assert.deepEqual(ex.params, ["Missing function 'lookup'"]); }); it("should throw if 'lookup' is not a function", function() { @@ -41,26 +37,27 @@ describe('lib/utils/user_profile_service_validator', function() { save: function() {}, lookup: 'notGonnaWork', }; - assert.throws(function() { + const ex = assert.throws(function() { validate(lookupNotFunction); - }, sprintf( - ERROR_MESSAGES.INVALID_USER_PROFILE_SERVICE, - 'USER_PROFILE_SERVICE_VALIDATOR', - "Missing function 'lookup'" - )); + }); + assert.equal(ex.baseMessage, INVALID_USER_PROFILE_SERVICE); + assert.deepEqual(ex.params, ["Missing function 'lookup'"]); }); it("should throw if the instance does not provide a 'save' function", function() { var missingSaveFunction = { lookup: function() {}, }; - assert.throws(function() { + const ex = assert.throws(function() { validate(missingSaveFunction); - }, sprintf( - ERROR_MESSAGES.INVALID_USER_PROFILE_SERVICE, - 'USER_PROFILE_SERVICE_VALIDATOR', - "Missing function 'save'" - )); + }); + assert.equal(ex.baseMessage, INVALID_USER_PROFILE_SERVICE); + assert.deepEqual(ex.params, ["Missing function 'save'"]); + // , sprintf( + // INVALID_USER_PROFILE_SERVICE, + // 'USER_PROFILE_SERVICE_VALIDATOR', + // "Missing function 'save'" + // )); }); it("should throw if 'save' is not a function", function() { @@ -68,13 +65,11 @@ describe('lib/utils/user_profile_service_validator', function() { lookup: function() {}, save: 'notGonnaWork', }; - assert.throws(function() { + const ex = assert.throws(function() { validate(saveNotFunction); - }, sprintf( - ERROR_MESSAGES.INVALID_USER_PROFILE_SERVICE, - 'USER_PROFILE_SERVICE_VALIDATOR', - "Missing function 'save'" - )); + }); + assert.equal(ex.baseMessage, INVALID_USER_PROFILE_SERVICE); + assert.deepEqual(ex.params, ["Missing function 'save'"]); }); it('should return true if the instance is valid', function() { diff --git a/packages/optimizely-sdk/lib/utils/user_profile_service_validator/index.ts b/lib/utils/user_profile_service_validator/index.ts similarity index 80% rename from packages/optimizely-sdk/lib/utils/user_profile_service_validator/index.ts rename to lib/utils/user_profile_service_validator/index.ts index 57df0c891..95e8cf61a 100644 --- a/packages/optimizely-sdk/lib/utils/user_profile_service_validator/index.ts +++ b/lib/utils/user_profile_service_validator/index.ts @@ -18,12 +18,10 @@ * Provides utility method for validating that the given user profile service implementation is valid. */ -import { sprintf } from '../../utils/fns'; import { ObjectWithUnknownProperties } from '../../shared_types'; +import { INVALID_USER_PROFILE_SERVICE } from 'error_message'; -import { ERROR_MESSAGES } from '../enums'; - -const MODULE_NAME = 'USER_PROFILE_SERVICE_VALIDATOR'; +import { OptimizelyError } from '../../error/optimizly_error'; /** * Validates user's provided user profile service instance @@ -35,11 +33,11 @@ const MODULE_NAME = 'USER_PROFILE_SERVICE_VALIDATOR'; export function validate(userProfileServiceInstance: unknown): boolean { if (typeof userProfileServiceInstance === 'object' && userProfileServiceInstance !== null) { if (typeof (userProfileServiceInstance as ObjectWithUnknownProperties)['lookup'] !== 'function') { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_USER_PROFILE_SERVICE, MODULE_NAME, "Missing function 'lookup'")); + throw new OptimizelyError(INVALID_USER_PROFILE_SERVICE, "Missing function 'lookup'"); } else if (typeof (userProfileServiceInstance as ObjectWithUnknownProperties)['save'] !== 'function') { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_USER_PROFILE_SERVICE, MODULE_NAME, "Missing function 'save'")); + throw new OptimizelyError(INVALID_USER_PROFILE_SERVICE, "Missing function 'save'"); } return true; } - throw new Error(sprintf(ERROR_MESSAGES.INVALID_USER_PROFILE_SERVICE, MODULE_NAME)); + throw new OptimizelyError(INVALID_USER_PROFILE_SERVICE, 'Not an object'); } diff --git a/lib/vuid/vuid.spec.ts b/lib/vuid/vuid.spec.ts new file mode 100644 index 000000000..0a0790b59 --- /dev/null +++ b/lib/vuid/vuid.spec.ts @@ -0,0 +1,39 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, expect, it } from 'vitest'; + +import { isVuid, makeVuid, VUID_MAX_LENGTH } from './vuid'; + +describe('isVuid', () => { + it('should return true if and only if the value strats with the VUID_PREFIX and is longer than vuid_prefix', () => { + expect(isVuid('vuid_a')).toBe(true); + expect(isVuid('vuid_123')).toBe(true); + expect(isVuid('vuid_')).toBe(false); + expect(isVuid('vuid')).toBe(false); + expect(isVuid('vui')).toBe(false); + expect(isVuid('vu_123')).toBe(false); + expect(isVuid('123')).toBe(false); + }) +}); + +describe('makeVuid', () => { + it('should return a string that is a valid vuid and whose length is within VUID_MAX_LENGTH', () => { + const vuid = makeVuid(); + expect(isVuid(vuid)).toBe(true); + expect(vuid.length).toBeLessThanOrEqual(VUID_MAX_LENGTH); + }); +}); diff --git a/lib/vuid/vuid.ts b/lib/vuid/vuid.ts new file mode 100644 index 000000000..d335c329d --- /dev/null +++ b/lib/vuid/vuid.ts @@ -0,0 +1,31 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { v4 as uuidV4 } from 'uuid'; + +export const VUID_PREFIX = `vuid_`; +export const VUID_MAX_LENGTH = 32; + +export const isVuid = (vuid: string): boolean => vuid.startsWith(VUID_PREFIX) && vuid.length > VUID_PREFIX.length; + +export const makeVuid = (): string => { + // make sure UUIDv4 is used (not UUIDv1 or UUIDv6) since the trailing 5 chars will be truncated. See TDD for details. + const uuid = uuidV4(); + const formatted = uuid.replace(/-/g, ''); + const vuidFull = `${VUID_PREFIX}${formatted}`; + + return vuidFull.length <= VUID_MAX_LENGTH ? vuidFull : vuidFull.substring(0, VUID_MAX_LENGTH); +}; diff --git a/lib/vuid/vuid_manager.spec.ts b/lib/vuid/vuid_manager.spec.ts new file mode 100644 index 000000000..3cfb2a608 --- /dev/null +++ b/lib/vuid/vuid_manager.spec.ts @@ -0,0 +1,230 @@ +/** + * Copyright 2022, 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { describe, it, expect, vi } from 'vitest'; + +import { DefaultVuidManager, VuidCacheManager } from './vuid_manager'; + +import { getMockAsyncCache } from '../tests/mock/mock_cache'; +import { isVuid } from './vuid'; +import { resolvablePromise } from '../utils/promise/resolvablePromise'; +import { exhaustMicrotasks } from '../tests/testUtils'; + + +const vuidCacheKey = 'optimizely-vuid'; + +describe('VuidCacheManager', () => { + it('should remove vuid from cache', async () => { + const cache = getMockAsyncCache<string>(); + await cache.set(vuidCacheKey, 'vuid_valid'); + + const manager = new VuidCacheManager(cache); + await manager.remove(); + const vuidInCache = await cache.get(vuidCacheKey); + expect(vuidInCache).toBeUndefined(); + }); + + it('should create and save a new vuid if there is no vuid in cache', async () => { + const cache = getMockAsyncCache<string>(); + + const manager = new VuidCacheManager(cache); + const vuid = await manager.load(); + const vuidInCache = await cache.get(vuidCacheKey); + expect(vuidInCache).toBe(vuid); + expect(isVuid(vuid!)).toBe(true); + }); + + it('should create and save a new vuid if old VUID from cache is not valid', async () => { + const cache = getMockAsyncCache<string>(); + await cache.set(vuidCacheKey, 'invalid-vuid'); + + const manager = new VuidCacheManager(cache); + const vuid = await manager.load(); + const vuidInCache = await cache.get(vuidCacheKey); + expect(vuidInCache).toBe(vuid); + expect(isVuid(vuid!)).toBe(true); + }); + + it('should return the same vuid without modifying the cache after creating a new vuid', async () => { + const cache = getMockAsyncCache<string>(); + + const manager = new VuidCacheManager(cache); + const vuid1 = await manager.load(); + const vuid2 = await manager.load(); + expect(vuid1).toBe(vuid2); + const vuidInCache = await cache.get(vuidCacheKey); + expect(vuidInCache).toBe(vuid1); + }); + + it('should use the vuid in cache if available', async () => { + const cache = getMockAsyncCache<string>(); + await cache.set(vuidCacheKey, 'vuid_valid'); + + const manager = new VuidCacheManager(cache); + const vuid1 = await manager.load(); + const vuid2 = await manager.load(); + expect(vuid1).toBe('vuid_valid'); + expect(vuid2).toBe('vuid_valid'); + const vuidInCache = await cache.get(vuidCacheKey); + expect(vuidInCache).toBe('vuid_valid'); + }); + + it('should use the new cache after setCache is called', async () => { + const cache1 = getMockAsyncCache<string>(); + const cache2 = getMockAsyncCache<string>(); + + await cache1.set(vuidCacheKey, 'vuid_123'); + await cache2.set(vuidCacheKey, 'vuid_456'); + + const manager = new VuidCacheManager(cache1); + const vuid1 = await manager.load(); + expect(vuid1).toBe('vuid_123'); + + manager.setCache(cache2); + await manager.load(); + const vuid2 = await cache2.get(vuidCacheKey); + expect(vuid2).toBe('vuid_456'); + + await manager.remove(); + const vuidInCache = await cache2.get(vuidCacheKey); + expect(vuidInCache).toBeUndefined(); + }); + + it('should sequence remove and load calls', async() => { + const cache = getMockAsyncCache<string>(); + const removeSpy = vi.spyOn(cache, 'remove'); + const getSpy = vi.spyOn(cache, 'get'); + const setSpy = vi.spyOn(cache, 'set'); + + const removePromise = resolvablePromise(); + removeSpy.mockReturnValueOnce(removePromise.promise); + + const getPromise = resolvablePromise<string>(); + getSpy.mockReturnValueOnce(getPromise.promise); + + const setPromise = resolvablePromise(); + setSpy.mockReturnValueOnce(setPromise.promise); + + const manager = new VuidCacheManager(cache); + + // this should try to remove from cache, which should stay pending + const call1 = manager.remove(); + + // this should try to get the vuid from cache + const call2 = manager.load(); + + // this should again try to remove vuid + const call3 = manager.remove(); + + await exhaustMicrotasks(); + + expect(removeSpy).toHaveBeenCalledTimes(1); // from the first manager.remove call + expect(getSpy).not.toHaveBeenCalled(); + + // this will resolve the first manager.remove call + removePromise.resolve(true); + await exhaustMicrotasks(); + await expect(call1).resolves.not.toThrow(); + + // this get call is from the load call + expect(getSpy).toHaveBeenCalledTimes(1); + await exhaustMicrotasks(); + + // as the get call is pending, remove call from the second manager.remove call should not yet happen + expect(removeSpy).toHaveBeenCalledTimes(1); + + // this should fail the load call, allowing the second remnove call to proceed + getPromise.reject(new Error('get failed')); + await exhaustMicrotasks(); + await expect(call2).rejects.toThrow(); + + expect(removeSpy).toHaveBeenCalledTimes(2); + }); +}); + +describe('DefaultVuidManager', () => { + const getMockCacheManager = () => ({ + remove: vi.fn(), + load: vi.fn(), + setCache: vi.fn(), + }); + + it('should return undefined for getVuid() before initialization', async () => { + const manager = new DefaultVuidManager({ + vuidCache: getMockAsyncCache<string>(), + vuidCacheManager: getMockCacheManager() as unknown as VuidCacheManager, + enableVuid: true + }); + + expect(manager.getVuid()).toBeUndefined(); + }); + + it('should set the cache on vuidCacheManager', async () => { + const vuidCacheManager = getMockCacheManager(); + + const cache = getMockAsyncCache<string>(); + + const manager = new DefaultVuidManager({ + vuidCache: cache, + vuidCacheManager: vuidCacheManager as unknown as VuidCacheManager, + enableVuid: false + }); + + await manager.initialize(); + expect(vuidCacheManager.setCache).toHaveBeenCalledWith(cache); + }); + + it('should call remove on VuidCacheManager if enableVuid is false', async () => { + const vuidCacheManager = getMockCacheManager(); + + const manager = new DefaultVuidManager({ + vuidCache: getMockAsyncCache<string>(), + vuidCacheManager: vuidCacheManager as unknown as VuidCacheManager, + enableVuid: false + }); + + await manager.initialize(); + expect(vuidCacheManager.remove).toHaveBeenCalled(); + }); + + it('should return undefined for getVuid() after initialization if enableVuid is false', async () => { + const vuidCacheManager = getMockCacheManager(); + + const manager = new DefaultVuidManager({ + vuidCache: getMockAsyncCache<string>(), + vuidCacheManager: vuidCacheManager as unknown as VuidCacheManager, + enableVuid: false + }); + + await manager.initialize(); + expect(manager.getVuid()).toBeUndefined(); + }); + + it('should load vuid using VuidCacheManger if enableVuid=true', async () => { + const vuidCacheManager = getMockCacheManager(); + vuidCacheManager.load.mockResolvedValue('vuid_valid'); + + const manager = new DefaultVuidManager({ + vuidCache: getMockAsyncCache<string>(), + vuidCacheManager: vuidCacheManager as unknown as VuidCacheManager, + enableVuid: true + }); + + await manager.initialize(); + expect(vuidCacheManager.load).toHaveBeenCalled(); + expect(manager.getVuid()).toBe('vuid_valid'); + }); +}); diff --git a/lib/vuid/vuid_manager.ts b/lib/vuid/vuid_manager.ts new file mode 100644 index 000000000..dd0c0322a --- /dev/null +++ b/lib/vuid/vuid_manager.ts @@ -0,0 +1,132 @@ +/** + * Copyright 2022-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { LoggerFacade } from '../logging/logger'; +import { Store } from '../utils/cache/store'; +import { AsyncProducer, Maybe } from '../utils/type'; +import { isVuid, makeVuid } from './vuid'; + +export interface VuidManager { + getVuid(): Maybe<string>; + isVuidEnabled(): boolean; + initialize(): Promise<void>; +} + +export class VuidCacheManager { + private logger?: LoggerFacade; + private vuidCacheKey = 'optimizely-vuid'; + private cache?: Store<string>; + // if this value is not undefined, this means the same value is in the cache. + // if this is undefined, it could either mean that there is no value in the cache + // or that there is a value in the cache but it has not been loaded yet or failed + // to load. + private vuid?: string; + private waitPromise: Promise<unknown> = Promise.resolve(); + + constructor(cache?: Store<string>, logger?: LoggerFacade) { + this.cache = cache; + this.logger = logger; + } + + setCache(cache: Store<string>): void { + this.cache = cache; + this.vuid = undefined; + } + + setLogger(logger: LoggerFacade): void { + this.logger = logger; + } + + private async serialize<T>(fn: AsyncProducer<T>): Promise<T> { + const resultPromise = this.waitPromise.then(fn, fn); + this.waitPromise = resultPromise.catch(() => {}); + return resultPromise; + } + + async remove(): Promise<unknown> { + const removeFn = async () => { + if (!this.cache) { + return; + } + this.vuid = undefined; + await this.cache.remove(this.vuidCacheKey); + } + + return this.serialize(removeFn); + } + + async load(): Promise<Maybe<string>> { + if (this.vuid) { + return this.vuid; + } + + const loadFn = async () => { + if (!this.cache) { + return; + } + const cachedValue = await this.cache.get(this.vuidCacheKey); + if (cachedValue && isVuid(cachedValue)) { + this.vuid = cachedValue; + return this.vuid; + } + const newVuid = makeVuid(); + await this.cache.set(this.vuidCacheKey, newVuid); + this.vuid = newVuid; + return newVuid; + } + return this.serialize(loadFn); + } +} + +export type VuidManagerConfig = { + enableVuid?: boolean; + vuidCache: Store<string>; + vuidCacheManager: VuidCacheManager; +} + +export class DefaultVuidManager implements VuidManager { + private vuidCacheManager: VuidCacheManager; + private vuid?: string; + private vuidCache: Store<string>; + private vuidEnabled = false; + + constructor(config: VuidManagerConfig) { + this.vuidCacheManager = config.vuidCacheManager; + this.vuidEnabled = config.enableVuid || false; + this.vuidCache = config.vuidCache; + } + + getVuid(): Maybe<string> { + return this.vuid; + } + + isVuidEnabled(): boolean { + return this.vuidEnabled; + } + + /** + * initializes the VuidManager + * @returns Promise that resolves when the VuidManager is initialized + */ + async initialize(): Promise<void> { + this.vuidCacheManager.setCache(this.vuidCache); + if (!this.vuidEnabled) { + await this.vuidCacheManager.remove(); + return; + } + + this.vuid = await this.vuidCacheManager.load(); + } +} diff --git a/lib/vuid/vuid_manager_factory.browser.spec.ts b/lib/vuid/vuid_manager_factory.browser.spec.ts new file mode 100644 index 000000000..59c8602db --- /dev/null +++ b/lib/vuid/vuid_manager_factory.browser.spec.ts @@ -0,0 +1,85 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { vi, describe, expect, it, beforeEach } from 'vitest'; + +vi.mock('../utils/cache/local_storage_cache.browser', () => { + return { + LocalStorageCache: vi.fn(), + }; +}); + +vi.mock('./vuid_manager', () => { + return { + DefaultVuidManager: vi.fn(), + VuidCacheManager: vi.fn(), + }; +}); + +import { getMockSyncCache } from '../tests/mock/mock_cache'; +import { createVuidManager } from './vuid_manager_factory.browser'; +import { LocalStorageCache } from '../utils/cache/local_storage_cache.browser'; +import { DefaultVuidManager, VuidCacheManager } from './vuid_manager'; +import { extractVuidManager } from './vuid_manager_factory'; + +describe('createVuidManager', () => { + const MockVuidCacheManager = vi.mocked(VuidCacheManager); + const MockLocalStorageCache = vi.mocked(LocalStorageCache); + const MockDefaultVuidManager = vi.mocked(DefaultVuidManager); + + beforeEach(() => { + MockLocalStorageCache.mockClear(); + MockDefaultVuidManager.mockClear(); + }); + + it('should pass the enableVuid option to the DefaultVuidManager', () => { + const manager = extractVuidManager(createVuidManager({ enableVuid: true })); + expect(manager).toBe(MockDefaultVuidManager.mock.instances[0]); + expect(MockDefaultVuidManager.mock.calls[0][0].enableVuid).toBe(true); + + const manager2 = extractVuidManager(createVuidManager({ enableVuid: false })); + expect(manager2).toBe(MockDefaultVuidManager.mock.instances[1]); + expect(MockDefaultVuidManager.mock.calls[1][0].enableVuid).toBe(false); + }); + + it('should use the provided cache', () => { + const cache = getMockSyncCache<string>(); + const manager = extractVuidManager(createVuidManager({ enableVuid: true, vuidCache: cache })); + expect(manager).toBe(MockDefaultVuidManager.mock.instances[0]); + expect(MockDefaultVuidManager.mock.calls[0][0].vuidCache).toBe(cache); + }); + + it('should use a LocalStorageCache if no cache is provided', () => { + const manager = extractVuidManager(createVuidManager({ enableVuid: true })); + expect(manager).toBe(MockDefaultVuidManager.mock.instances[0]); + + const usedCache = MockDefaultVuidManager.mock.calls[0][0].vuidCache; + expect(usedCache).toBe(MockLocalStorageCache.mock.instances[0]); + }); + + it('should use a single VuidCacheManager instance for all VuidManager instances', () => { + const manager1 = extractVuidManager(createVuidManager({ enableVuid: true })); + const manager2 = extractVuidManager(createVuidManager({ enableVuid: true })); + expect(manager1).toBe(MockDefaultVuidManager.mock.instances[0]); + expect(manager2).toBe(MockDefaultVuidManager.mock.instances[1]); + expect(MockVuidCacheManager.mock.instances.length).toBe(1); + + const usedCacheManager1 = MockDefaultVuidManager.mock.calls[0][0].vuidCacheManager; + const usedCacheManager2 = MockDefaultVuidManager.mock.calls[1][0].vuidCacheManager; + expect(usedCacheManager1).toBe(usedCacheManager2); + expect(usedCacheManager1).toBe(MockVuidCacheManager.mock.instances[0]); + }); +}); diff --git a/lib/vuid/vuid_manager_factory.browser.ts b/lib/vuid/vuid_manager_factory.browser.ts new file mode 100644 index 000000000..0691fd5e7 --- /dev/null +++ b/lib/vuid/vuid_manager_factory.browser.ts @@ -0,0 +1,28 @@ +/** +* Copyright 2024-2025, Optimizely +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +import { DefaultVuidManager, VuidCacheManager, VuidManager } from './vuid_manager'; +import { LocalStorageCache } from '../utils/cache/local_storage_cache.browser'; +import { OpaqueVuidManager, VuidManagerOptions, wrapVuidManager } from './vuid_manager_factory'; + +export const vuidCacheManager = new VuidCacheManager(); + +export const createVuidManager = (options: VuidManagerOptions = {}): OpaqueVuidManager => { + return wrapVuidManager(new DefaultVuidManager({ + vuidCacheManager, + vuidCache: options.vuidCache || new LocalStorageCache<string>(), + enableVuid: options.enableVuid + })); +}; diff --git a/packages/optimizely-sdk/lib/plugins/event_processor/index.react_native.ts b/lib/vuid/vuid_manager_factory.node.spec.ts similarity index 51% rename from packages/optimizely-sdk/lib/plugins/event_processor/index.react_native.ts rename to lib/vuid/vuid_manager_factory.node.spec.ts index 277512a6e..8f6b21e74 100644 --- a/packages/optimizely-sdk/lib/plugins/event_processor/index.react_native.ts +++ b/lib/vuid/vuid_manager_factory.node.spec.ts @@ -1,11 +1,11 @@ /** - * Copyright 2022, Optimizely + * Copyright 2024, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -14,13 +14,14 @@ * limitations under the License. */ -import { LogTierV1EventProcessor, LocalStoragePendingEventsDispatcher } from '../../../lib/modules/event_processor/index.react_native'; +import { vi, describe, expect, it } from 'vitest'; -export function createEventProcessor( - ...args: ConstructorParameters<typeof LogTierV1EventProcessor> -): LogTierV1EventProcessor { - return new LogTierV1EventProcessor(...args); -} +import { createVuidManager } from './vuid_manager_factory.node'; +import { extractVuidManager } from './vuid_manager_factory'; -export default { createEventProcessor, LocalStoragePendingEventsDispatcher }; - +describe('createVuidManager', () => { + it('should return a undefined vuid manager wrapped as OpaqueVuidManager', () => { + expect(extractVuidManager(createVuidManager({ enableVuid: true }))) + .toBeUndefined(); + }); +}); diff --git a/lib/vuid/vuid_manager_factory.node.ts b/lib/vuid/vuid_manager_factory.node.ts new file mode 100644 index 000000000..439e70ec1 --- /dev/null +++ b/lib/vuid/vuid_manager_factory.node.ts @@ -0,0 +1,20 @@ +/** +* Copyright 2024-2025, Optimizely +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +import { OpaqueVuidManager, VuidManagerOptions, wrapVuidManager } from './vuid_manager_factory'; + +export const createVuidManager = (options: VuidManagerOptions = {}): OpaqueVuidManager => { + return wrapVuidManager(undefined); +}; diff --git a/lib/vuid/vuid_manager_factory.react_native.spec.ts b/lib/vuid/vuid_manager_factory.react_native.spec.ts new file mode 100644 index 000000000..8057946e3 --- /dev/null +++ b/lib/vuid/vuid_manager_factory.react_native.spec.ts @@ -0,0 +1,86 @@ +/** + * Copyright 2024, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { vi, describe, expect, it, beforeEach } from 'vitest'; + +vi.mock('../utils/cache/async_storage_cache.react_native', () => { + return { + AsyncStorageCache: vi.fn(), + }; +}); + +vi.mock('./vuid_manager', () => { + return { + DefaultVuidManager: vi.fn(), + VuidCacheManager: vi.fn(), + }; +}); + +import { getMockAsyncCache } from '../tests/mock/mock_cache'; +import { createVuidManager } from './vuid_manager_factory.react_native'; +import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; + +import { DefaultVuidManager, VuidCacheManager } from './vuid_manager'; +import { extractVuidManager } from './vuid_manager_factory'; + +describe('extractVuidManager(createVuidManager', () => { + const MockVuidCacheManager = vi.mocked(VuidCacheManager); + const MockAsyncStorageCache = vi.mocked(AsyncStorageCache); + const MockDefaultVuidManager = vi.mocked(DefaultVuidManager); + + beforeEach(() => { + MockAsyncStorageCache.mockClear(); + MockDefaultVuidManager.mockClear(); + }); + + it('should pass the enableVuid option to the DefaultVuidManager', () => { + const manager = extractVuidManager(createVuidManager({ enableVuid: true })); + expect(manager).toBe(MockDefaultVuidManager.mock.instances[0]); + expect(MockDefaultVuidManager.mock.calls[0][0].enableVuid).toBe(true); + + const manager2 = extractVuidManager(createVuidManager({ enableVuid: false })); + expect(manager2).toBe(MockDefaultVuidManager.mock.instances[1]); + expect(MockDefaultVuidManager.mock.calls[1][0].enableVuid).toBe(false); + }); + + it('should use the provided cache', () => { + const cache = getMockAsyncCache<string>(); + const manager = extractVuidManager(createVuidManager({ enableVuid: true, vuidCache: cache })); + expect(manager).toBe(MockDefaultVuidManager.mock.instances[0]); + expect(MockDefaultVuidManager.mock.calls[0][0].vuidCache).toBe(cache); + }); + + it('should use a AsyncStorageCache if no cache is provided', () => { + const manager = extractVuidManager(createVuidManager({ enableVuid: true })); + expect(manager).toBe(MockDefaultVuidManager.mock.instances[0]); + + const usedCache = MockDefaultVuidManager.mock.calls[0][0].vuidCache; + expect(usedCache).toBe(MockAsyncStorageCache.mock.instances[0]); + }); + + it('should use a single VuidCacheManager instance for all VuidManager instances', () => { + const manager1 = extractVuidManager(createVuidManager({ enableVuid: true })); + const manager2 = extractVuidManager(createVuidManager({ enableVuid: true })); + expect(manager1).toBe(MockDefaultVuidManager.mock.instances[0]); + expect(manager2).toBe(MockDefaultVuidManager.mock.instances[1]); + expect(MockVuidCacheManager.mock.instances.length).toBe(1); + + const usedCacheManager1 = MockDefaultVuidManager.mock.calls[0][0].vuidCacheManager; + const usedCacheManager2 = MockDefaultVuidManager.mock.calls[1][0].vuidCacheManager; + expect(usedCacheManager1).toBe(usedCacheManager2); + expect(usedCacheManager1).toBe(MockVuidCacheManager.mock.instances[0]); + }); +}); diff --git a/lib/vuid/vuid_manager_factory.react_native.ts b/lib/vuid/vuid_manager_factory.react_native.ts new file mode 100644 index 000000000..0aeb1c537 --- /dev/null +++ b/lib/vuid/vuid_manager_factory.react_native.ts @@ -0,0 +1,28 @@ +/** +* Copyright 2024-2025, Optimizely +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* https://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ +import { DefaultVuidManager, VuidCacheManager, VuidManager } from './vuid_manager'; +import { AsyncStorageCache } from '../utils/cache/async_storage_cache.react_native'; +import { OpaqueVuidManager, VuidManagerOptions, wrapVuidManager } from './vuid_manager_factory'; + +export const vuidCacheManager = new VuidCacheManager(); + +export const createVuidManager = (options: VuidManagerOptions = {}): OpaqueVuidManager => { + return wrapVuidManager(new DefaultVuidManager({ + vuidCacheManager, + vuidCache: options.vuidCache || new AsyncStorageCache<string>(), + enableVuid: options.enableVuid + })); +}; diff --git a/lib/vuid/vuid_manager_factory.ts b/lib/vuid/vuid_manager_factory.ts new file mode 100644 index 000000000..f7f1b760f --- /dev/null +++ b/lib/vuid/vuid_manager_factory.ts @@ -0,0 +1,44 @@ +/** + * Copyright 2024-2025, Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Store } from '../utils/cache/store'; +import { Maybe } from '../utils/type'; +import { VuidManager } from './vuid_manager'; + +export type VuidManagerOptions = { + vuidCache?: Store<string>; + enableVuid?: boolean; +} + +const vuidManagerSymbol: unique symbol = Symbol(); + +export type OpaqueVuidManager = { + [vuidManagerSymbol]: unknown; +}; + +export const extractVuidManager = (opaqueVuidManager: Maybe<OpaqueVuidManager>): Maybe<VuidManager> => { + if (!opaqueVuidManager || typeof opaqueVuidManager !== 'object') { + return undefined; + } + + return opaqueVuidManager[vuidManagerSymbol] as Maybe<VuidManager>; +}; + +export const wrapVuidManager = (vuidManager: Maybe<VuidManager>): OpaqueVuidManager => { + return { + [vuidManagerSymbol]: vuidManager + } +}; diff --git a/message_generator.ts b/message_generator.ts new file mode 100644 index 000000000..fae725a1c --- /dev/null +++ b/message_generator.ts @@ -0,0 +1,36 @@ +import path from 'path'; +import { writeFile } from 'fs/promises'; + +const generate = async () => { + const inp = process.argv.slice(2); + for(const filePath of inp) { + console.log('generating messages for: ', filePath); + const parsedPath = path.parse(filePath); + const fileName = parsedPath.name; + const dirName = parsedPath.dir; + const ext = parsedPath.ext; + + const genFilePath = path.join(dirName, `${fileName}.gen${ext}`); + console.log('generated file path: ', genFilePath); + const exports = await import(filePath); + const messages : Array<any> = []; + + let genOut = ''; + + Object.keys(exports).forEach((key, i) => { + if (key === 'messages') return; + genOut += `export const ${key} = '${i}';\n`; + messages.push(exports[key]) + }); + + genOut += `export const messages = ${JSON.stringify(messages, null, 2)};` + await writeFile(genFilePath, genOut, 'utf-8'); + } +} + +generate().then(() => { + console.log('successfully generated messages'); +}).catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/package-lock.json b/package-lock.json index 5bc8a6877..05ce4f677 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5072 +1,14599 @@ { + "name": "@optimizely/optimizely-sdk", + "version": "6.2.0", + "lockfileVersion": 3, "requires": true, - "lockfileVersion": 1, - "dependencies": { - "@lerna/add": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@lerna/add/-/add-3.2.0.tgz", - "integrity": "sha512-qGA7agAWcKlrXZR3FwFJXTr26Q2rqjOVMNhtm8uyawImqfdKp4WJXuGdioiWOSW20jMvzLIFhWZh5lCh0UyMBw==", - "requires": { - "@lerna/bootstrap": "^3.2.0", - "@lerna/command": "^3.1.3", - "@lerna/filter-options": "^3.1.2", - "@lerna/npm-conf": "^3.0.0", - "@lerna/validation-error": "^3.0.0", - "dedent": "^0.7.0", - "npm-package-arg": "^6.0.0", - "p-map": "^1.2.0", - "pacote": "^9.1.0", - "semver": "^5.5.0" - } - }, - "@lerna/batch-packages": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@lerna/batch-packages/-/batch-packages-3.1.2.tgz", - "integrity": "sha512-HAkpptrYeUVlBYbLScXgeCgk6BsNVXxDd53HVWgzzTWpXV4MHpbpeKrByyt7viXlNhW0w73jJbipb/QlFsHIhQ==", - "requires": { - "@lerna/package-graph": "^3.1.2", - "@lerna/validation-error": "^3.0.0", - "npmlog": "^4.1.2" - } - }, - "@lerna/bootstrap": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@lerna/bootstrap/-/bootstrap-3.2.0.tgz", - "integrity": "sha512-xh6dPpdzsAEWF7lqASaym5AThkmP3ArR7Q+P/tiPWCT+OT7QT5QI2IQAz1aAYEBQL3ACzpE6kq+VOGi0m+9bxw==", - "requires": { - "@lerna/batch-packages": "^3.1.2", - "@lerna/command": "^3.1.3", - "@lerna/filter-options": "^3.1.2", - "@lerna/has-npm-version": "^3.0.4", - "@lerna/npm-conf": "^3.0.0", - "@lerna/npm-install": "^3.0.0", - "@lerna/rimraf-dir": "^3.0.0", - "@lerna/run-lifecycle": "^3.2.0", - "@lerna/run-parallel-batches": "^3.0.0", - "@lerna/symlink-binary": "^3.1.4", - "@lerna/symlink-dependencies": "^3.1.4", - "@lerna/validation-error": "^3.0.0", - "dedent": "^0.7.0", - "get-port": "^3.2.0", - "multimatch": "^2.1.0", - "npm-package-arg": "^6.0.0", - "npmlog": "^4.1.2", - "p-finally": "^1.0.0", - "p-map": "^1.2.0", - "p-map-series": "^1.0.0", - "p-waterfall": "^1.0.0", - "read-package-tree": "^5.1.6", - "semver": "^5.5.0" - } - }, - "@lerna/changed": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@lerna/changed/-/changed-3.2.0.tgz", - "integrity": "sha512-R+vGzzXPN5s5lJT0v1zSTLw43O2ek2yekqCqvw7p9UFqgqYSbxUsyWXMdhku/mOIFWTc6DzrsOi+U7CX3TXmHg==", - "requires": { - "@lerna/collect-updates": "^3.1.0", - "@lerna/command": "^3.1.3", - "@lerna/listable": "^3.0.0", - "@lerna/output": "^3.0.0", - "@lerna/version": "^3.2.0" - } - }, - "@lerna/check-working-tree": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@lerna/check-working-tree/-/check-working-tree-3.1.0.tgz", - "integrity": "sha512-ruy6s44IUcaPEa4JlDDDk6nbacMESUPRSb+dohzLJxfhXx1wFnEVF6L91TGxFP+C0lt5V6zd8rnJxkW/uZzNAA==", - "requires": { - "@lerna/describe-ref": "^3.1.0", - "@lerna/validation-error": "^3.0.0" - } - }, - "@lerna/child-process": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/child-process/-/child-process-3.0.0.tgz", - "integrity": "sha512-8vHRDkpGhzSaMsXgyXVgY80mUSC5WSkDmhWWA3bnB/n5FBK1gK8EKQUpHTk14SckwvUgEJzBd35gR5/XKGOgmQ==", - "requires": { - "chalk": "^2.3.1", - "execa": "^0.10.0", - "strong-log-transformer": "^1.0.6" - } - }, - "@lerna/clean": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@lerna/clean/-/clean-3.1.3.tgz", - "integrity": "sha512-XVdcIOjhudXlk5pTXjrpsnNLqeVi2rBu2oWzPH2GHrxWGBZBW8thGIFhQf09da/RbRT3uzBWXpUv+sbL2vbX3g==", - "requires": { - "@lerna/command": "^3.1.3", - "@lerna/filter-options": "^3.1.2", - "@lerna/prompt": "^3.0.0", - "@lerna/rimraf-dir": "^3.0.0", - "p-map": "^1.2.0", - "p-map-series": "^1.0.0", - "p-waterfall": "^1.0.0" - } - }, - "@lerna/cli": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@lerna/cli/-/cli-3.2.0.tgz", - "integrity": "sha512-JdbLyTxHqxUlrkI+Ke+ltXbtyA+MPu9zR6kg/n8Fl6uaez/2fZWtReXzYi8MgLxfUFa7+1OHWJv4eAMZlByJ+Q==", - "requires": { - "@lerna/global-options": "^3.1.3", - "dedent": "^0.7.0", - "npmlog": "^4.1.2", - "yargs": "^12.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - } - }, - "decamelize": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-2.0.0.tgz", - "integrity": "sha512-Ikpp5scV3MSYxY39ymh45ZLEecsTdv/Xj2CaQfI8RLMuwi7XvjX9H/fhraiSuU+C5w5NTDu4ZU72xNiZnurBPg==", - "requires": { - "xregexp": "4.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "p-limit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.0.0.tgz", - "integrity": "sha512-fl5s52lI5ahKCernzzIyAP0QAZbGIovtVHGwpcu1Jr/EpzLVDI2myISHwGqK7m8uQFugVWSrbxH7XnhGtvEc+A==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } + "packages": { + "": { + "name": "@optimizely/optimizely-sdk", + "version": "6.2.0", + "license": "Apache-2.0", + "dependencies": { + "decompress-response": "^7.0.0", + "json-schema": "^0.4.0", + "murmurhash": "^2.0.1", + "uuid": "^10.0.0" + }, + "devDependencies": { + "@react-native-async-storage/async-storage": "^2", + "@react-native-community/netinfo": "^11.3.2", + "@rollup/plugin-commonjs": "^11.0.2", + "@rollup/plugin-node-resolve": "^7.1.1", + "@types/chai": "^4.2.11", + "@types/mocha": "^5.2.7", + "@types/nise": "^1.4.0", + "@types/node": "^18.7.18", + "@types/ua-parser-js": "^0.7.36", + "@types/uuid": "^10.0.0", + "@typescript-eslint/eslint-plugin": "^5.33.0", + "@typescript-eslint/parser": "^5.33.0", + "@vitest/coverage-istanbul": "^2.0.5", + "chai": "^4.2.0", + "coveralls-next": "^4.2.0", + "eslint": "^8.21.0", + "eslint-config-prettier": "^6.10.0", + "eslint-plugin-prettier": "^3.1.2", + "happy-dom": "^16.6.0", + "jiti": "^2.4.1", + "karma": "^6.4.0", + "karma-browserstack-launcher": "^1.5.1", + "karma-chai": "^0.1.0", + "karma-chrome-launcher": "^2.1.1", + "karma-mocha": "^2.0.1", + "karma-webpack": "^5.0.1", + "lodash": "^4.17.11", + "mocha": "^10.2.0", + "mocha-lcov-reporter": "^1.3.0", + "nise": "^1.4.10", + "nock": "11.9.1", + "nyc": "^15.0.1", + "prettier": "^1.19.1", + "promise-polyfill": "8.1.0", + "rollup": "2.79.2", + "rollup-plugin-terser": "^5.3.0", + "rollup-plugin-typescript2": "^0.27.1", + "sinon": "^2.3.1", + "ts-loader": "^9.3.1", + "ts-node": "^8.10.2", + "tsconfig-paths": "^4.2.0", + "typescript": "^4.7.4", + "vitest": "^2.0.5", + "webpack": "^5.74.0" + }, + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "@react-native-async-storage/async-storage": ">=1.0.0 <3.0.0", + "@react-native-community/netinfo": ">=5.0.0 <12.0.0", + "fast-text-encoding": "^1.0.6", + "react-native-get-random-values": "^1.11.0", + "ua-parser-js": "^1.0.38" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true }, - "p-try": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.0.0.tgz", - "integrity": "sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ==" + "@react-native-community/netinfo": { + "optional": true }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } + "fast-text-encoding": { + "optional": true }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } + "react-native-get-random-values": { + "optional": true }, - "yargs": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.1.tgz", - "integrity": "sha512-B0vRAp1hRX4jgIOWFtjfNjd9OA9RWYZ6tqGA9/I/IrTMsxmKvtWy+ersM+jzpQqbC3YfLzeABPdeTgcJ9eu1qQ==", - "requires": { - "cliui": "^4.0.0", - "decamelize": "^2.0.0", - "find-up": "^3.0.0", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1 || ^4.0.0", - "yargs-parser": "^10.1.0" - } + "ua-parser-js": { + "optional": true } } }, - "@lerna/collect-updates": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@lerna/collect-updates/-/collect-updates-3.1.0.tgz", - "integrity": "sha512-zHxHRZOteTqcW9mAyLrmoWEKpfxgA3c6LJj4nauB2pM3MKyKNhg0gqiy2RHFu7EGivPki4Q1624I301iAXtUVA==", - "requires": { - "@lerna/child-process": "^3.0.0", - "@lerna/describe-ref": "^3.1.0", - "minimatch": "^3.0.4", - "npmlog": "^4.1.2", - "slash": "^1.0.0" + "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" } }, - "@lerna/command": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@lerna/command/-/command-3.1.3.tgz", - "integrity": "sha512-ptaFNsfcTpxnSkaNrGgW3fRbWWVSVDUx4BpkjUUnRTgy9mwoykQWgQB3inhgTYHwW6e4PzO79F2hovfUMzHuzg==", - "requires": { - "@lerna/child-process": "^3.0.0", - "@lerna/package-graph": "^3.1.2", - "@lerna/project": "^3.0.0", - "@lerna/validation-error": "^3.0.0", - "@lerna/write-log-file": "^3.0.0", - "dedent": "^0.7.0", - "execa": "^0.10.0", - "is-ci": "^1.0.10", - "lodash": "^4.17.5", - "npmlog": "^4.1.2" - } - }, - "@lerna/conventional-commits": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@lerna/conventional-commits/-/conventional-commits-3.0.2.tgz", - "integrity": "sha512-Cxd1eWXn3usADKXIUvYmTERx2+1N7oJD4Whz+FVu8kTfufsfTO7fYOan1RVkg86ukZbNDyS+iOxZ8DJ2JspS9g==", - "requires": { - "@lerna/validation-error": "^3.0.0", - "conventional-changelog-angular": "^1.6.6", - "conventional-changelog-core": "^2.0.5", - "conventional-recommended-bump": "^2.0.6", - "dedent": "^0.7.0", - "fs-extra": "^6.0.1", - "get-stream": "^3.0.0", - "npm-package-arg": "^6.0.0", - "npmlog": "^4.1.2", - "semver": "^5.5.0" - } - }, - "@lerna/create": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@lerna/create/-/create-3.1.3.tgz", - "integrity": "sha512-CmXKCBc6AE3F9O6mMg4Y76cQ8eTCy3ksqDFKxVQMdYDtHKnTrH1s0l8sn3J1AiylqVDnvxhb3Rjyj+OWyzmFPQ==", - "requires": { - "@lerna/child-process": "^3.0.0", - "@lerna/command": "^3.1.3", - "@lerna/npm-conf": "^3.0.0", - "@lerna/validation-error": "^3.0.0", - "camelcase": "^4.1.0", - "dedent": "^0.7.0", - "fs-extra": "^6.0.1", - "globby": "^8.0.1", - "init-package-json": "^1.10.3", - "npm-package-arg": "^6.0.0", - "pify": "^3.0.0", - "semver": "^5.5.0", - "slash": "^1.0.0", - "validate-npm-package-license": "^3.0.3", - "validate-npm-package-name": "^3.0.0", - "whatwg-url": "^6.5.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" - } + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" } }, - "@lerna/create-symlink": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/create-symlink/-/create-symlink-3.0.0.tgz", - "integrity": "sha512-Q9qAzGGqQtVzHWrCz+Md4SH0tW99DrgFJ68cnFqilOO6H3Y/y/H0gwHICqM9YxRwLs6GJdkzoqJATFShM7PKJA==", - "requires": { - "cmd-shim": "^2.0.2", - "fs-extra": "^6.0.1", - "npmlog": "^4.1.2" + "node_modules/@babel/code-frame": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.24.7", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" } }, - "@lerna/describe-ref": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@lerna/describe-ref/-/describe-ref-3.1.0.tgz", - "integrity": "sha512-0a7WFKDSmdEwwmEj+ZfhI7SkkG1CDcVhfW8VhPqr6gnCMY+ryt6iV/rR7ygb0eCDg8wEe9eQsiwbnrbXDLjIDw==", - "requires": { - "@lerna/child-process": "^3.0.0", - "npmlog": "^4.1.2" + "node_modules/@babel/compat-data": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.2.tgz", + "integrity": "sha512-bYcppcpKBvX4znYaPEeFau03bp89ShqNMLs+rmdptMw+heSZh9+z84d2YG+K7cYLbWwzdjtDoW/uqZmPjulClQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" } }, - "@lerna/diff": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@lerna/diff/-/diff-3.1.3.tgz", - "integrity": "sha512-30G74DxdQC4dR3U0yqh5mjcioLDUmSy1ntntdF3khvKV9oiMVzCSOO0oOlSwIdmohke+bQ//oF+oyl0Dy1TUTQ==", - "requires": { - "@lerna/child-process": "^3.0.0", - "@lerna/command": "^3.1.3", - "@lerna/validation-error": "^3.0.0", - "npmlog": "^4.1.2" + "node_modules/@babel/core": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", + "dev": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" } }, - "@lerna/exec": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@lerna/exec/-/exec-3.1.3.tgz", - "integrity": "sha512-r0yQj9Rza5a42Shts8rXYGU1/Va8hO2atk/TEZgrA1EcTwgZyAuXsuML5UWbC/eLgwEjVDmc3MUSj8O1JijBMQ==", - "requires": { - "@lerna/batch-packages": "^3.1.2", - "@lerna/child-process": "^3.0.0", - "@lerna/command": "^3.1.3", - "@lerna/filter-options": "^3.1.2", - "@lerna/run-parallel-batches": "^3.0.0", - "@lerna/validation-error": "^3.0.0" - } - }, - "@lerna/filter-options": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@lerna/filter-options/-/filter-options-3.1.2.tgz", - "integrity": "sha512-smbvSGK/eU+7PDKO4jbJ7XYO2XTfhnwPeOTuwSm1mlWS5dUGasYkhAuFzouFh60aZBvmW0e6APe0XYeofQNcCg==", - "requires": { - "@lerna/collect-updates": "^3.1.0", - "@lerna/filter-packages": "^3.0.0", - "dedent": "^0.7.0" + "node_modules/@babel/core/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/@babel/core/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/@babel/generator": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.0.tgz", + "integrity": "sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw==", + "dev": true, + "dependencies": { + "@babel/types": "^7.25.0", + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "@lerna/filter-packages": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/filter-packages/-/filter-packages-3.0.0.tgz", - "integrity": "sha512-zwbY1J4uRjWRZ/FgYbtVkq7I3Nduwsg2V2HwLKSzwV2vPglfGqgovYOVkND6/xqe2BHwDX4IyA2+e7OJmLaLSA==", - "requires": { - "@lerna/validation-error": "^3.0.0", - "multimatch": "^2.1.0", - "npmlog": "^4.1.2" + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" } }, - "@lerna/get-npm-exec-opts": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/get-npm-exec-opts/-/get-npm-exec-opts-3.0.0.tgz", - "integrity": "sha512-arcYUm+4xS8J3Palhl+5rRJXnZnFHsLFKHBxznkPIxjwGQeAEw7df38uHdVjEQ+HNeFmHnBgSqfbxl1VIw5DHg==", - "requires": { - "npmlog": "^4.1.2" + "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", + "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" } }, - "@lerna/global-options": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@lerna/global-options/-/global-options-3.1.3.tgz", - "integrity": "sha512-LVeZU/Zgc0XkHdGMRYn+EmHfDmmYNwYRv3ta59iCVFXLVp7FRFWF7oB1ss/WRa9x/pYU0o6L8as/5DomLUGASA==" - }, - "@lerna/has-npm-version": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@lerna/has-npm-version/-/has-npm-version-3.0.4.tgz", - "integrity": "sha512-RisEWZBROi8corPb/PUIQqT+xGPLeriJ/n6VCeO6GROCO1fyYBX7kgFmVpFOytufWFkI04qBgLaUs+CEc8Yspg==", - "requires": { - "@lerna/child-process": "^3.0.0", - "semver": "^5.5.0" + "node_modules/@babel/helper-compilation-targets": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" } }, - "@lerna/import": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@lerna/import/-/import-3.1.3.tgz", - "integrity": "sha512-+XV/EHXEHbyMmprz8zQa0VF0TZ+txRIrcF3Q/PsuZ4isVxawIbP1CmgE0yn0/1XSNJwGKsuPfGauRtnjUi2LJw==", - "requires": { - "@lerna/child-process": "^3.0.0", - "@lerna/command": "^3.1.3", - "@lerna/prompt": "^3.0.0", - "@lerna/validation-error": "^3.0.0", - "dedent": "^0.7.0", - "fs-extra": "^6.0.1", - "p-map-series": "^1.0.0" - } - }, - "@lerna/init": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@lerna/init/-/init-3.1.3.tgz", - "integrity": "sha512-c418p6fAfJ+b/tidB8/O/ABGLX7a5y5uJSWxH2/Mp7i5da/RD27XJ6E6818hGAbUbVQw04+XuXHtrWYlWLEJCw==", - "requires": { - "@lerna/child-process": "^3.0.0", - "@lerna/command": "^3.1.3", - "fs-extra": "^6.0.1", - "p-map": "^1.2.0", - "write-json-file": "^2.3.0" - } - }, - "@lerna/link": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@lerna/link/-/link-3.1.4.tgz", - "integrity": "sha512-AAl4ctKtE6975zxdrsr16CAh/K4y0ZjjY9XDdD+zMxsSPELvRVG6M36O1A72AmKz5Nhh1l82bPrw7w54TbQ8hA==", - "requires": { - "@lerna/command": "^3.1.3", - "@lerna/package-graph": "^3.1.2", - "@lerna/symlink-dependencies": "^3.1.4", - "p-map": "^1.2.0", - "slash": "^1.0.0" - } - }, - "@lerna/list": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@lerna/list/-/list-3.1.3.tgz", - "integrity": "sha512-/ncX5Kj1ddLgZuBkjaoluJgcmAAm/L4/AymVNBgVrw6dOad0C0RB6oIcRAbxDennEQ25wSOFmuXRZHYHY9VYyg==", - "requires": { - "@lerna/command": "^3.1.3", - "@lerna/filter-options": "^3.1.2", - "@lerna/listable": "^3.0.0", - "@lerna/output": "^3.0.0" + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" } }, - "@lerna/listable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/listable/-/listable-3.0.0.tgz", - "integrity": "sha512-HX/9hyx1HLg2kpiKXIUc1EimlkK1T58aKQ7ovO7rQdTx9ForpefoMzyLnHE1n4XrUtEszcSWJIICJ/F898M6Ag==", - "requires": { - "chalk": "^2.3.1", - "columnify": "^1.5.4" + "node_modules/@babel/helper-compilation-targets/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" } }, - "@lerna/log-packed": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@lerna/log-packed/-/log-packed-3.0.4.tgz", - "integrity": "sha512-vVQHgMagE2wnbxhNY9nFkdu+Cx2TsyWalkJfkxbNzmo6gOCrDsxCBDj9vTEV8Q+4aWx0C0Bsc0sB2Eb8y/+ofA==", - "requires": { - "byte-size": "^4.0.3", - "columnify": "^1.5.4", - "has-unicode": "^2.0.1", - "npmlog": "^4.1.2" + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.0.tgz", + "integrity": "sha512-GYM6BxeQsETc9mnct+nIIpf63SAyzvyYN7UB/IlTyd+MBg06afFGp0mIeUqGyWgS2mxad6vqbMrHVlaL3m70sQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/traverse": "^7.25.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin/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, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.25.2.tgz", + "integrity": "sha512-+wqVGP+DFmqwFD3EH6TMTfUNeqDehV3E/dl+Sd54eaXqm17tEUNbEIn4sVivVowbvUpOtIGxdo3GoXyDH9N/9g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "regexpu-core": "^5.3.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-regexp-features-plugin/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, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz", + "integrity": "sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.22.6", + "@babel/helper-plugin-utils": "^7.22.5", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "@lerna/npm-conf": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/npm-conf/-/npm-conf-3.0.0.tgz", - "integrity": "sha512-xXG7qt349t+xzaHTQELmIDjbq8Q49HOMR8Nx/gTDBkMl02Fno91LXFnA4A7ErPiyUSGqNSfLw+zgij0hgpeN7w==", - "requires": { - "config-chain": "^1.1.11", - "pify": "^3.0.0" + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz", + "integrity": "sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/traverse": "^7.24.8", + "@babel/types": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" } }, - "@lerna/npm-dist-tag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/npm-dist-tag/-/npm-dist-tag-3.0.0.tgz", - "integrity": "sha512-ZOcfcsNJlCoVHvLOROdCTvqD3keG3TJ78Cu8sALsz8n0kEz2ga7tNy5wbQ67SGyY7+jq4YiBv5BwXjV+56Sv+A==", - "requires": { - "@lerna/child-process": "^3.0.0", - "@lerna/get-npm-exec-opts": "^3.0.0", - "npmlog": "^4.1.2" + "node_modules/@babel/helper-module-imports": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" } }, - "@lerna/npm-install": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/npm-install/-/npm-install-3.0.0.tgz", - "integrity": "sha512-e0sspVUfzEKhqsRIxzWqZ/uMBHzZSzOa4HCeORErEZu+dmDoI145XYhqvCVn7EvbAb407FV2H9GVeoP0JeG8GQ==", - "requires": { - "@lerna/child-process": "^3.0.0", - "@lerna/get-npm-exec-opts": "^3.0.0", - "fs-extra": "^6.0.1", - "npm-package-arg": "^6.0.0", - "npmlog": "^4.1.2", - "signal-exit": "^3.0.2", - "write-pkg": "^3.1.0" + "node_modules/@babel/helper-module-transforms": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "@lerna/npm-publish": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@lerna/npm-publish/-/npm-publish-3.2.0.tgz", - "integrity": "sha512-x13EGrjZk9w8gCQAE44aKbeO1xhLizLJ4tKjzZmQqKEaUCugF4UU8ZRGshPMRFBdsHTEWh05dkKx2oPMoaf0dw==", - "requires": { - "@lerna/child-process": "^3.0.0", - "@lerna/get-npm-exec-opts": "^3.0.0", - "@lerna/has-npm-version": "^3.0.4", - "@lerna/log-packed": "^3.0.4", - "fs-extra": "^6.0.1", - "npmlog": "^4.1.2", - "p-map": "^1.2.0" + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", + "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz", + "integrity": "sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.25.0.tgz", + "integrity": "sha512-NhavI2eWEIz/H9dbrG0TuOicDhNexze43i5z7lEqwYm0WEZVTwnPpA0EafUTP7+6/W79HWIP2cTe3Z5NiSTVpw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-wrap-function": "^7.25.0", + "@babel/traverse": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "@lerna/npm-run-script": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/npm-run-script/-/npm-run-script-3.0.0.tgz", - "integrity": "sha512-Y1H4Myer1S7an33FDK0eqyR+95PujUePC/xJZKq/H50SaQNwBw7KMlxXxy6kXVEcQhmvQsER4Bw3msgqwwGYIw==", - "requires": { - "@lerna/child-process": "^3.0.0", - "@lerna/get-npm-exec-opts": "^3.0.0", - "npmlog": "^4.1.2" + "node_modules/@babel/helper-replace-supers": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz", + "integrity": "sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-member-expression-to-functions": "^7.24.8", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/traverse": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "@lerna/output": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/output/-/output-3.0.0.tgz", - "integrity": "sha512-EFxnSbO0zDEVKkTKpoCUAFcZjc3gn3DwPlyTDxbeqPU7neCfxP4rA4+0a6pcOfTlRS5kLBRMx79F2TRCaMM3DA==", - "requires": { - "npmlog": "^4.1.2" + "node_modules/@babel/helper-simple-access": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" } }, - "@lerna/package": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/package/-/package-3.0.0.tgz", - "integrity": "sha512-djzEJxzn212wS8d9znBnlXkeRlPL7GqeAYBykAmsuq51YGvaQK67Umh5ejdO0uxexF/4r7yRwgrlRHpQs8Rfqg==", - "requires": { - "npm-package-arg": "^6.0.0", - "write-pkg": "^3.1.0" + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", + "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.25.0.tgz", + "integrity": "sha512-s6Q1ebqutSiZnEjaofc/UKDyC4SbzV5n5SrA2Gq8UawLycr3i04f1dX4OzoQVnexm6aOCh37SQNYlJ/8Ku+PMQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.0", + "@babel/types": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" } }, - "@lerna/package-graph": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@lerna/package-graph/-/package-graph-3.1.2.tgz", - "integrity": "sha512-9wIWb49I1IJmyjPdEVZQ13IAi9biGfH/OZHOC04U2zXGA0GLiY+B3CAx6FQvqkZ8xEGfqzmXnv3LvZ0bQfc1aQ==", - "requires": { - "@lerna/validation-error": "^3.0.0", - "npm-package-arg": "^6.0.0", - "semver": "^5.5.0" + "node_modules/@babel/helpers": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.0.tgz", + "integrity": "sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw==", + "dev": true, + "dependencies": { + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" } }, - "@lerna/project": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/project/-/project-3.0.0.tgz", - "integrity": "sha512-XhDFVfqj79jG2Speggd15RpYaE8uiR25UKcQBDmumbmqvTS7xf2cvl2pq2UTvDafaJ0YwFF3xkxQZeZnFMwdkw==", - "requires": { - "@lerna/package": "^3.0.0", - "@lerna/validation-error": "^3.0.0", - "cosmiconfig": "^5.0.2", - "dedent": "^0.7.0", - "dot-prop": "^4.2.0", - "glob-parent": "^3.1.0", - "globby": "^8.0.1", - "load-json-file": "^4.0.0", - "npmlog": "^4.1.2", - "p-map": "^1.2.0", - "resolve-from": "^4.0.0", - "write-json-file": "^2.3.0" - } - }, - "@lerna/prompt": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/prompt/-/prompt-3.0.0.tgz", - "integrity": "sha512-EzvNexDTh//GlpOz68zRo16NdOIqWqiiXMs9tIxpELQubH+kUGKvBSiBrZ2Zyrfd8pQhIf+8qARtkCG+G7wzQQ==", - "requires": { - "inquirer": "^5.1.0", - "npmlog": "^4.1.2" + "node_modules/@babel/highlight": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.24.7", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0", + "picocolors": "^1.0.0" + }, + "engines": { + "node": ">=6.9.0" } }, - "@lerna/publish": { + "node_modules/@babel/highlight/node_modules/ansi-styles": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@lerna/publish/-/publish-3.2.1.tgz", - "integrity": "sha512-SnSBstK/G9qLt5rS56pihNacgsu3UgxXiCexWb57GGEp2eDguQ7rFzxVs4JMQQWmVG97EMJQxfFV54tW2sqtIw==", - "requires": { - "@lerna/batch-packages": "^3.1.2", - "@lerna/check-working-tree": "^3.1.0", - "@lerna/child-process": "^3.0.0", - "@lerna/collect-updates": "^3.1.0", - "@lerna/command": "^3.1.3", - "@lerna/describe-ref": "^3.1.0", - "@lerna/get-npm-exec-opts": "^3.0.0", - "@lerna/npm-dist-tag": "^3.0.0", - "@lerna/npm-publish": "^3.2.0", - "@lerna/output": "^3.0.0", - "@lerna/prompt": "^3.0.0", - "@lerna/run-lifecycle": "^3.2.0", - "@lerna/run-parallel-batches": "^3.0.0", - "@lerna/validation-error": "^3.0.0", - "@lerna/version": "^3.2.0", - "fs-extra": "^6.0.1", - "npm-package-arg": "^6.0.0", - "npmlog": "^4.1.2", - "p-finally": "^1.0.0", - "p-map": "^1.2.0", - "p-pipe": "^1.2.0", - "p-reduce": "^1.0.0", - "semver": "^5.5.0" - } - }, - "@lerna/resolve-symlink": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/resolve-symlink/-/resolve-symlink-3.0.0.tgz", - "integrity": "sha512-MqjW9e+QVXts5IK5dk1XnYx7JKb+g+tQkOnnpAxYWHjahf3rGJ7Ru8maWh8KoPE+nIHAekk4WcjpiA9nLKvkFQ==", - "requires": { - "fs-extra": "^6.0.1", - "npmlog": "^4.1.2", - "read-cmd-shim": "^1.0.1" + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" } }, - "@lerna/rimraf-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/rimraf-dir/-/rimraf-dir-3.0.0.tgz", - "integrity": "sha512-epvh/RGWSOYdrNgrizMcRq9VyCHkeY0LpIE4074r4ouKdYNhBT0LlpT0yMLvQgQKJkKRlqcfhJHvZeGHhXQyGg==", - "requires": { - "@lerna/child-process": "^3.0.0", - "npmlog": "^4.1.2", - "path-exists": "^3.0.0", - "rimraf": "^2.6.2" + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" } }, - "@lerna/run": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@lerna/run/-/run-3.1.3.tgz", - "integrity": "sha512-O26WdR+sQFSG2Fpc67nw+m8oVq3R+H6jsscKuB6VJafU+V4/hPURSbuFZIcmnD9MLmzAIhlQiCf0Fy6s/1MPPA==", - "requires": { - "@lerna/batch-packages": "^3.1.2", - "@lerna/command": "^3.1.3", - "@lerna/filter-options": "^3.1.2", - "@lerna/npm-run-script": "^3.0.0", - "@lerna/output": "^3.0.0", - "@lerna/run-parallel-batches": "^3.0.0", - "@lerna/validation-error": "^3.0.0", - "p-map": "^1.2.0" - } - }, - "@lerna/run-lifecycle": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@lerna/run-lifecycle/-/run-lifecycle-3.2.0.tgz", - "integrity": "sha512-kGGdHJRyeZF+VTtal1DBptg6qwIsOLg3pKtmRm1rCMNN7j4kgrA9L07ZoRar8LjQXvfuheB1LSKHd5d04pr4Tg==", - "requires": { - "@lerna/npm-conf": "^3.0.0", - "npm-lifecycle": "^2.0.0", - "npmlog": "^4.1.2" - } - }, - "@lerna/run-parallel-batches": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/run-parallel-batches/-/run-parallel-batches-3.0.0.tgz", - "integrity": "sha512-Mj1ravlXF7AkkewKd9YFq9BtVrsStNrvVLedD/b2wIVbNqcxp8lS68vehXVOzoL/VWNEDotvqCQtyDBilCodGw==", - "requires": { - "p-map": "^1.2.0", - "p-map-series": "^1.0.0" - } - }, - "@lerna/symlink-binary": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@lerna/symlink-binary/-/symlink-binary-3.1.4.tgz", - "integrity": "sha512-uQ8pYxzygahshXJAeC/vY4eSi+kFM0S2Pi15hJsJI3W7Ec6ysSYU1lXemb6kqoIqkTDJZWnfKXEq/3FLE+JYhg==", - "requires": { - "@lerna/create-symlink": "^3.0.0", - "@lerna/package": "^3.0.0", - "fs-extra": "^6.0.1", - "p-map": "^1.2.0", - "read-pkg": "^3.0.0" - } - }, - "@lerna/symlink-dependencies": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@lerna/symlink-dependencies/-/symlink-dependencies-3.1.4.tgz", - "integrity": "sha512-iModwb0Xh0N0t55C6S4K2mzLdu1zXVsBc0qubUY1x0RSul92z8NeAe1aM5JzwMzuSoMA/LRiD1lNMWMRBf4JVg==", - "requires": { - "@lerna/create-symlink": "^3.0.0", - "@lerna/resolve-symlink": "^3.0.0", - "@lerna/symlink-binary": "^3.1.4", - "fs-extra": "^6.0.1", - "p-finally": "^1.0.0", - "p-map": "^1.2.0", - "p-map-series": "^1.0.0" - } - }, - "@lerna/validation-error": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/validation-error/-/validation-error-3.0.0.tgz", - "integrity": "sha512-5wjkd2PszV0kWvH+EOKZJWlHEqCTTKrWsvfHnHhcUaKBe/NagPZFWs+0xlsDPZ3DJt5FNfbAPAnEBQ05zLirFA==", - "requires": { - "npmlog": "^4.1.2" - } - }, - "@lerna/version": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@lerna/version/-/version-3.2.0.tgz", - "integrity": "sha512-1AVDMpeecSMiG1cacduE+f2KO0mC7F/9MvWsHtp+rjkpficMcsVme7IMtycuvu/F07wY4Xr9ioFKYTwTcybbIA==", - "requires": { - "@lerna/batch-packages": "^3.1.2", - "@lerna/check-working-tree": "^3.1.0", - "@lerna/child-process": "^3.0.0", - "@lerna/collect-updates": "^3.1.0", - "@lerna/command": "^3.1.3", - "@lerna/conventional-commits": "^3.0.2", - "@lerna/output": "^3.0.0", - "@lerna/prompt": "^3.0.0", - "@lerna/run-lifecycle": "^3.2.0", - "@lerna/validation-error": "^3.0.0", - "chalk": "^2.3.1", - "dedent": "^0.7.0", - "minimatch": "^3.0.4", - "npmlog": "^4.1.2", - "p-map": "^1.2.0", - "p-pipe": "^1.2.0", - "p-reduce": "^1.0.0", - "p-waterfall": "^1.0.0", - "semver": "^5.5.0", - "slash": "^1.0.0", - "temp-write": "^3.4.0" + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" } }, - "@lerna/write-log-file": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@lerna/write-log-file/-/write-log-file-3.0.0.tgz", - "integrity": "sha512-SfbPp29lMeEVOb/M16lJwn4nnx5y+TwCdd7Uom9umd7KcZP0NOvpnX0PHehdonl7TyHZ1Xx2maklYuCLbQrd/A==", - "requires": { - "npmlog": "^4.1.2", - "write-file-atomic": "^2.3.0" + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" } }, - "@mrmlnc/readdir-enhanced": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", - "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", - "requires": { - "call-me-maybe": "^1.0.1", - "glob-to-regexp": "^0.3.0" + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" } }, - "@nodelib/fs.stat": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.1.tgz", - "integrity": "sha512-KU/VDjC5RwtDUZiz3d+DHXJF2lp5hB9dn552TXIyptj8SH1vXmR40mG0JgGq03IlYsOgGfcv8xrLpSQ0YUMQdA==" + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } }, - "JSONStream": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.4.tgz", - "integrity": "sha512-Y7vfi3I5oMOYIr+WxV8NZxDSwcbNgzdKYsTNInmycOq9bUYwGg9ryu57Wg5NLmCjqdFPNUmpMBo3kSJN9tCbXg==", - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" + "node_modules/@babel/parser": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", + "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.27.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" } }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.25.3.tgz", + "integrity": "sha512-wUrcsxZg6rqBXG05HG1FPYgsP6EvwF4WpBbxIpWIIYnH8wG0gzx3yZY3dtEHas4sTAOGkbTsc9EGPxwff8lRoA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } }, - "agent-base": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz", - "integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==", - "requires": { - "es6-promisify": "^5.0.0" + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.25.0.tgz", + "integrity": "sha512-Bm4bH2qsX880b/3ziJ8KD711LT7z4u8CFudmjqle65AZj/HNUFhEf90dqYv6O86buWvSBmeQDjv0Tn2aF/bIBA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "agentkeepalive": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.1.tgz", - "integrity": "sha512-Cte/sTY9/XcygXjJ0q58v//SnEQ7ViWExKyJpLJlLqomDbQyMLh6Is4KuWJ/wmxzhiwkGRple7Gqv1zf6Syz5w==", - "requires": { - "humanize-ms": "^1.2.1" + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.25.0.tgz", + "integrity": "sha512-lXwdNZtTmeVOOFtwM/WDe7yg1PL8sYhRk/XH0FzbR2HDQ0xC+EnQ/JHeoMYSavtU115tnUk0q9CDyq8si+LMAA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", + "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" } }, - "align-text": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", - "requires": { - "kind-of": "^3.0.2", - "longest": "^1.0.1", - "repeat-string": "^1.5.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.25.0.tgz", + "integrity": "sha512-tggFrk1AIShG/RUQbEwt2Tr/E+ObkfwrPjR6BjbRvsx24+PSjK8zrq0GWPNCjo8qpRx4DuJzlcvWJqlm+0h3kw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" } }, - "amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" + "node_modules/@babel/plugin-proposal-class-properties": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", + "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-class-properties instead.", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "ansi-escapes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==" + "node_modules/@babel/plugin-proposal-export-default-from": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.24.7.tgz", + "integrity": "sha512-CcmFwUJ3tKhLjPdt4NP+SHMshebytF8ZTYOv5ZDpkzq2sin80Wb5vJrGt8fhPrORQCfoSa0LAxC/DW+GAC5+Hw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-export-default-from": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", + "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-nullish-coalescing-operator instead.", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" + "node_modules/@babel/plugin-proposal-optional-chaining": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.21.0.tgz", + "integrity": "sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==", + "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-optional-chaining instead.", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + "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, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" + "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==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "requires": { - "sprintf-js": "~1.0.2" + "node_modules/@babel/plugin-syntax-class-static-block": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", + "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + "node_modules/@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==" + "node_modules/@babel/plugin-syntax-export-default-from": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.24.7.tgz", + "integrity": "sha512-bTPz4/635WQ9WhwsyPdxUJDVpsi/X9BMmy/8Rf/UAlOO4jSql4CxUCjWI5PiM+jG+c4LVPTScoTw80geFj9+Bw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" + "node_modules/@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.3" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=" + "node_modules/@babel/plugin-syntax-flow": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.24.7.tgz", + "integrity": "sha512-9G8GYT/dxn/D1IIKOUBmGX0mnmj46mGH9NnZyJLwtCpgh5f7D2VbuKodb+2s9m1Yavh1s7ASQN8lf0eqrb1LTw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "array-find-index": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", + "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "array-ify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", - "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=" + "node_modules/@babel/plugin-syntax-import-attributes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "requires": { - "array-uniq": "^1.0.1" + "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==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" + "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==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + "node_modules/@babel/plugin-syntax-jsx": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.24.7.tgz", + "integrity": "sha512-6ddciUPe/mpMnOKv/U+RSd2vvVy+Yw/JfBB0ZHYjEZt9NLHmCUylNYlsbqCCS1Bffjlb0fCwC9Vqz+sBz6PsiQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + "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==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + "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==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "requires": { - "safer-buffer": "~2.1.0" + "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==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.10.4" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" } }, - "assert-plus": { + "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==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.8.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", + "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz", + "integrity": "sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-arrow-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", + "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.25.0.tgz", + "integrity": "sha512-uaIi2FdqzjpAMvVqvB51S42oC2JEVgh0LDsGfZVDysWE8LrJtQC2jvKmOqEYThKyB7bDEb7BP1GYWDm7tABA0Q==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-remap-async-to-generator": "^7.25.0", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/traverse": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", + "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", + "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.25.0.tgz", + "integrity": "sha512-yBQjYoOjXlFv9nlXb3f1casSHOZkWr29NX+zChVanLg5Nc157CrbEX9D7hxxtTpuFy7Q0YzmmWfJxzvps4kXrQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz", + "integrity": "sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", + "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-class-static-block": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, + "node_modules/@babel/plugin-transform-classes": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.25.0.tgz", + "integrity": "sha512-xyi6qjr/fYU304fiRwFbekzkqVJZ6A7hOjWZd+89FVcBqPV3S9Wuozz82xdpLspckeaafntbzglaW4pqpzvtSw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-replace-supers": "^7.25.0", + "@babel/traverse": "^7.25.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-classes/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, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", + "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/template": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz", + "integrity": "sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", + "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", + "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.25.0.tgz", + "integrity": "sha512-YLpb4LlYSc3sCUa35un84poXoraOiQucUTTu8X1j18JV+gNa8E0nyUf/CjZ171IRGr4jEguF+vzJU66QZhn29g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", + "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", + "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", + "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-flow-strip-types": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.25.2.tgz", + "integrity": "sha512-InBZ0O8tew5V0K6cHcQ+wgxlrjOw1W4wDXLkOTjLRD8GYhTSkxTVBtdy3MMtvYBrbAWa1Qm3hNoTc1620Yj+Mg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/plugin-syntax-flow": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", + "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.25.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.25.1.tgz", + "integrity": "sha512-TVVJVdW9RKMNgJJlLtHsKDTydjZAbwIsn6ySBPQaEAUU5+gVvlJt/9nRmqVbsV/IBanRjzWoaAQKLoamWVOUuA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/traverse": "^7.25.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", + "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-json-strings": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.25.2.tgz", + "integrity": "sha512-HQI+HcTbm9ur3Z2DkO+jgESMAMcYLuN/A7NRw9juzxAezN9AvqvUTnpKP/9kkYANz6u7dFlAyOu44ejuGySlfw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", + "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", + "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", + "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-commonjs": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz", + "integrity": "sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.24.8", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-simple-access": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.25.0.tgz", + "integrity": "sha512-YPJfjQPDXxyQWg/0+jHKj1llnY5f/R6a0p/vP4lPymxLu7Lvl4k2WMitqi08yxwQcCVUUdG9LCUj4TNEgAp3Jw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", + "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", + "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", + "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", + "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", + "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", + "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", + "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", + "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz", + "integrity": "sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", + "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz", + "integrity": "sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", + "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", + "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-display-name": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.24.7.tgz", + "integrity": "sha512-H/Snz9PFxKsS1JLI4dJLtnJgCJRoo0AUm3chP6NYr+9En1JMKloheEiLIhlp5MDVznWo+H3AAC1Mc8lmUEpsgg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.25.2.tgz", + "integrity": "sha512-KQsqEAVBpU82NM/B/N9j9WOdphom1SZH3R+2V7INrQUH+V9EBFwZsEJl8eBIVeQE62FxJCc70jzEZwqU7RcVqA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/plugin-syntax-jsx": "^7.24.7", + "@babel/types": "^7.25.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.24.7.tgz", + "integrity": "sha512-fOPQYbGSgH0HUp4UJO4sMBFjY6DuWq+2i8rixyUMb3CdGixs/gccURvYOAhajBdKDoGajFr3mUq5rH3phtkGzw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.24.7.tgz", + "integrity": "sha512-J2z+MWzZHVOemyLweMqngXrgGC42jQ//R0KdxqkIz/OrbVIIlhFI3WigZ5fO+nwFvBlncr4MGapd8vTyc7RPNQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", + "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "regenerator-transform": "^0.15.2" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", + "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", + "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.1", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-runtime/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, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/plugin-transform-shorthand-properties": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", + "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", + "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", + "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-template-literals": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", + "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.24.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz", + "integrity": "sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.8" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.2.tgz", + "integrity": "sha512-lBwRvjSmqiMYe/pS0+1gggjJleUJi7NzjvQ1Fkqtt69hBa/0t1YuW/MLQMAPixfwaQOHUXsd6jeU3Z+vdGv3+A==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.25.0", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-syntax-typescript": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-escapes": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", + "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", + "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", + "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz", + "integrity": "sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.25.3.tgz", + "integrity": "sha512-QsYW7UeAaXvLPX9tdVliMJE7MD7M6MLYVTovRTIwhoYQVFHR1rM4wO8wqAezYi3/BpSD+NzVCZ69R6smWiIi8g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.25.2", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-plugin-utils": "^7.24.8", + "@babel/helper-validator-option": "^7.24.8", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.25.3", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.25.0", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.25.0", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.25.0", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.14.5", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-import-assertions": "^7.24.7", + "@babel/plugin-syntax-import-attributes": "^7.24.7", + "@babel/plugin-syntax-import-meta": "^7.10.4", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.5", + "@babel/plugin-syntax-top-level-await": "^7.14.5", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.25.0", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoped-functions": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.25.0", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-class-static-block": "^7.24.7", + "@babel/plugin-transform-classes": "^7.25.0", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.8", + "@babel/plugin-transform-dotall-regex": "^7.24.7", + "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.25.0", + "@babel/plugin-transform-dynamic-import": "^7.24.7", + "@babel/plugin-transform-exponentiation-operator": "^7.24.7", + "@babel/plugin-transform-export-namespace-from": "^7.24.7", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.25.1", + "@babel/plugin-transform-json-strings": "^7.24.7", + "@babel/plugin-transform-literals": "^7.25.2", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-member-expression-literals": "^7.24.7", + "@babel/plugin-transform-modules-amd": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.8", + "@babel/plugin-transform-modules-systemjs": "^7.25.0", + "@babel/plugin-transform-modules-umd": "^7.24.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-new-target": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-object-super": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.8", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-property-literals": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-reserved-words": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-template-literals": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.8", + "@babel/plugin-transform-unicode-escapes": "^7.24.7", + "@babel/plugin-transform-unicode-property-regex": "^7.24.7", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.10", + "babel-plugin-polyfill-corejs3": "^0.10.4", + "babel-plugin-polyfill-regenerator": "^0.6.1", + "core-js-compat": "^3.37.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/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, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/preset-flow": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.24.7.tgz", + "integrity": "sha512-NL3Lo0NorCU607zU3NwRyJbpaB6E3t0xtd3LfAQKDfkeX4/ggcDXvkmkW42QWT5owUeW/jAe4hn+2qvkV1IbfQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "@babel/plugin-transform-flow-strip-types": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.24.7.tgz", + "integrity": "sha512-SyXRe3OdWwIwalxDg5UtJnJQO+YPcTfwiIY2B0Xlddh9o7jpWLvv8X1RthIeDOxQ+O1ML5BLPCONToObyVQVuQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "@babel/plugin-syntax-jsx": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.7", + "@babel/plugin-transform-typescript": "^7.24.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/register": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.24.6.tgz", + "integrity": "sha512-WSuFCc2wCqMeXkz/i3yfAAsxwWflEgbVkZzivgAmXl/MxrXeoYFZOOPllbC8R8WTF7u61wSRQtDVZ1879cdu6w==", + "dev": true, + "peer": true, + "dependencies": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.6", + "source-map-support": "^0.5.16" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/register/node_modules/find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "peer": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "peer": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "peer": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "peer": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/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, + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/register/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "peer": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/register/node_modules/pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "peer": true, + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "peer": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/@babel/register/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "peer": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/@babel/regjsgen": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", + "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==", + "dev": true, + "peer": true + }, + "node_modules/@babel/runtime": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.0.tgz", + "integrity": "sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw==", + "dev": true, + "peer": true, + "dependencies": { + "regenerator-runtime": "^0.14.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime/node_modules/regenerator-runtime": { + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "dev": true, + "peer": true + }, + "node_modules/@babel/template": { + "version": "7.25.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.25.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.3.tgz", + "integrity": "sha512-HefgyP1x754oGCsKmV5reSmtV7IXj/kpaE1XYY+D9G5PvKKoFfSbiS4M77MdjuwlZKDIKFCffq9rPU+H/s3ZdQ==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.0", + "@babel/parser": "^7.25.3", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.2", + "debug": "^4.3.1", + "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/types": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", + "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "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/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", + "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "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.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@eslint/js": { + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz", + "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@hapi/hoek": { + "version": "9.3.0", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", + "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", + "dev": true, + "peer": true + }, + "node_modules/@hapi/topo": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", + "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", + "dev": true, + "peer": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "deprecated": "Use @eslint/config-array instead", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "deprecated": "Use @eslint/object-schema instead", + "dev": true + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui/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/@isaacs/cliui/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": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true + }, + "node_modules/@isaacs/cliui/node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@isaacs/cliui/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-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/cliui/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": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/@isaacs/ttlcache": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz", + "integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12" + } + }, + "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==", + "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/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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==", + "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/create-cache-key-function": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz", + "integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/environment": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/fake-timers": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@sinonjs/fake-timers": "^10.0.2", + "@types/node": "*", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "peer": true, + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.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.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.5.tgz", + "integrity": "sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-2.1.2.tgz", + "integrity": "sha512-dvlNq4AlGWC+ehtH12p65+17V0Dx7IecOWl6WanF2ja38O1Dcjjvn7jVzkUHJ5oWkQBlyASurTPlTHgKXyYiow==", + "dev": true, + "license": "MIT", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.65 <1.0" + } + }, + "node_modules/@react-native-community/cli": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-14.0.0.tgz", + "integrity": "sha512-KwMKJB5jsDxqOhT8CGJ55BADDAYxlYDHv5R/ASQlEcdBEZxT0zZmnL0iiq2VqzETUy+Y/Nop+XDFgqyoQm0C2w==", + "dev": true, + "peer": true, + "dependencies": { + "@react-native-community/cli-clean": "14.0.0", + "@react-native-community/cli-config": "14.0.0", + "@react-native-community/cli-debugger-ui": "14.0.0", + "@react-native-community/cli-doctor": "14.0.0", + "@react-native-community/cli-server-api": "14.0.0", + "@react-native-community/cli-tools": "14.0.0", + "@react-native-community/cli-types": "14.0.0", + "chalk": "^4.1.2", + "commander": "^9.4.1", + "deepmerge": "^4.3.0", + "execa": "^5.0.0", + "find-up": "^5.0.0", + "fs-extra": "^8.1.0", + "graceful-fs": "^4.1.3", + "prompts": "^2.4.2", + "semver": "^7.5.2" + }, + "bin": { + "rnc-cli": "build/bin.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native-community/cli-clean": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-14.0.0.tgz", + "integrity": "sha512-kvHthZTNur/wLLx8WL5Oh+r04zzzFAX16r8xuaLhu9qGTE6Th1JevbsIuiQb5IJqD8G/uZDKgIZ2a0/lONcbJg==", + "dev": true, + "peer": true, + "dependencies": { + "@react-native-community/cli-tools": "14.0.0", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-glob": "^3.3.2" + } + }, + "node_modules/@react-native-community/cli-config": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-14.0.0.tgz", + "integrity": "sha512-2Nr8KR+dgn1z+HLxT8piguQ1SoEzgKJnOPQKE1uakxWaRFcQ4LOXgzpIAscYwDW6jmQxdNqqbg2cRUoOS7IMtQ==", + "dev": true, + "peer": true, + "dependencies": { + "@react-native-community/cli-tools": "14.0.0", + "chalk": "^4.1.2", + "cosmiconfig": "^9.0.0", + "deepmerge": "^4.3.0", + "fast-glob": "^3.3.2", + "joi": "^17.2.1" + } + }, + "node_modules/@react-native-community/cli-debugger-ui": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-14.0.0.tgz", + "integrity": "sha512-JpfzILfU7eKE9+7AMCAwNJv70H4tJGVv3ZGFqSVoK1YHg5QkVEGsHtoNW8AsqZRS6Fj4os+Fmh+r+z1L36sPmg==", + "dev": true, + "peer": true, + "dependencies": { + "serve-static": "^1.13.1" + } + }, + "node_modules/@react-native-community/cli-doctor": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-14.0.0.tgz", + "integrity": "sha512-in6jylHjaPUaDzV+JtUblh8m9JYIHGjHOf6Xn57hrmE5Zwzwuueoe9rSMHF1P0mtDgRKrWPzAJVejElddfptWA==", + "dev": true, + "peer": true, + "dependencies": { + "@react-native-community/cli-config": "14.0.0", + "@react-native-community/cli-platform-android": "14.0.0", + "@react-native-community/cli-platform-apple": "14.0.0", + "@react-native-community/cli-platform-ios": "14.0.0", + "@react-native-community/cli-tools": "14.0.0", + "chalk": "^4.1.2", + "command-exists": "^1.2.8", + "deepmerge": "^4.3.0", + "envinfo": "^7.13.0", + "execa": "^5.0.0", + "node-stream-zip": "^1.9.1", + "ora": "^5.4.1", + "semver": "^7.5.2", + "strip-ansi": "^5.2.0", + "wcwidth": "^1.0.1", + "yaml": "^2.2.1" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-doctor/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@react-native-community/cli-platform-android": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-14.0.0.tgz", + "integrity": "sha512-nt7yVz3pGKQXnVa5MAk7zR+1n41kNKD3Hi2OgybH5tVShMBo7JQoL2ZVVH6/y/9wAwI/s7hXJgzf1OIP3sMq+Q==", + "dev": true, + "peer": true, + "dependencies": { + "@react-native-community/cli-tools": "14.0.0", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-glob": "^3.3.2", + "fast-xml-parser": "^4.2.4", + "logkitty": "^0.7.1" + } + }, + "node_modules/@react-native-community/cli-platform-apple": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-apple/-/cli-platform-apple-14.0.0.tgz", + "integrity": "sha512-WniJL8vR4MeIsjqio2hiWWuUYUJEL3/9TDL5aXNwG68hH3tYgK3742+X9C+vRzdjTmf5IKc/a6PwLsdplFeiwQ==", + "dev": true, + "peer": true, + "dependencies": { + "@react-native-community/cli-tools": "14.0.0", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "fast-glob": "^3.3.2", + "fast-xml-parser": "^4.2.4", + "ora": "^5.4.1" + } + }, + "node_modules/@react-native-community/cli-platform-ios": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-14.0.0.tgz", + "integrity": "sha512-8kxGv7mZ5nGMtueQDq+ndu08f0ikf3Zsqm3Ix8FY5KCXpSgP14uZloO2GlOImq/zFESij+oMhCkZJGggpWpfAw==", + "dev": true, + "peer": true, + "dependencies": { + "@react-native-community/cli-platform-apple": "14.0.0" + } + }, + "node_modules/@react-native-community/cli-server-api": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-14.0.0.tgz", + "integrity": "sha512-A0FIsj0QCcDl1rswaVlChICoNbfN+mkrKB5e1ab5tOYeZMMyCHqvU+eFvAvXjHUlIvVI+LbqCkf4IEdQ6H/2AQ==", + "dev": true, + "peer": true, + "dependencies": { + "@react-native-community/cli-debugger-ui": "14.0.0", + "@react-native-community/cli-tools": "14.0.0", + "compression": "^1.7.1", + "connect": "^3.6.5", + "errorhandler": "^1.5.1", + "nocache": "^3.0.1", + "pretty-format": "^26.6.2", + "serve-static": "^1.13.1", + "ws": "^6.2.3" + } + }, + "node_modules/@react-native-community/cli-server-api/node_modules/@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dev": true, + "peer": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@react-native-community/cli-server-api/node_modules/@types/yargs": { + "version": "15.0.19", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", + "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", + "dev": true, + "peer": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@react-native-community/cli-server-api/node_modules/pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@react-native-community/cli-server-api/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "peer": true + }, + "node_modules/@react-native-community/cli-server-api/node_modules/ws": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "dev": true, + "peer": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/@react-native-community/cli-tools": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-14.0.0.tgz", + "integrity": "sha512-L7GX5hyYYv0ZWbAyIQKzhHuShnwDqlKYB0tqn57wa5riGCaxYuRPTK+u4qy+WRCye7+i8M4Xj6oQtSd4z0T9cA==", + "dev": true, + "peer": true, + "dependencies": { + "appdirsjs": "^1.2.4", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "find-up": "^5.0.0", + "mime": "^2.4.1", + "open": "^6.2.0", + "ora": "^5.4.1", + "semver": "^7.5.2", + "shell-quote": "^1.7.3", + "sudo-prompt": "^9.0.0" + } + }, + "node_modules/@react-native-community/cli-types": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-14.0.0.tgz", + "integrity": "sha512-CMUevd1pOWqvmvutkUiyQT2lNmMHUzSW7NKc1xvHgg39NjbS58Eh2pMzIUP85IwbYNeocfYc3PH19vA/8LnQtg==", + "dev": true, + "peer": true, + "dependencies": { + "joi": "^17.2.1" + } + }, + "node_modules/@react-native-community/netinfo": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-11.3.2.tgz", + "integrity": "sha512-YsaS3Dutnzqd1BEoeC+DEcuNJedYRkN6Ef3kftT5Sm8ExnCF94C/nl4laNxuvFli3+Jz8Df3jO25Jn8A9S0h4w==", + "dev": true, + "peerDependencies": { + "react-native": ">=0.59" + } + }, + "node_modules/@react-native/assets-registry": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.75.2.tgz", + "integrity": "sha512-P1dLHjpUeC0AIkDHRYcx0qLMr+p92IPWL3pmczzo6T76Qa9XzruQOYy0jittxyBK91Csn6HHQ/eit8TeXW8MVw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/babel-plugin-codegen": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.75.2.tgz", + "integrity": "sha512-BIKVh2ZJPkzluUGgCNgpoh6NTHgX8j04FCS0Z/rTmRJ66hir/EUBl8frMFKrOy/6i4VvZEltOWB5eWfHe1AYgw==", + "dev": true, + "peer": true, + "dependencies": { + "@react-native/codegen": "0.75.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/babel-preset": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.75.2.tgz", + "integrity": "sha512-mprpsas+WdCEMjQZnbDiAC4KKRmmLbMB+o/v4mDqKlH4Mcm7RdtP5t80MZGOVCHlceNp1uEIpXywx69DNwgbgg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.20.0", + "@babel/plugin-proposal-export-default-from": "^7.0.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.0", + "@babel/plugin-syntax-export-default-from": "^7.0.0", + "@babel/plugin-syntax-flow": "^7.18.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", + "@babel/plugin-syntax-optional-chaining": "^7.0.0", + "@babel/plugin-transform-arrow-functions": "^7.0.0", + "@babel/plugin-transform-async-generator-functions": "^7.24.3", + "@babel/plugin-transform-async-to-generator": "^7.20.0", + "@babel/plugin-transform-block-scoping": "^7.0.0", + "@babel/plugin-transform-class-properties": "^7.24.1", + "@babel/plugin-transform-classes": "^7.0.0", + "@babel/plugin-transform-computed-properties": "^7.0.0", + "@babel/plugin-transform-destructuring": "^7.20.0", + "@babel/plugin-transform-flow-strip-types": "^7.20.0", + "@babel/plugin-transform-for-of": "^7.0.0", + "@babel/plugin-transform-function-name": "^7.0.0", + "@babel/plugin-transform-literals": "^7.0.0", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.1", + "@babel/plugin-transform-modules-commonjs": "^7.0.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1", + "@babel/plugin-transform-numeric-separator": "^7.24.1", + "@babel/plugin-transform-object-rest-spread": "^7.24.5", + "@babel/plugin-transform-optional-catch-binding": "^7.24.1", + "@babel/plugin-transform-optional-chaining": "^7.24.5", + "@babel/plugin-transform-parameters": "^7.0.0", + "@babel/plugin-transform-private-methods": "^7.22.5", + "@babel/plugin-transform-private-property-in-object": "^7.22.11", + "@babel/plugin-transform-react-display-name": "^7.0.0", + "@babel/plugin-transform-react-jsx": "^7.0.0", + "@babel/plugin-transform-react-jsx-self": "^7.0.0", + "@babel/plugin-transform-react-jsx-source": "^7.0.0", + "@babel/plugin-transform-regenerator": "^7.20.0", + "@babel/plugin-transform-runtime": "^7.0.0", + "@babel/plugin-transform-shorthand-properties": "^7.0.0", + "@babel/plugin-transform-spread": "^7.0.0", + "@babel/plugin-transform-sticky-regex": "^7.0.0", + "@babel/plugin-transform-typescript": "^7.5.0", + "@babel/plugin-transform-unicode-regex": "^7.0.0", + "@babel/template": "^7.0.0", + "@react-native/babel-plugin-codegen": "0.75.2", + "babel-plugin-transform-flow-enums": "^0.0.2", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/codegen": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.75.2.tgz", + "integrity": "sha512-OkWdbtO2jTkfOXfj3ibIL27rM6LoaEuApOByU2G8X+HS6v9U87uJVJlMIRWBDmnxODzazuHwNVA2/wAmSbucaw==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/parser": "^7.20.0", + "glob": "^7.1.1", + "hermes-parser": "0.22.0", + "invariant": "^2.2.4", + "jscodeshift": "^0.14.0", + "mkdirp": "^0.5.1", + "nullthrows": "^1.1.1", + "yargs": "^17.6.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" + } + }, + "node_modules/@react-native/community-cli-plugin": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.75.2.tgz", + "integrity": "sha512-/tz0bzVja4FU0aAimzzQ7iYR43peaD6pzksArdrrGhlm8OvFYAQPOYSNeIQVMSarwnkNeg1naFKaeYf1o3++yA==", + "dev": true, + "peer": true, + "dependencies": { + "@react-native-community/cli-server-api": "14.0.0-alpha.11", + "@react-native-community/cli-tools": "14.0.0-alpha.11", + "@react-native/dev-middleware": "0.75.2", + "@react-native/metro-babel-transformer": "0.75.2", + "chalk": "^4.0.0", + "execa": "^5.1.1", + "metro": "^0.80.3", + "metro-config": "^0.80.3", + "metro-core": "^0.80.3", + "node-fetch": "^2.2.0", + "querystring": "^0.2.1", + "readline": "^1.3.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dev": true, + "peer": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/@react-native-community/cli-debugger-ui": { + "version": "14.0.0-alpha.11", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-14.0.0-alpha.11.tgz", + "integrity": "sha512-0wCNQxhCniyjyMXgR1qXliY180y/2QbvoiYpp2MleGQADr5M1b8lgI4GoyADh5kE+kX3VL0ssjgyxpmbpCD86A==", + "dev": true, + "peer": true, + "dependencies": { + "serve-static": "^1.13.1" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/@react-native-community/cli-server-api": { + "version": "14.0.0-alpha.11", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-14.0.0-alpha.11.tgz", + "integrity": "sha512-I7YeYI7S5wSxnQAqeG8LNqhT99FojiGIk87DU0vTp6U8hIMLcA90fUuBAyJY38AuQZ12ZJpGa8ObkhIhWzGkvg==", + "dev": true, + "peer": true, + "dependencies": { + "@react-native-community/cli-debugger-ui": "14.0.0-alpha.11", + "@react-native-community/cli-tools": "14.0.0-alpha.11", + "compression": "^1.7.1", + "connect": "^3.6.5", + "errorhandler": "^1.5.1", + "nocache": "^3.0.1", + "pretty-format": "^26.6.2", + "serve-static": "^1.13.1", + "ws": "^6.2.3" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/@react-native-community/cli-tools": { + "version": "14.0.0-alpha.11", + "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-14.0.0-alpha.11.tgz", + "integrity": "sha512-HQCfVnX9aqRdKdLxmQy4fUAUo+YhNGlBV7ZjOayPbuEGWJ4RN+vSy0Cawk7epo7hXd6vKzc7P7y3HlU6Kxs7+w==", + "dev": true, + "peer": true, + "dependencies": { + "appdirsjs": "^1.2.4", + "chalk": "^4.1.2", + "execa": "^5.0.0", + "find-up": "^5.0.0", + "mime": "^2.4.1", + "open": "^6.2.0", + "ora": "^5.4.1", + "semver": "^7.5.2", + "shell-quote": "^1.7.3", + "sudo-prompt": "^9.0.0" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/@types/yargs": { + "version": "15.0.19", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", + "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", + "dev": true, + "peer": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@react-native/community-cli-plugin/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "peer": true + }, + "node_modules/@react-native/community-cli-plugin/node_modules/ws": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "dev": true, + "peer": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/@react-native/debugger-frontend": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.75.2.tgz", + "integrity": "sha512-qIC6mrlG8RQOPaYLZQiJwqnPchAVGnHWcVDeQxPMPLkM/D5+PC8tuKWYOwgLcEau3RZlgz7QQNk31Qj2/OJG6Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/dev-middleware": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.75.2.tgz", + "integrity": "sha512-fTC5m2uVjYp1XPaIJBFgscnQjPdGVsl96z/RfLgXDq0HBffyqbg29ttx6yTCx7lIa9Gdvf6nKQom+e+Oa4izSw==", + "dev": true, + "peer": true, + "dependencies": { + "@isaacs/ttlcache": "^1.4.1", + "@react-native/debugger-frontend": "0.75.2", + "chrome-launcher": "^0.15.2", + "chromium-edge-launcher": "^0.2.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "node-fetch": "^2.2.0", + "nullthrows": "^1.1.1", + "open": "^7.0.3", + "selfsigned": "^2.4.1", + "serve-static": "^1.13.1", + "ws": "^6.2.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/dev-middleware/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, + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "peer": true + }, + "node_modules/@react-native/dev-middleware/node_modules/open": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz", + "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==", + "dev": true, + "peer": true, + "dependencies": { + "is-docker": "^2.0.0", + "is-wsl": "^2.1.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@react-native/dev-middleware/node_modules/ws": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "dev": true, + "peer": true, + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/@react-native/gradle-plugin": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.75.2.tgz", + "integrity": "sha512-AELeAOCZi3B2vE6SeN+mjpZjjqzqa76yfFBB3L3f3NWiu4dm/YClTGOj+5IVRRgbt8LDuRImhDoaj7ukheXr4Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/js-polyfills": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.75.2.tgz", + "integrity": "sha512-AtLd3mbiE+FXK2Ru3l2NFOXDhUvzdUsCP4qspUw0haVaO/9xzV97RVD2zz0lur2f/LmZqQ2+KXyYzr7048b5iw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@react-native/metro-babel-transformer": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/metro-babel-transformer/-/metro-babel-transformer-0.75.2.tgz", + "integrity": "sha512-EygglCCuOub2sZ00CSIiEekCXoGL2XbOC6ssOB47M55QKvhdPG/0WBQXvmOmiN42uZgJK99Lj749v4rB0PlPIQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.20.0", + "@react-native/babel-preset": "0.75.2", + "hermes-parser": "0.22.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@babel/core": "*" + } + }, + "node_modules/@react-native/normalize-colors": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.75.2.tgz", + "integrity": "sha512-nPwWJFtsqNFS/qSG9yDOiSJ64mjG7RCP4X/HXFfyWzCM1jq49h/DYBdr+c3e7AvTKGIdy0gGT3vgaRUHZFVdUQ==", + "dev": true, + "peer": true + }, + "node_modules/@react-native/virtualized-lists": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.75.2.tgz", + "integrity": "sha512-pD5SVCjxc8k+JdoyQ+IlulBTEqJc3S4KUKsmv5zqbNCyETB0ZUvd4Su7bp+lLF6ALxx6KKmbGk8E3LaWEjUFFQ==", + "dev": true, + "peer": true, + "dependencies": { + "invariant": "^2.2.4", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/react": "^18.2.6", + "react": "*", + "react-native": "*" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-commonjs": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.1.0.tgz", + "integrity": "sha512-Ycr12N3ZPN96Fw2STurD21jMqzKwL9QuFhms3SD7KKRK7oaXUsBU9Zt0jL/rOPHiPYisI21/rXGO3jr9BnLHUA==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.0.8", + "commondir": "^1.0.1", + "estree-walker": "^1.0.1", + "glob": "^7.1.2", + "is-reference": "^1.1.2", + "magic-string": "^0.25.2", + "resolve": "^1.11.0" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz", + "integrity": "sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.0.8", + "@types/resolve": "0.0.8", + "builtin-modules": "^3.1.0", + "is-module": "^1.0.0", + "resolve": "^1.14.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "dependencies": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.40.0.tgz", + "integrity": "sha512-+Fbls/diZ0RDerhE8kyC6hjADCXA1K4yVNlH0EYfd2XjyH0UGgzaQ8MlT0pCXAThfxv3QUAczHaL+qSv1E4/Cg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.40.0.tgz", + "integrity": "sha512-PPA6aEEsTPRz+/4xxAmaoWDqh67N7wFbgFUJGMnanCFs0TV99M0M8QhhaSCks+n6EbQoFvLQgYOGXxlMGQe/6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.40.0.tgz", + "integrity": "sha512-GwYOcOakYHdfnjjKwqpTGgn5a6cUX7+Ra2HeNj/GdXvO2VJOOXCiYYlRFU4CubFM67EhbmzLOmACKEfvp3J1kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.40.0.tgz", + "integrity": "sha512-CoLEGJ+2eheqD9KBSxmma6ld01czS52Iw0e2qMZNpPDlf7Z9mj8xmMemxEucinev4LgHalDPczMyxzbq+Q+EtA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.40.0.tgz", + "integrity": "sha512-r7yGiS4HN/kibvESzmrOB/PxKMhPTlz+FcGvoUIKYoTyGd5toHp48g1uZy1o1xQvybwwpqpe010JrcGG2s5nkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.40.0.tgz", + "integrity": "sha512-mVDxzlf0oLzV3oZOr0SMJ0lSDd3xC4CmnWJ8Val8isp9jRGl5Dq//LLDSPFrasS7pSm6m5xAcKaw3sHXhBjoRw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.40.0.tgz", + "integrity": "sha512-y/qUMOpJxBMy8xCXD++jeu8t7kzjlOCkoxxajL58G62PJGBZVl/Gwpm7JK9+YvlB701rcQTzjUZ1JgUoPTnoQA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.40.0.tgz", + "integrity": "sha512-GoCsPibtVdJFPv/BOIvBKO/XmwZLwaNWdyD8TKlXuqp0veo2sHE+A/vpMQ5iSArRUz/uaoj4h5S6Pn0+PdhRjg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.40.0.tgz", + "integrity": "sha512-L5ZLphTjjAD9leJzSLI7rr8fNqJMlGDKlazW2tX4IUF9P7R5TMQPElpH82Q7eNIDQnQlAyiNVfRPfP2vM5Avvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.40.0.tgz", + "integrity": "sha512-ATZvCRGCDtv1Y4gpDIXsS+wfFeFuLwVxyUBSLawjgXK2tRE6fnsQEkE4csQQYWlBlsFztRzCnBvWVfcae/1qxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loongarch64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.40.0.tgz", + "integrity": "sha512-wG9e2XtIhd++QugU5MD9i7OnpaVb08ji3P1y/hNbxrQ3sYEelKJOq1UJ5dXczeo6Hj2rfDEL5GdtkMSVLa/AOg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.40.0.tgz", + "integrity": "sha512-vgXfWmj0f3jAUvC7TZSU/m/cOE558ILWDzS7jBhiCAFpY2WEBn5jqgbqvmzlMjtp8KlLcBlXVD2mkTSEQE6Ixw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.40.0.tgz", + "integrity": "sha512-uJkYTugqtPZBS3Z136arevt/FsKTF/J9dEMTX/cwR7lsAW4bShzI2R0pJVw+hcBTWF4dxVckYh72Hk3/hWNKvA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.40.0.tgz", + "integrity": "sha512-rKmSj6EXQRnhSkE22+WvrqOqRtk733x3p5sWpZilhmjnkHkpeCgWsFFo0dGnUGeA+OZjRl3+VYq+HyCOEuwcxQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.40.0.tgz", + "integrity": "sha512-SpnYlAfKPOoVsQqmTFJ0usx0z84bzGOS9anAC0AZ3rdSo3snecihbhFTlJZ8XMwzqAcodjFU4+/SM311dqE5Sw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", + "integrity": "sha512-RcDGMtqF9EFN8i2RYN2W+64CdHruJ5rPqrlYw+cgM3uOVPSsnAQps7cpjXe9be/yDp8UC7VLoCoKC8J3Kn2FkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.40.0.tgz", + "integrity": "sha512-HZvjpiUmSNx5zFgwtQAV1GaGazT2RWvqeDi0hV+AtC8unqqDSsaFjPxfsO6qPtKRRg25SisACWnJ37Yio8ttaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.40.0.tgz", + "integrity": "sha512-UtZQQI5k/b8d7d3i9AZmA/t+Q4tk3hOC0tMOMSq2GlMYOfxbesxG4mJSeDp0EHs30N9bsfwUvs3zF4v/RzOeTQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.40.0.tgz", + "integrity": "sha512-+m03kvI2f5syIqHXCZLPVYplP8pQch9JHyXKZ3AGMKlg8dCyr2PKHjwRLiW53LTrN/Nc3EqHOKxUxzoSPdKddA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.40.0.tgz", + "integrity": "sha512-lpPE1cLfP5oPzVjKMx10pgBmKELQnFJXHgvtHCtuJWOv8MxqdEIMNtgHgBFf7Ea2/7EuVwa9fodWUfXAlXZLZQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sideway/address": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", + "integrity": "sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==", + "dev": true, + "peer": true, + "dependencies": { + "@hapi/hoek": "^9.0.0" + } + }, + "node_modules/@sideway/formula": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.1.tgz", + "integrity": "sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==", + "dev": true, + "peer": true + }, + "node_modules/@sideway/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", + "dev": true, + "peer": true + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.8", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", + "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "dev": true, + "peer": true + }, + "node_modules/@sinonjs/commons": { + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", + "dev": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "peer": true, + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@sinonjs/fake-timers/node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dev": true, + "peer": true, + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/formatio": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", + "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", + "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.3.0", + "array-from": "^2.1.1", + "lodash": "^4.17.15" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", + "dev": true + }, + "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/@types/chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-VOVRLM1mBxIRxydiViqPcKn6MIxZytrbMpd6RJLIWKxUNr3zux8no0Oc7kJx0WAPIitgZ0gkrDS+btlqQpubpw==", + "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/cors": { + "version": "2.8.14", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.14.tgz", + "integrity": "sha512-RXHUvNWYICtbP6s18PnOCaqToK8y14DnLd75c6HfyKf228dxy7pHNOQkxPtvXKp/hINFMDjbYzsj63nnpPMSRQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/eslint": { + "version": "8.44.2", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", + "integrity": "sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true + }, + "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", + "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", + "dev": true, + "peer": true + }, + "node_modules/@types/istanbul-lib-report": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", + "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/istanbul-lib-coverage": "*" + } + }, + "node_modules/@types/istanbul-reports": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", + "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", + "dev": true, + "peer": true, + "dependencies": { + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.13.tgz", + "integrity": "sha512-RbSSoHliUbnXj3ny0CNFOoxrIDV6SUGyStHsvDqosw6CkdPV8TtWGlfecuK4ToyMEAql6pzNxgCFKanovUzlgQ==", + "dev": true + }, + "node_modules/@types/mocha": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", + "dev": true + }, + "node_modules/@types/nise": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@types/nise/-/nise-1.4.1.tgz", + "integrity": "sha512-LWDwHYO1C3YPpIQWXHeXAVih2nLsgN1Q5RamkYZRIZYfsz8HGNRji8vNhHs54LjcSgVx6AJC/6n/Q3Tn+fUb3g==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.17.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.17.18.tgz", + "integrity": "sha512-/4QOuy3ZpV7Ya1GTRz5CYSz3DgkKpyUptXuQ5PPce7uuyJAOR7r9FhkmxJfvcNUXyklbC63a+YvB3jxy7s9ngw==", + "dev": true + }, + "node_modules/@types/node-forge": { + "version": "1.3.11", + "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.11.tgz", + "integrity": "sha512-FQx220y22OKNTqaByeBGqHWYz4cl94tpcxeFdvBo3wjG6XPBuZ0BNgNZRV5J5TFmmcsJ4IzsLkmGRiQbnYsBEQ==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/resolve": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", + "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/semver": { + "version": "7.5.2", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.2.tgz", + "integrity": "sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==", + "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, + "peer": true + }, + "node_modules/@types/ua-parser-js": { + "version": "0.7.37", + "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.37.tgz", + "integrity": "sha512-4sOxS3ZWXC0uHJLYcWAaLMxTvjRX3hT96eF4YWUh1ovTaenvibaZOE5uXtIp4mksKMLRwo7YDiCBCw6vBiUPVg==", + "dev": true + }, + "node_modules/@types/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==", + "dev": true + }, + "node_modules/@types/yargs": { + "version": "17.0.24", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.24.tgz", + "integrity": "sha512-6i0aC7jV6QzQB8ne1joVZ0eSFIstHsCrobmOtghM11yGlH0j43FKL2UhWdELkyps0zuf7qVTUVCCR+tgSlyLLw==", + "dev": true, + "peer": true, + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/@types/yargs-parser": { + "version": "21.0.0", + "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", + "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", + "dev": true, + "peer": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz", + "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.4.0", + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/type-utils": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "natural-compare-lite": "^1.4.0", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.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" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz", + "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "5.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz", + "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz", + "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "5.62.0", + "@typescript-eslint/utils": "5.62.0", + "debug": "^4.3.4", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz", + "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz", + "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/visitor-keys": "5.62.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.3.7", + "tsutils": "^3.21.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz", + "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==", + "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.62.0", + "@typescript-eslint/types": "5.62.0", + "@typescript-eslint/typescript-estree": "5.62.0", + "eslint-scope": "^5.1.1", + "semver": "^7.3.7" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "5.62.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz", + "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "5.62.0", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@vitest/coverage-istanbul": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/coverage-istanbul/-/coverage-istanbul-2.1.9.tgz", + "integrity": "sha512-vdYE4FkC/y2lxcN3Dcj54Bw+ericmDwiex0B8LV5F/YNYEYP1mgVwhPnHwWGAXu38qizkjOuyczKbFTALfzFKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@istanbuljs/schema": "^0.1.3", + "debug": "^4.3.7", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-instrument": "^6.0.3", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magicast": "^0.3.5", + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "vitest": "2.1.9" + } + }, + "node_modules/@vitest/coverage-istanbul/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@vitest/coverage-istanbul/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@vitest/coverage-istanbul/node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vitest/coverage-istanbul/node_modules/glob": { + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "dev": true, + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vitest/coverage-istanbul/node_modules/istanbul-lib-instrument": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.3.tgz", + "integrity": "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==", + "dev": true, + "dependencies": { + "@babel/core": "^7.23.9", + "@babel/parser": "^7.23.9", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-coverage": "^3.2.0", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vitest/coverage-istanbul/node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@vitest/coverage-istanbul/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vitest/coverage-istanbul/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/coverage-istanbul/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vitest/coverage-istanbul/node_modules/test-exclude": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.1.tgz", + "integrity": "sha512-pFYqmTw68LXVjeWJMST4+borgQP2AyMNbg1BpZh9LbyhUeNkeaPF9gzfPGUAnSMV3qPYdWUwDIjjCLiSDOl7vg==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^9.0.4" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/expect": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", + "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/expect/node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/@vitest/expect/node_modules/chai": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@vitest/expect/node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/@vitest/expect/node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@vitest/expect/node_modules/loupe": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/expect/node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/@vitest/mocker": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz", + "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "2.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.12" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/mocker/node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitest/mocker/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/@vitest/mocker/node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz", + "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz", + "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "2.1.9", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz", + "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot/node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0" + } + }, + "node_modules/@vitest/spy": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz", + "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz", + "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "2.1.9", + "loupe": "^3.1.2", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils/node_modules/loupe": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.6.tgz", + "integrity": "sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz", + "integrity": "sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz", + "integrity": "sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz", + "integrity": "sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-opt": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6", + "@webassemblyjs/wast-printer": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz", + "integrity": "sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz", + "integrity": "sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-buffer": "1.11.6", + "@webassemblyjs/wasm-gen": "1.11.6", + "@webassemblyjs/wasm-parser": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz", + "integrity": "sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz", + "integrity": "sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.6", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "dev": true, + "peer": true, + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "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.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", + "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "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", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/anser": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", + "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", + "dev": true, + "peer": true + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-fragments": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", + "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", + "dev": true, + "peer": true, + "dependencies": { + "colorette": "^1.0.7", + "slice-ansi": "^2.0.0", + "strip-ansi": "^5.0.0" + } + }, + "node_modules/ansi-fragments/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-fragments/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/appdirsjs": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", + "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", + "dev": true, + "peer": true + }, + "node_modules/append-transform": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", + "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", + "dev": true, + "dependencies": { + "default-require-extensions": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true + }, + "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": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha512-GQTc6Uupx1FCavi5mPzBvVT7nEOeWMmUA9P95wpfpW1XwMSKs+KaymD5C2Up7KAUKg/mYwbsUYzdZWcoajlNZg==", + "dev": true + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "peer": true + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/ast-types": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.15.2.tgz", + "integrity": "sha512-c27loCv9QkZinsa5ProX751khO9DJl/AcB5c2KNtA6NRvHKS0PgLfcftz72KVq504vB0Gku5s2kUZzDBvQWvHg==", + "dev": true, + "peer": true, + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/astral-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", + "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", + "dev": true, + "peer": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/babel-core": { + "version": "7.0.0-bridge.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", + "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", + "dev": true, + "peer": true, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.2", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs2/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, + "peer": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz", + "integrity": "sha512-b37+KR2i/khY5sKmWNVQAnitvquQbNdWy6lJdsr0kmquCKEEUgMKK4SboVM3HtfnZilfjr4MMQ7vY58FVWDtIA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2", + "core-js-compat": "^3.38.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.2" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-transform-flow-enums": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz", + "integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/plugin-syntax-flow": "^7.12.1" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "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" + } + ], + "peer": true + }, + "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/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "peer": true, + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "dev": true, + "license": "MIT", + "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.13.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/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": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.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", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/browserslist": { + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/browserstack": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", + "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", + "dev": true, + "dependencies": { + "https-proxy-agent": "^2.2.1" + } + }, + "node_modules/browserstack-local": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.4.tgz", + "integrity": "sha512-OueHCaQQutO+Fezg+ZTieRn+gdV+JocLjiAQ8nYecu08GhIt3ms79cDHfpoZmECAdoQ6OLdm7ODd+DtQzl4lrA==", + "dev": true, + "dependencies": { + "agent-base": "^6.0.2", + "https-proxy-agent": "^5.0.1", + "is-running": "^2.1.0", + "ps-tree": "=1.2.0", + "temp-fs": "^0.9.9" + } + }, + "node_modules/browserstack/node_modules/agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", + "dev": true, + "dependencies": { + "es6-promisify": "^5.0.0" + }, + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/browserstack/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/browserstack/node_modules/https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "dependencies": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "node-int64": "^0.4.0" + } + }, + "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" + } + ], + "peer": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/builtin-modules": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", + "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/caching-transform": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", + "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", + "dev": true, + "dependencies": { + "hasha": "^5.0.0", + "make-dir": "^3.0.0", + "package-hash": "^4.0.0", + "write-file-atomic": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/caching-transform/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/caching-transform/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/caching-transform/node_modules/write-file-atomic": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", + "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", + "dev": true, + "dependencies": { + "imurmurhash": "^0.1.4", + "is-typedarray": "^1.0.0", + "signal-exit": "^3.0.2", + "typedarray-to-buffer": "^3.1.5" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", + "dev": true, + "peer": true, + "dependencies": { + "callsites": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/caller-callsite/node_modules/callsites": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", + "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", + "dev": true, + "peer": true, + "dependencies": { + "caller-callsite": "^2.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001651", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", + "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chai": { + "version": "4.3.8", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.8.tgz", + "integrity": "sha512-vX4YvVVtxlfSZ2VecZgFUTU5qPCYsobVI2O9FmwEXBhDigYGQA6jRXCycIs1yJnnWbZ6/+a2zNIF5DfVCcJBFQ==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chrome-launcher": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", + "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0" + }, + "bin": { + "print-chrome-path": "bin/print-chrome-path.js" + }, + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/chromium-edge-launcher": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.2.0.tgz", + "integrity": "sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "escape-string-regexp": "^4.0.0", + "is-wsl": "^2.2.0", + "lighthouse-logger": "^1.0.0", + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + } + }, + "node_modules/chromium-edge-launcher/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "peer": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ci-info": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.8.0.tgz", + "integrity": "sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "peer": true, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "peer": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "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, + "peer": 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, + "peer": true, + "dependencies": { + "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/colorette": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", + "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", + "dev": true, + "peer": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/command-exists": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", + "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", + "dev": true, + "peer": true + }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "peer": true, + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dev": true, + "peer": true, + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "dev": true, + "peer": true, + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/compression/node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compression/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, + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/compression/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "peer": true + }, + "node_modules/compression/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "peer": 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/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/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/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/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/core-js-compat": { + "version": "3.38.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.38.1.tgz", + "integrity": "sha512-JRH6gfXxGmrzF3tZ57lFx97YARxCXPaMzPo6jELZhv88pBH5VXpQ+y0znKGlFnzuaihqhLbefxSJxWJMPtfDzw==", + "dev": true, + "peer": true, + "dependencies": { + "browserslist": "^4.23.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "dev": true, + "peer": 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": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "peer": true, + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cosmiconfig/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "peer": true + }, + "node_modules/cosmiconfig/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/coveralls-next": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/coveralls-next/-/coveralls-next-4.2.0.tgz", + "integrity": "sha512-zg41a/4QDSASPtlV6gp+6owoU43U5CguxuPZR3nPZ26M5ZYdEK3MdUe7HwE+AnCZPkucudfhqqJZehCNkz2rYg==", + "dev": true, + "dependencies": { + "form-data": "4.0.0", + "js-yaml": "4.1.0", + "lcov-parse": "1.0.0", + "log-driver": "1.2.7", + "minimist": "1.2.7" + }, + "bin": { + "coveralls": "bin/coveralls.js" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/coveralls-next/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/coveralls-next/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/coveralls-next/node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "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/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/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "dev": true, + "peer": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decompress-response": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-7.0.0.tgz", + "integrity": "sha512-6IvPrADQyyPGLpMnUh6kfKiqy7SrbXbjoUuZ90WMBJKErzv2pCiwlGEXjRX9/54OnTq+XFVnkOnOMzclLI5aEA==", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/default-require-extensions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", + "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", + "dev": true, + "dependencies": { + "strip-bom": "^4.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "peer": true, + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/denodeify": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", + "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==", + "dev": true, + "peer": true + }, + "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": ">= 0.8" + } + }, + "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": ">= 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": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "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/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "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.5.12", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.12.tgz", + "integrity": "sha512-tIhPkdlEoCL1Y+PToq3zRNehUaKp3wBX/sr7aclAWdIWjvqAe/Im/H0SiCM4c1Q8BLPHCdoJTol+ZblflydehA==", + "dev": true + }, + "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/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.5.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", + "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "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.2.1", + "ws": "~8.17.1" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.1.tgz", + "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/enhanced-resolve": { + "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", + "tapable": "^2.2.0" + }, + "engines": { + "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/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/envinfo": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.13.0.tgz", + "integrity": "sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q==", + "dev": true, + "peer": true, + "bin": { + "envinfo": "dist/cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "peer": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/error-stack-parser": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", + "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", + "dev": true, + "peer": true, + "dependencies": { + "stackframe": "^1.3.4" + } + }, + "node_modules/errorhandler": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", + "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", + "dev": true, + "peer": true, + "dependencies": { + "accepts": "~1.3.7", + "escape-html": "~1.0.3" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "dev": true + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "dev": true, + "dependencies": { + "es6-promise": "^4.0.3" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "dev": true, + "engines": { + "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", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", + "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.49.0", + "@humanwhocodes/config-array": "^0.11.11", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "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.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", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "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.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz", + "integrity": "sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==", + "dev": true, + "dependencies": { + "get-stdin": "^6.0.0" + }, + "bin": { + "eslint-config-prettier-check": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=3.14.1" + } + }, + "node_modules/eslint-plugin-prettier": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", + "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", + "dev": true, + "dependencies": { + "prettier-linter-helpers": "^1.0.0" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "eslint": ">=5.0.0", + "prettier": ">=1.13.0" + }, + "peerDependenciesMeta": { + "eslint-config-prettier": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/eslint-visitor-keys": { + "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" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/eslint/node_modules/eslint-scope": { + "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", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/eslint/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/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/espree": { + "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.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "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", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", + "dev": true, + "dependencies": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "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", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "peer": true, + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/expect-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.2.1.tgz", + "integrity": "sha512-/kP8CAwxzLVEeFrMm4kMmy4CCDlpipyA7MYLVrdJIkV0fYF0UaigQHRsxHiuY/GEea+bh4KSv3TIlgr+2UL6bw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/exponential-backoff": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.1.tgz", + "integrity": "sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==", + "dev": true, + "peer": true + }, + "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", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-diff": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz", + "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", + "dev": true + }, + "node_modules/fast-glob": { + "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", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "peer": true, + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "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, + "peer": 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", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dev": true, + "dependencies": { + "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": ">= 0.8" + } + }, + "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": { + "ms": "2.0.0" + } + }, + "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": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/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/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-cache-dir/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-cache-dir/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/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": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/flat-cache": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", + "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "dev": true, + "dependencies": { + "flatted": "^3.2.7", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, + "node_modules/flow-enums-runtime": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz", + "integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==", + "dev": true, + "peer": true + }, + "node_modules/flow-parser": { + "version": "0.244.0", + "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.244.0.tgz", + "integrity": "sha512-Dkc88m5k8bx1VvHTO9HEJ7tvMcSb3Zvcv1PY4OHK7pHdtdY2aUjhmPy6vpjVJ2uUUOIybRlb91sXE8g4doChtA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/foreground-child": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", + "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/formatio": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", + "integrity": "sha512-YAF05v8+XCxAyHOdiiAmHdgCVPrWO8X744fYIPtBciIorh5LndWfi1gjeJ16sTbJhzek9kd+j3YByhohtz5Wmg==", + "deprecated": "This package is unmaintained. Use @sinonjs/formatio instead", + "dev": true, + "dependencies": { + "samsam": "1.x" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/from": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", + "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", + "dev": true + }, + "node_modules/fromentries": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", + "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", + "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/fs-access": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", + "integrity": "sha512-05cXDIwNbFaoFWaz5gNHlUTbH5whiss/hr/ibzPd4MH3cR4w0ZKeIPiVdbyJurg3O5r/Bjpvn9KOb1/rPMf3nA==", + "dev": true, + "dependencies": { + "null-check": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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": ">=6 <7 || >=8" + } + }, + "node_modules/fs-extra/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/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.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "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.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "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-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "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-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-stdin": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", + "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "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, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "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": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "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/globals": { + "version": "13.22.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", + "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "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==", + "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": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/happy-dom": { + "version": "16.6.0", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-16.6.0.tgz", + "integrity": "sha512-Zz5S9sog8a3p8XYZbO+eI1QMOAvCNnIoyrH8A8MLX+X2mJrzADTy+kdETmc4q+uD9AGAvQYGn96qBAn2RAciKw==", + "dev": true, + "dependencies": { + "webidl-conversions": "^7.0.0", + "whatwg-mimetype": "^3.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "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": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasha": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", + "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", + "dev": true, + "dependencies": { + "is-stream": "^2.0.0", + "type-fest": "^0.8.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/hasha/node_modules/type-fest": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", + "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/hermes-estree": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.22.0.tgz", + "integrity": "sha512-FLBt5X9OfA8BERUdc6aZS36Xz3rRuB0Y/mfocSADWEJfomc1xfene33GdyAmtTkKTBXTN/EgAy+rjTKkkZJHlw==", + "dev": true, + "peer": true + }, + "node_modules/hermes-parser": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.22.0.tgz", + "integrity": "sha512-gn5RfZiEXCsIWsFGsKiykekktUoh0PdFWYocXsUdZIyWSckT6UIyPcyyUIPSR3kpnELWeK3n3ztAse7Mat6PSA==", + "dev": true, + "peer": true, + "dependencies": { + "hermes-estree": "0.22.0" + } + }, + "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/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.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": { + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "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, + "peer": true, + "engines": { + "node": ">=10.17.0" + } + }, + "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" + } + ], + "peer": true + }, + "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/image-size": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/image-size/-/image-size-1.1.1.tgz", + "integrity": "sha512-541xKlUw6jr/6gGuk92F+mYM5zaFAc5ahphvkqvNe2bQ6gVBkd6bfrmVJ2t4KDAfikAYZyIqTnktX3i6/aQDrQ==", + "dev": true, + "peer": true, + "dependencies": { + "queue": "6.0.2" + }, + "bin": { + "image-size": "bin/image-size.js" + }, + "engines": { + "node": ">=16.x" + } + }, + "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": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "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==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "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==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "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/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dev": true, + "peer": true, + "dependencies": { + "loose-envify": "^1.0.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, + "peer": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-directory": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", + "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "dev": true, + "peer": true, + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "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/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": ">=0.10.0" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true + }, + "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": ">=0.12.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==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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, + "peer": true, + "dependencies": { + "isobject": "^3.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-reference": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", + "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", + "dev": true, + "dependencies": { + "@types/estree": "*" + } + }, + "node_modules/is-running": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", + "integrity": "sha512-mjJd3PujZMl7j+D395WTIO5tU5RIDBfVSRtRR4VOJou3H66E38UjbjvDGh3slJzPuolsb+yQFqwHNNdyp5jg3w==", + "dev": true + }, + "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": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "dev": true + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "dev": true, + "peer": true, + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "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, + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" + } + }, + "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, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-hook": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", + "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", + "dev": true, + "dependencies": { + "append-transform": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", + "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", + "dev": true, + "dependencies": { + "archy": "^1.0.0", + "cross-spawn": "^7.0.3", + "istanbul-lib-coverage": "^3.2.0", + "p-map": "^3.0.0", + "rimraf": "^3.0.0", + "uuid": "^8.3.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-processinfo/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "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": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", + "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "dev": true, + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jest-environment-node": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-get-type": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", + "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", + "dev": true, + "peer": true, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-message-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.12.13", + "@jest/types": "^29.6.3", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.4", + "pretty-format": "^29.7.0", + "slash": "^3.0.0", + "stack-utils": "^2.0.3" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-mock": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "jest-util": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-util": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "@types/node": "*", + "chalk": "^4.0.0", + "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" + } + }, + "node_modules/jest-validate": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^29.6.3", + "camelcase": "^6.2.0", + "chalk": "^4.0.0", + "jest-get-type": "^29.6.3", + "leven": "^3.1.0", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.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-worker/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, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/jiti": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.1.tgz", + "integrity": "sha512-yPBThwecp1wS9DmoA4x4KR2h3QoslacnDR8ypuFM962kI4/456Iy1oHx2RAgh4jfZNdn0bctsdadceiBUgpU1g==", + "dev": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/joi": { + "version": "17.13.3", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", + "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "dev": true, + "peer": true, + "dependencies": { + "@hapi/hoek": "^9.3.0", + "@hapi/topo": "^5.1.0", + "@sideway/address": "^4.1.5", + "@sideway/formula": "^3.0.1", + "@sideway/pinpoint": "^2.0.0" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "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==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsc-android": { + "version": "250231.0.0", + "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-250231.0.0.tgz", + "integrity": "sha512-rS46PvsjYmdmuz1OAWXY/1kCYG7pnf1TBqeTiOJr1iDz7s5DLxxC9n/ZMknLDxzYzNVfI7R95MH10emSSG1Wuw==", + "dev": true, + "peer": true + }, + "node_modules/jsc-safe-url": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz", + "integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==", + "dev": true, + "peer": true + }, + "node_modules/jscodeshift": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.14.0.tgz", + "integrity": "sha512-7eCC1knD7bLUPuSCwXsMZUH51O8jIcoVyKtI6P0XM0IVzlGjckPy3FIwQlorzbN0Sg79oK+RlohN32Mqf/lrYA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.13.16", + "@babel/parser": "^7.13.16", + "@babel/plugin-proposal-class-properties": "^7.13.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", + "@babel/plugin-proposal-optional-chaining": "^7.13.12", + "@babel/plugin-transform-modules-commonjs": "^7.13.8", + "@babel/preset-flow": "^7.13.13", + "@babel/preset-typescript": "^7.13.0", + "@babel/register": "^7.13.16", + "babel-core": "^7.0.0-bridge.0", + "chalk": "^4.1.2", + "flow-parser": "0.*", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.4", + "neo-async": "^2.5.0", + "node-dir": "^0.1.17", + "recast": "^0.21.0", + "temp": "^0.8.4", + "write-file-atomic": "^2.3.0" + }, + "bin": { + "jscodeshift": "bin/jscodeshift.js" + }, + "peerDependencies": { + "@babel/preset-env": "^7.1.6" + } + }, + "node_modules/jscodeshift/node_modules/write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "dev": true, + "peer": true, + "dependencies": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true, + "peer": true + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "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": "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" + } + }, + "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/just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, + "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-browserstack-launcher": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/karma-browserstack-launcher/-/karma-browserstack-launcher-1.6.0.tgz", + "integrity": "sha512-Y/UWPdHZkHIVH2To4GWHCTzmrsB6H7PBWy6pw+TWz5sr4HW2mcE+Uj6qWgoVNxvQU1Pfn5LQQzI6EQ65p8QbiQ==", + "dev": true, + "dependencies": { + "browserstack": "~1.5.1", + "browserstack-local": "^1.3.7", + "q": "~1.5.0" + }, + "peerDependencies": { + "karma": ">=0.9" + } + }, + "node_modules/karma-chai": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/karma-chai/-/karma-chai-0.1.0.tgz", + "integrity": "sha512-mqKCkHwzPMhgTYca10S90aCEX9+HjVjjrBFAsw36Zj7BlQNbokXXCAe6Ji04VUMsxcY5RLP7YphpfO06XOubdg==", + "dev": true, + "peerDependencies": { + "chai": "*", + "karma": ">=0.10.9" + } + }, + "node_modules/karma-chrome-launcher": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", + "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", + "dev": true, + "dependencies": { + "fs-access": "^1.0.0", + "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-mocha": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-2.0.1.tgz", + "integrity": "sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ==", + "dev": true, + "dependencies": { + "minimist": "^1.2.3" + } + }, + "node_modules/karma-webpack": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-5.0.1.tgz", + "integrity": "sha512-oo38O+P3W2mSPCSUrQdySSPv1LvPpXP+f+bBimNomS5sW+1V4SuhCuW8TfJzV+rDv921w2fDSDw0xJbPe6U+kQ==", + "dev": true, + "dependencies": { + "glob": "^7.1.3", + "minimatch": "^9.0.3", + "webpack-merge": "^4.1.5" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "webpack": "^5.0.0" + } + }, + "node_modules/karma-webpack/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/karma-webpack/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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/ua-parser-js": { + "version": "0.7.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.38.tgz", + "integrity": "sha512-fYmIy7fKTSFAhG3fuPlubeGaMoAd6r0rSnfEsO5nEY55i26KSLt9EH7PLQiiqPUhNqYIJvSkTy1oArIcXAbPbA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "engines": { + "node": "*" + } + }, + "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": ">=10" + } + }, + "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": ">=10" + } + }, + "node_modules/keyv": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", + "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "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, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/lcov-parse": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", + "integrity": "sha512-aprLII/vPzuQvYZnDRU78Fns9I2Ag3gi4Ipga/hxnVMCZC8DnR2nI7XBqrPoywGfxqIx/DgarGvDJZAD3YBTgQ==", + "dev": true, + "bin": { + "lcov-parse": "bin/cli.js" + } + }, + "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, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lighthouse-logger": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", + "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", + "dev": true, + "peer": true, + "dependencies": { + "debug": "^2.6.9", + "marky": "^1.2.2" + } + }, + "node_modules/lighthouse-logger/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, + "peer": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/lighthouse-logger/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "peer": true + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "peer": true + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "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.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "peer": true + }, + "node_modules/lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", + "dev": true + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, + "node_modules/lodash.throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", + "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", + "dev": true, + "peer": true + }, + "node_modules/log-driver": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", + "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", + "dev": true, + "engines": { + "node": ">=0.8.6" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + "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": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "flatted": "^3.2.7", + "rfdc": "^1.3.0", + "streamroller": "^3.1.5" + }, + "engines": { + "node": ">=8.0" + } }, - "async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + "node_modules/logkitty": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", + "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-fragments": "^0.2.1", + "dayjs": "^1.8.15", + "yargs": "^15.1.0" + }, + "bin": { + "logkitty": "bin/logkitty.js" + } }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "node_modules/logkitty/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==" + "node_modules/logkitty/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==", + "dev": true, + "peer": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + "node_modules/logkitty/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==", + "dev": true, + "peer": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + "node_modules/logkitty/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, + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } + "node_modules/logkitty/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, + "peer": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" } }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "requires": { - "tweetnacl": "^0.14.3" + "node_modules/logkitty/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==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" } }, - "block-stream": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", - "requires": { - "inherits": "~2.0.0" + "node_modules/logkitty/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true, + "peer": true + }, + "node_modules/logkitty/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "peer": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" } }, - "bluebird": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", - "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" + "node_modules/logkitty/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "peer": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "node_modules/lolex": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", + "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", + "dev": true, + "dependencies": { + "@sinonjs/commons": "^1.7.0" } }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" } }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + "node_modules/loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "deprecated": "Please upgrade to 2.3.7 which fixes GHSA-4q6p-r6v2-jvc5", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.0" + } }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } }, - "builtins": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", - "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=" + "node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "dependencies": { + "sourcemap-codec": "^1.4.8" + } }, - "byline": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/byline/-/byline-5.0.0.tgz", - "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=" + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } }, - "byte-size": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-4.0.3.tgz", - "integrity": "sha512-JGC3EV2bCzJH/ENSh3afyJrH4vwxbHTuO5ljLoI5+2iJOcEpMgP8T782jH9b5qGxf2mSUIp1lfGnfKNrRHpvVg==" - }, - "cacache": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.2.0.tgz", - "integrity": "sha512-IFWl6lfK6wSeYCHUXh+N1lY72UDrpyrYQJNIVQf48paDuWbv5RbAtJYf/4gUQFObTCHZwdZ5sI8Iw7nqwP6nlQ==", - "requires": { - "bluebird": "^3.5.1", - "chownr": "^1.0.1", - "figgy-pudding": "^3.1.0", - "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "lru-cache": "^4.1.3", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.2", - "ssri": "^6.0.0", - "unique-filename": "^1.1.0", - "y18n": "^4.0.0" + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "call-me-maybe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "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, + "peer": true, + "dependencies": { + "tmpl": "1.0.5" + } }, - "camelcase": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" + "node_modules/map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", + "dev": true }, - "camelcase-keys": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-4.2.0.tgz", - "integrity": "sha1-oqpfsa9oh1glnDLBQUJteJI7m3c=", - "requires": { - "camelcase": "^4.1.0", - "map-obj": "^2.0.0", - "quick-lru": "^1.0.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" - } + "node_modules/marky": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", + "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", + "dev": true, + "peer": true + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" } }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + "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" + } }, - "center-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", - "requires": { - "align-text": "^0.1.3", - "lazy-cache": "^1.0.3" + "node_modules/memoize-one": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", + "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", + "dev": true, + "peer": true + }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "dev": true, + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" } }, - "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/metro": { + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro/-/metro-0.80.10.tgz", + "integrity": "sha512-FDPi0X7wpafmDREXe1lgg3WzETxtXh6Kpq8+IwsG35R2tMyp2kFIqDdshdohuvDt1J/qDARcEPq7V/jElTb1kA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/parser": "^7.20.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.20.0", + "@babel/types": "^7.20.0", + "accepts": "^1.3.7", + "chalk": "^4.0.0", + "ci-info": "^2.0.0", + "connect": "^3.6.5", + "debug": "^2.2.0", + "denodeify": "^1.2.1", + "error-stack-parser": "^2.0.6", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "hermes-parser": "0.23.0", + "image-size": "^1.0.2", + "invariant": "^2.2.4", + "jest-worker": "^29.6.3", + "jsc-safe-url": "^0.2.2", + "lodash.throttle": "^4.1.1", + "metro-babel-transformer": "0.80.10", + "metro-cache": "0.80.10", + "metro-cache-key": "0.80.10", + "metro-config": "0.80.10", + "metro-core": "0.80.10", + "metro-file-map": "0.80.10", + "metro-resolver": "0.80.10", + "metro-runtime": "0.80.10", + "metro-source-map": "0.80.10", + "metro-symbolicate": "0.80.10", + "metro-transform-plugins": "0.80.10", + "metro-transform-worker": "0.80.10", + "mime-types": "^2.1.27", + "node-fetch": "^2.2.0", + "nullthrows": "^1.1.1", + "serialize-error": "^2.1.0", + "source-map": "^0.5.6", + "strip-ansi": "^6.0.0", + "throat": "^5.0.0", + "ws": "^7.5.10", + "yargs": "^17.6.2" + }, + "bin": { + "metro": "src/cli.js" + }, + "engines": { + "node": ">=18" } }, - "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=" + "node_modules/metro-babel-transformer": { + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.80.10.tgz", + "integrity": "sha512-GXHueUzgzcazfzORDxDzWS9jVVRV6u+cR6TGvHOfGdfLzJCj7/D0PretLfyq+MwN20twHxLW+BUXkoaB8sCQBg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.20.0", + "flow-enums-runtime": "^0.0.6", + "hermes-parser": "0.23.0", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/metro-babel-transformer/node_modules/hermes-estree": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.23.0.tgz", + "integrity": "sha512-Rkp0PNLGpORw4ktsttkVbpYJbrYKS3hAnkxu8D9nvQi6LvSbuPa+tYw/t2u3Gjc35lYd/k95YkjqyTcN4zspag==", + "dev": true, + "peer": true + }, + "node_modules/metro-babel-transformer/node_modules/hermes-parser": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.23.0.tgz", + "integrity": "sha512-xLwM4ylfHGwrm+2qXfO1JT/fnqEDGSnpS/9hQ4VLtqTexSviu2ZpBgz07U8jVtndq67qdb/ps0qvaWDZ3fkTyg==", + "dev": true, + "peer": true, + "dependencies": { + "hermes-estree": "0.23.0" + } }, - "chownr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.0.1.tgz", - "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=" + "node_modules/metro-cache": { + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.80.10.tgz", + "integrity": "sha512-8CBtDJwMguIE5RvV3PU1QtxUG8oSSX54mIuAbRZmcQ0MYiOl9JdrMd4JCBvIyhiZLoSStph425SMyCSnjtJsdA==", + "dev": true, + "peer": true, + "dependencies": { + "exponential-backoff": "^3.1.1", + "flow-enums-runtime": "^0.0.6", + "metro-core": "0.80.10" + }, + "engines": { + "node": ">=18" + } }, - "ci-info": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.4.0.tgz", - "integrity": "sha512-Oqmw2pVfCl8sCL+1QgMywPfdxPJPkC51y4usw0iiE2S9qnEOAqXy8bwl1CpMpnoU39g4iKJTz6QZj+28FvOnjQ==" - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - } + "node_modules/metro-cache-key": { + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.80.10.tgz", + "integrity": "sha512-57qBhO3zQfoU/hP4ZlLW5hVej2jVfBX6B4NcSfMj4LgDPL3YknWg80IJBxzQfjQY/m+fmMLmPy8aUMHzUp/guA==", + "dev": true, + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=18" } }, - "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", - "requires": { - "restore-cursor": "^2.0.0" + "node_modules/metro-config": { + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.80.10.tgz", + "integrity": "sha512-0GYAw0LkmGbmA81FepKQepL1KU/85Cyv7sAiWm6QWeV6AcVCpsKg6jGLqGHJ0LLPL60rWzA4TV1DQAlzdJAEtA==", + "dev": true, + "peer": true, + "dependencies": { + "connect": "^3.6.5", + "cosmiconfig": "^5.0.5", + "flow-enums-runtime": "^0.0.6", + "jest-validate": "^29.6.3", + "metro": "0.80.10", + "metro-cache": "0.80.10", + "metro-core": "0.80.10", + "metro-runtime": "0.80.10" + }, + "engines": { + "node": ">=18" } }, - "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" + "node_modules/metro-config/node_modules/cosmiconfig": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", + "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", + "dev": true, + "peer": true, + "dependencies": { + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.13.1", + "parse-json": "^4.0.0" + }, + "engines": { + "node": ">=4" + } }, - "cliui": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", - "requires": { - "center-align": "^0.1.1", - "right-align": "^0.1.1", - "wordwrap": "0.0.2" - }, - "dependencies": { - "wordwrap": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", - "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=" - } + "node_modules/metro-config/node_modules/import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "dev": true, + "peer": true, + "dependencies": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "engines": { + "node": ">=4" } }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" + "node_modules/metro-config/node_modules/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "dev": true, + "peer": true, + "dependencies": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + }, + "engines": { + "node": ">=4" + } }, - "cmd-shim": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-2.0.2.tgz", - "integrity": "sha1-b8vamUg6j9FdfTChlspp1oii79s=", - "requires": { - "graceful-fs": "^4.1.2", - "mkdirp": "~0.5.0" + "node_modules/metro-config/node_modules/resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/metro-core": { + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.80.10.tgz", + "integrity": "sha512-nwBB6HbpGlNsZMuzxVqxqGIOsn5F3JKpsp8PziS7Z4mV8a/jA1d44mVOgYmDa2q5WlH5iJfRIIhdz24XRNDlLA==", + "dev": true, + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "lodash.throttle": "^4.1.1", + "metro-resolver": "0.80.10" + }, + "engines": { + "node": ">=18" } }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + "node_modules/metro-file-map": { + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.80.10.tgz", + "integrity": "sha512-ytsUq8coneaN7ZCVk1IogojcGhLIbzWyiI2dNmw2nnBgV/0A+M5WaTTgZ6dJEz3dzjObPryDnkqWPvIGLCPtiw==", + "dev": true, + "peer": true, + "dependencies": { + "anymatch": "^3.0.3", + "debug": "^2.2.0", + "fb-watchman": "^2.0.0", + "flow-enums-runtime": "^0.0.6", + "graceful-fs": "^4.2.4", + "invariant": "^2.2.4", + "jest-worker": "^29.6.3", + "micromatch": "^4.0.4", + "node-abort-controller": "^3.1.1", + "nullthrows": "^1.1.1", + "walker": "^1.0.7" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + "node_modules/metro-file-map/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, + "peer": true, + "dependencies": { + "ms": "2.0.0" + } }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" + "node_modules/metro-file-map/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "peer": true + }, + "node_modules/metro-minify-terser": { + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.80.10.tgz", + "integrity": "sha512-Xyv9pEYpOsAerrld7cSLIcnCCpv8ItwysOmTA+AKf1q4KyE9cxrH2O2SA0FzMCkPzwxzBWmXwHUr+A89BpEM6g==", + "dev": true, + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "terser": "^5.15.0" + }, + "engines": { + "node": ">=18" } }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" + "node_modules/metro-resolver": { + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.80.10.tgz", + "integrity": "sha512-EYC5CL7f+bSzrqdk1bylKqFNGabfiI5PDctxoPx70jFt89Jz+ThcOscENog8Jb4LEQFG6GkOYlwmPpsi7kx3QA==", + "dev": true, + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=18" } }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "node_modules/metro-runtime": { + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.80.10.tgz", + "integrity": "sha512-Xh0N589ZmSIgJYAM+oYwlzTXEHfASZac9TYPCNbvjNTn0EHKqpoJ/+Im5G3MZT4oZzYv4YnvzRtjqS5k0tK94A==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.0.0", + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=18" + } }, - "columnify": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz", - "integrity": "sha1-Rzfd8ce2mop8NAVweC6UfuyOeLs=", - "requires": { - "strip-ansi": "^3.0.0", - "wcwidth": "^1.0.0" + "node_modules/metro-source-map": { + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.80.10.tgz", + "integrity": "sha512-EyZswqJW8Uukv/HcQr6K19vkMXW1nzHAZPWJSEyJFKIbgp708QfRZ6vnZGmrtFxeJEaFdNup4bGnu8/mIOYlyA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/traverse": "^7.20.0", + "@babel/types": "^7.20.0", + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-symbolicate": "0.80.10", + "nullthrows": "^1.1.1", + "ob1": "0.80.10", + "source-map": "^0.5.6", + "vlq": "^1.0.0" + }, + "engines": { + "node": ">=18" } }, - "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "requires": { - "delayed-stream": "~1.0.0" + "node_modules/metro-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, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/metro-symbolicate": { + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.80.10.tgz", + "integrity": "sha512-qAoVUoSxpfZ2DwZV7IdnQGXCSsf2cAUExUcZyuCqGlY5kaWBb0mx2BL/xbMFDJ4wBp3sVvSBPtK/rt4J7a0xBA==", + "dev": true, + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6", + "invariant": "^2.2.4", + "metro-source-map": "0.80.10", + "nullthrows": "^1.1.1", + "source-map": "^0.5.6", + "through2": "^2.0.1", + "vlq": "^1.0.0" + }, + "bin": { + "metro-symbolicate": "src/index.js" + }, + "engines": { + "node": ">=18" } }, - "compare-func": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.2.tgz", - "integrity": "sha1-md0LpFfh+bxyKxLAjsM+6rMfpkg=", - "requires": { - "array-ify": "^1.0.0", - "dot-prop": "^3.0.0" - }, - "dependencies": { - "dot-prop": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz", - "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=", - "requires": { - "is-obj": "^1.0.0" - } - } + "node_modules/metro-symbolicate/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, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/metro-transform-plugins": { + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.80.10.tgz", + "integrity": "sha512-leAx9gtA+2MHLsCeWK6XTLBbv2fBnNFu/QiYhWzMq8HsOAP4u1xQAU0tSgPs8+1vYO34Plyn79xTLUtQCRSSUQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/template": "^7.0.0", + "@babel/traverse": "^7.20.0", + "flow-enums-runtime": "^0.0.6", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" } }, - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + "node_modules/metro-transform-worker": { + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.80.10.tgz", + "integrity": "sha512-zNfNLD8Rz99U+JdOTqtF2o7iTjcDMMYdVS90z6+81Tzd2D0lDWVpls7R1hadS6xwM+ymgXFQTjM6V6wFoZaC0g==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/core": "^7.20.0", + "@babel/generator": "^7.20.0", + "@babel/parser": "^7.20.0", + "@babel/types": "^7.20.0", + "flow-enums-runtime": "^0.0.6", + "metro": "0.80.10", + "metro-babel-transformer": "0.80.10", + "metro-cache": "0.80.10", + "metro-cache-key": "0.80.10", + "metro-minify-terser": "0.80.10", + "metro-source-map": "0.80.10", + "metro-transform-plugins": "0.80.10", + "nullthrows": "^1.1.1" + }, + "engines": { + "node": ">=18" + } }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "node_modules/metro/node_modules/ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true, + "peer": true }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" + "node_modules/metro/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, + "peer": true, + "dependencies": { + "ms": "2.0.0" } }, - "config-chain": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.11.tgz", - "integrity": "sha1-q6CXR9++TD5w52am5BWG4YWfxvI=", - "requires": { - "ini": "^1.3.4", - "proto-list": "~1.2.1" + "node_modules/metro/node_modules/hermes-estree": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.23.0.tgz", + "integrity": "sha512-Rkp0PNLGpORw4ktsttkVbpYJbrYKS3hAnkxu8D9nvQi6LvSbuPa+tYw/t2u3Gjc35lYd/k95YkjqyTcN4zspag==", + "dev": true, + "peer": true + }, + "node_modules/metro/node_modules/hermes-parser": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.23.0.tgz", + "integrity": "sha512-xLwM4ylfHGwrm+2qXfO1JT/fnqEDGSnpS/9hQ4VLtqTexSviu2ZpBgz07U8jVtndq67qdb/ps0qvaWDZ3fkTyg==", + "dev": true, + "peer": true, + "dependencies": { + "hermes-estree": "0.23.0" } }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" - }, - "conventional-changelog-angular": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/conventional-changelog-angular/-/conventional-changelog-angular-1.6.6.tgz", - "integrity": "sha512-suQnFSqCxRwyBxY68pYTsFkG0taIdinHLNEAX5ivtw8bCRnIgnpvcHmlR/yjUyZIrNPYAoXlY1WiEKWgSE4BNg==", - "requires": { - "compare-func": "^1.3.1", - "q": "^1.5.1" - } - }, - "conventional-changelog-core": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/conventional-changelog-core/-/conventional-changelog-core-2.0.11.tgz", - "integrity": "sha512-HvTE6RlqeEZ/NFPtQeFLsIDOLrGP3bXYr7lFLMhCVsbduF1MXIe8OODkwMFyo1i9ku9NWBwVnVn0jDmIFXjDRg==", - "requires": { - "conventional-changelog-writer": "^3.0.9", - "conventional-commits-parser": "^2.1.7", - "dateformat": "^3.0.0", - "get-pkg-repo": "^1.0.0", - "git-raw-commits": "^1.3.6", - "git-remote-origin-url": "^2.0.0", - "git-semver-tags": "^1.3.6", - "lodash": "^4.2.1", - "normalize-package-data": "^2.3.5", - "q": "^1.5.1", - "read-pkg": "^1.1.0", - "read-pkg-up": "^1.0.1", - "through2": "^2.0.0" - }, - "dependencies": { - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "requires": { - "error-ex": "^1.2.0" - } - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } + "node_modules/metro/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "peer": true + }, + "node_modules/metro/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, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/metro/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "requires": { - "is-utf8": "^0.2.0" - } + "utf-8-validate": { + "optional": true } } }, - "conventional-changelog-preset-loader": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/conventional-changelog-preset-loader/-/conventional-changelog-preset-loader-1.1.8.tgz", - "integrity": "sha512-MkksM4G4YdrMlT2MbTsV2F6LXu/hZR0Tc/yenRrDIKRwBl/SP7ER4ZDlglqJsCzLJi4UonBc52Bkm5hzrOVCcw==" - }, - "conventional-changelog-writer": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/conventional-changelog-writer/-/conventional-changelog-writer-3.0.9.tgz", - "integrity": "sha512-n9KbsxlJxRQsUnK6wIBRnARacvNnN4C/nxnxCkH+B/R1JS2Fa+DiP1dU4I59mEDEjgnFaN2+9wr1P1s7GYB5/Q==", - "requires": { - "compare-func": "^1.3.1", - "conventional-commits-filter": "^1.1.6", - "dateformat": "^3.0.0", - "handlebars": "^4.0.2", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.2.1", - "meow": "^4.0.0", - "semver": "^5.5.0", - "split": "^1.0.0", - "through2": "^2.0.0" - } - }, - "conventional-commits-filter": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/conventional-commits-filter/-/conventional-commits-filter-1.1.6.tgz", - "integrity": "sha512-KcDgtCRKJCQhyk6VLT7zR+ZOyCnerfemE/CsR3iQpzRRFbLEs0Y6rwk3mpDvtOh04X223z+1xyJ582Stfct/0Q==", - "requires": { - "is-subset": "^0.1.1", - "modify-values": "^1.0.0" - } - }, - "conventional-commits-parser": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/conventional-commits-parser/-/conventional-commits-parser-2.1.7.tgz", - "integrity": "sha512-BoMaddIEJ6B4QVMSDu9IkVImlGOSGA1I2BQyOZHeLQ6qVOJLcLKn97+fL6dGbzWEiqDzfH4OkcveULmeq2MHFQ==", - "requires": { - "JSONStream": "^1.0.4", - "is-text-path": "^1.0.0", - "lodash": "^4.2.1", - "meow": "^4.0.0", - "split2": "^2.0.0", - "through2": "^2.0.0", - "trim-off-newlines": "^1.0.0" - } - }, - "conventional-recommended-bump": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/conventional-recommended-bump/-/conventional-recommended-bump-2.0.9.tgz", - "integrity": "sha512-YE6/o+648qkX3fTNvfBsvPW3tSnbZ6ec3gF0aBahCPgyoVHU2Mw0nUAZ1h1UN65GazpORngrgRC8QCltNYHPpQ==", - "requires": { - "concat-stream": "^1.6.0", - "conventional-changelog-preset-loader": "^1.1.8", - "conventional-commits-filter": "^1.1.6", - "conventional-commits-parser": "^2.1.7", - "git-raw-commits": "^1.3.6", - "git-semver-tags": "^1.3.6", - "meow": "^4.0.0", - "q": "^1.5.1" - } - }, - "copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "requires": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" + "node_modules/micromatch": { + "version": "4.0.5", + "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" + }, + "engines": { + "node": ">=8.6" } }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + "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": ">=4.0.0" + } }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } }, - "cosmiconfig": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.6.tgz", - "integrity": "sha512-6DWfizHriCrFWURP1/qyhsiFvYdlJzbCzmtFWh744+KyWsJo5+kPzUZZaMRSSItoYc0pxFX7gEO7ZC1/gN/7AQ==", - "requires": { - "is-directory": "^0.3.1", - "js-yaml": "^3.9.0", - "parse-json": "^4.0.0" + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "currently-unhandled": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", - "requires": { - "array-find-index": "^1.0.1" + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" } }, - "cyclist": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-0.2.2.tgz", - "integrity": "sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=" - }, - "dargs": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-4.1.0.tgz", - "integrity": "sha1-A6nbtLXC8Tm/FK5T8LiipqhvThc=", - "requires": { - "number-is-nan": "^1.0.0" + "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" } }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "requires": { - "assert-plus": "^1.0.0" + "node_modules/mocha": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", + "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" } }, - "dateformat": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" + "node_modules/mocha-lcov-reporter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/mocha-lcov-reporter/-/mocha-lcov-reporter-1.3.0.tgz", + "integrity": "sha512-/5zI2tW4lq/ft8MGpYQ1nIH6yePPtIzdGeUEwFMKfMRdLfAQ1QW2c68eEJop32tNdN5srHa/E2TzB+erm3YMYA==", + "dev": true, + "engines": { + "node": ">= 0.6.0" } }, - "debuglog": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", - "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=" - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" - }, - "decamelize-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.0.tgz", - "integrity": "sha1-0XGoeTMlKAfrPLYdwcFEXQeN8tk=", - "requires": { - "decamelize": "^1.1.0", - "map-obj": "^1.0.0" - }, + "node_modules/mocha/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/mocha/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": { - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" - } + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" } }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=" - }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "requires": { - "clone": "^1.0.2" + "node_modules/mocha/node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/mocha/node_modules/glob": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, + "node_modules/mocha/node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" - }, - "detect-indent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-5.0.0.tgz", - "integrity": "sha1-OHHMCmoALow+Wzz38zYmRnXwa50=" - }, - "dezalgo": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", - "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", - "requires": { - "asap": "^2.0.0", - "wrappy": "1" + "node_modules/mocha/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "dir-glob": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", - "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", - "requires": { - "arrify": "^1.0.1", - "path-type": "^3.0.0" + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" } }, - "dot-prop": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz", - "integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==", - "requires": { - "is-obj": "^1.0.0" + "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" } }, - "duplexer": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", - "integrity": "sha1-rOb/gIwc5mtX0ev5eXessCM0z8E=" + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true }, - "duplexify": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.6.0.tgz", - "integrity": "sha512-fO3Di4tBKJpYTFHAxTU00BcfWMY9w24r/x21a6rZRbsD/ToUgGxsMbiGRmB7uVAXeGKXD9MwiLZa5E97EVgIRQ==", - "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" + "node_modules/mocha/node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" } }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" + "node_modules/mocha/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" } }, - "encoding": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz", - "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=", - "requires": { - "iconv-lite": "~0.4.13" + "node_modules/mocha/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": ">=10" } }, - "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "requires": { - "once": "^1.4.0" + "node_modules/mocha/node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" } }, - "err-code": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/err-code/-/err-code-1.1.2.tgz", - "integrity": "sha1-BuARbTAo9q70gGhJ6w6mp0iuaWA=" + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" + "node_modules/murmurhash": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/murmurhash/-/murmurhash-2.0.1.tgz", + "integrity": "sha512-5vQEh3y+DG/lMPM0mCGPDnyV8chYg/g7rl6v3Gd8WMF9S429ox3Xk8qrk174kWhG767KQMqqxLD1WnGd77hiew==" + }, + "node_modules/nanoid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", + "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "es6-promise": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz", - "integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==" + "node_modules/native-promise-only": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", + "integrity": "sha512-zkVhZUA3y8mbz652WrL5x0fB0ehrBkulWT3TomAQ9iDtyXZvzKeEA6GPxAItBYeNYl5yngKRX612qHOhvMkDeg==", + "dev": true }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "requires": { - "es6-promise": "^4.0.3" - } + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "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 }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" - }, - "execa": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", - "integrity": "sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw==", - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } + "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" } }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/nise": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", + "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", + "dev": true, + "dependencies": { + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^5.0.1", + "path-to-regexp": "^1.7.0" + } }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } + "node_modules/nocache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", + "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/nock": { + "version": "11.9.1", + "resolved": "https://registry.npmjs.org/nock/-/nock-11.9.1.tgz", + "integrity": "sha512-U5wPctaY4/ar2JJ5Jg4wJxlbBfayxgKbiAeGh+a1kk6Pwnc2ZEuKviLyDSG6t0uXl56q7AALIxoM6FJrBSsVXA==", + "dev": true, + "dependencies": { + "debug": "^4.1.0", + "json-stringify-safe": "^5.0.1", + "lodash": "^4.17.13", + "mkdirp": "^0.5.0", + "propagate": "^2.0.0" + }, + "engines": { + "node": ">= 8.0" } }, - "external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", - "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", - "tmp": "^0.0.33" + "node_modules/node-abort-controller": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.1.1.tgz", + "integrity": "sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==", + "dev": true, + "peer": true + }, + "node_modules/node-dir": { + "version": "0.1.17", + "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", + "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==", + "dev": true, + "peer": true, + "dependencies": { + "minimatch": "^3.0.2" + }, + "engines": { + "node": ">= 0.10.5" } }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "peer": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true } } }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + "node_modules/node-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "peer": true }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" - }, - "fast-glob": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.2.tgz", - "integrity": "sha512-TR6zxCKftDQnUAPvkrCWdBgDq/gbqx8A3ApnBrR5rMvpp6+KMJI0Igw7fkWPgeVK0uhRXTXdvO3O+YP0CaUX2g==", - "requires": { - "@mrmlnc/readdir-enhanced": "^2.2.1", - "@nodelib/fs.stat": "^1.0.1", - "glob-parent": "^3.1.0", - "is-glob": "^4.0.0", - "merge2": "^1.2.1", - "micromatch": "^3.1.10" - }, + "node_modules/node-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "peer": true + }, + "node_modules/node-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "peer": true, "dependencies": { - "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", - "requires": { - "is-extglob": "^2.1.1" - } - } + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" } }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" + "node_modules/node-forge": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", + "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 6.13.0" + } }, - "figgy-pudding": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", - "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==" + "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, + "peer": true }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", - "requires": { - "escape-string-regexp": "^1.0.5" + "node_modules/node-preload": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", + "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", + "dev": true, + "dependencies": { + "process-on-spawn": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/node-releases": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true + }, + "node_modules/node-stream-zip": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", + "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.12.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/antelle" } }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "requires": { - "locate-path": "^2.0.0" + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "peer": true, + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "flush-write-stream": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", - "integrity": "sha512-calZMC10u0FMUqoiunI2AiGIIUtUIvifNwkHhNupZH4cbNnW1Itkoh/Nf5HFYmDrwWPjrUxpkZT0KhuCq0jmGw==", - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.4" + "node_modules/null-check": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", + "integrity": "sha512-j8ZNHg19TyIQOWCGeeQJBuu6xZYIEurf8M1Qsfd8mFrGEfIZytbw18YjKWg+LcO25NowXGZXZpKAx+Ui3TFfDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" - }, - "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "1.0.6", - "mime-types": "^2.1.12" + "node_modules/nullthrows": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", + "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", + "dev": true, + "peer": true + }, + "node_modules/nyc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", + "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", + "dev": true, + "dependencies": { + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "caching-transform": "^4.0.0", + "convert-source-map": "^1.7.0", + "decamelize": "^1.2.0", + "find-cache-dir": "^3.2.0", + "find-up": "^4.1.0", + "foreground-child": "^2.0.0", + "get-package-type": "^0.1.0", + "glob": "^7.1.6", + "istanbul-lib-coverage": "^3.0.0", + "istanbul-lib-hook": "^3.0.0", + "istanbul-lib-instrument": "^4.0.0", + "istanbul-lib-processinfo": "^2.0.2", + "istanbul-lib-report": "^3.0.0", + "istanbul-lib-source-maps": "^4.0.0", + "istanbul-reports": "^3.0.2", + "make-dir": "^3.0.0", + "node-preload": "^0.2.1", + "p-map": "^3.0.0", + "process-on-spawn": "^1.0.0", + "resolve-from": "^5.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "spawn-wrap": "^2.0.0", + "test-exclude": "^6.0.0", + "yargs": "^15.0.2" + }, + "bin": { + "nyc": "bin/nyc.js" + }, + "engines": { + "node": ">=8.9" } }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "requires": { - "map-cache": "^0.2.2" + "node_modules/nyc/node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" } }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" + "node_modules/nyc/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==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "fs-extra": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz", - "integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==", - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "node_modules/nyc/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": ">=8" } }, - "fs-minipass": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", - "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", - "requires": { - "minipass": "^2.2.1" + "node_modules/nyc/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==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" } }, - "fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "requires": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" + "node_modules/nyc/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "fstream": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", - "integrity": "sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE=", - "requires": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" - } - }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "genfun": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/genfun/-/genfun-4.0.1.tgz", - "integrity": "sha1-7RAEHy5KfxsKOEZtF6XD4n3x38E=" + "node_modules/nyc/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" + } }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + "node_modules/nyc/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" + } }, - "get-pkg-repo": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/get-pkg-repo/-/get-pkg-repo-1.4.0.tgz", - "integrity": "sha1-xztInAbYDMVTbCyFP54FIyBWly0=", - "requires": { - "hosted-git-info": "^2.1.4", - "meow": "^3.3.0", - "normalize-package-data": "^2.3.0", - "parse-github-repo-url": "^1.3.0", - "through2": "^2.0.0" - }, - "dependencies": { - "camelcase": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" - }, - "camelcase-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", - "requires": { - "camelcase": "^2.0.0", - "map-obj": "^1.0.0" - } - }, - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "requires": { - "repeating": "^2.0.0" - } - }, - "map-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" - }, - "meow": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", - "requires": { - "camelcase-keys": "^2.0.0", - "decamelize": "^1.1.2", - "loud-rejection": "^1.0.0", - "map-obj": "^1.0.1", - "minimist": "^1.1.3", - "normalize-package-data": "^2.3.4", - "object-assign": "^4.0.1", - "read-pkg-up": "^1.0.1", - "redent": "^1.0.0", - "trim-newlines": "^1.0.0" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" - }, - "redent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", - "requires": { - "indent-string": "^2.1.0", - "strip-indent": "^1.0.1" - } - }, - "strip-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-1.0.1.tgz", - "integrity": "sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI=", - "requires": { - "get-stdin": "^4.0.1" - } - }, - "trim-newlines": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz", - "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=" - } + "node_modules/nyc/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/nyc/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/nyc/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==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" } }, - "get-port": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", - "integrity": "sha1-3Xzn3hh8Bsi/NTeWrHHgmfCYDrw=" + "node_modules/nyc/node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dev": true + }, + "node_modules/nyc/node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dev": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" + "node_modules/nyc/node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + "node_modules/ob1": { + "version": "0.80.10", + "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.80.10.tgz", + "integrity": "sha512-dJHyB0S6JkMorUSfSGcYGkkg9kmq3qDUu3ygZUKIfkr47XOPuG35r2Sk6tbwtHXbdKIXmcMvM8DF2CwgdyaHfQ==", + "dev": true, + "peer": true, + "dependencies": { + "flow-enums-runtime": "^0.0.6" + }, + "engines": { + "node": ">=18" + } }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + "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.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "requires": { - "assert-plus": "^1.0.0" + "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" } }, - "git-raw-commits": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/git-raw-commits/-/git-raw-commits-1.3.6.tgz", - "integrity": "sha512-svsK26tQ8vEKnMshTDatSIQSMDdz8CxIIqKsvPqbtV23Etmw6VNaFAitu8zwZ0VrOne7FztwPyRLxK7/DIUTQg==", - "requires": { - "dargs": "^4.0.1", - "lodash.template": "^4.0.2", - "meow": "^4.0.0", - "split2": "^2.0.0", - "through2": "^2.0.0" + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.8" } }, - "git-remote-origin-url": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/git-remote-origin-url/-/git-remote-origin-url-2.0.0.tgz", - "integrity": "sha1-UoJlna4hBxRaERJhEq0yFuxfpl8=", - "requires": { - "gitconfiglocal": "^1.0.0", - "pify": "^2.3.0" - }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - } + "wrappy": "1" } }, - "git-semver-tags": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/git-semver-tags/-/git-semver-tags-1.3.6.tgz", - "integrity": "sha512-2jHlJnln4D/ECk9FxGEBh3k44wgYdWjWDtMmJPaecjoRmxKo3Y1Lh8GMYuOPu04CHw86NTAODchYjC5pnpMQig==", - "requires": { - "meow": "^4.0.0", - "semver": "^5.5.0" + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "peer": true, + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "gitconfiglocal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gitconfiglocal/-/gitconfiglocal-1.0.0.tgz", - "integrity": "sha1-QdBF84UaXqiPA/JMocYXgRRGS5s=", - "requires": { - "ini": "^1.3.2" + "node_modules/open": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", + "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", + "dev": true, + "peer": true, + "dependencies": { + "is-wsl": "^1.1.0" + }, + "engines": { + "node": ">=8" } }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "node_modules/open/node_modules/is-wsl": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", + "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/optionator": { + "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" + }, + "engines": { + "node": ">= 0.8.0" } }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "dev": true, + "peer": true, + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "glob-to-regexp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", - "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=" - }, - "globby": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.1.tgz", - "integrity": "sha512-oMrYrJERnKBLXNLVTqhm3vPEdJ/b2ZE28xN4YARiix1NOIOBPEpOUnm844K1iu/BkphCaf2WNFwMszv8Soi1pw==", - "requires": { - "array-union": "^1.0.1", - "dir-glob": "^2.0.0", - "fast-glob": "^2.0.2", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" - }, - "handlebars": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", - "integrity": "sha1-Ywo13+ApS8KB7a5v/F0yn8eYLcw=", - "requires": { - "async": "^1.4.0", - "optimist": "^0.6.1", - "source-map": "^0.4.4", - "uglify-js": "^2.6" - }, - "dependencies": { - "source-map": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", - "requires": { - "amdefine": ">=0.0.4" - } - } + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" - }, - "har-validator": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", - "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", - "requires": { - "ajv": "^5.3.0", - "har-schema": "^2.0.0" + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "has-flag": { + "node_modules/p-map": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" + "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", + "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "dev": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "requires": { - "is-buffer": "^1.1.5" - } - } + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" } }, - "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==" + "node_modules/package-hash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", + "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.1.15", + "hasha": "^5.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + }, + "engines": { + "node": ">=8" + } }, - "http-cache-semantics": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz", - "integrity": "sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==" + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true }, - "http-proxy-agent": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", - "integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==", - "requires": { - "agent-base": "4", - "debug": "3.1.0" + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "peer": true, "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - } + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "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" } }, - "https-proxy-agent": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", - "integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==", - "requires": { - "agent-base": "^4.1.0", - "debug": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "requires": { - "ms": "2.0.0" - } - } + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" } }, - "humanize-ms": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", - "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", - "requires": { - "ms": "^2.0.0" + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" } }, - "iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=" + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, - "ignore-walk": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", - "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", - "requires": { - "minimatch": "^3.0.4" + "node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "dependencies": { + "isarray": "0.0.1" } }, - "import-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", - "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", - "requires": { - "pkg-dir": "^2.0.0", - "resolve-cwd": "^2.0.0" + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" } }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" - }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=" + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/pause-stream": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", + "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", + "dev": true, + "dependencies": { + "through": "~2.3" } }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" - }, - "init-package-json": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/init-package-json/-/init-package-json-1.10.3.tgz", - "integrity": "sha512-zKSiXKhQveNteyhcj1CoOP8tqp1QuxPIPBl8Bid99DGLFqA1p87M6lNgfjJHSBoWJJlidGOv5rWjyYKEB3g2Jw==", - "requires": { - "glob": "^7.1.1", - "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", - "promzard": "^0.3.0", - "read": "~1.0.1", - "read-package-json": "1 || 2", - "semver": "2.x || 3.x || 4 || 5", - "validate-npm-package-license": "^3.0.1", - "validate-npm-package-name": "^3.0.0" + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "inquirer": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-5.2.0.tgz", - "integrity": "sha512-E9BmnJbAKLPGonz0HeWHtbKf+EeSP93paWO3ZYoUpq/aowXvYGjjCSuashhXPpzbArIjBbji39THkxTz9ZeEUQ==", - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^2.1.0", - "figures": "^2.0.0", - "lodash": "^4.3.0", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rxjs": "^5.5.2", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - } + "node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" } }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" - }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=" - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 6" } }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "requires": { - "builtin-modules": "^1.0.0" + "node_modules/pkg-dir/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==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "is-ci": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.0.tgz", - "integrity": "sha512-plgvKjQtalH2P3Gytb7L61Lmz95g2DlpzFiQyRSFew8WoJKxtKRzrZMeyRN2supblm3Psc8OQGy7Xjb6XG11jw==", - "requires": { - "ci-info": "^1.3.0" + "node_modules/pkg-dir/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==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" } }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } + "node_modules/pkg-dir/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" } }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "node_modules/pkg-dir/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/postcss": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + "nanoid": "^3.3.8", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" - }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "requires": { - "number-is-nan": "^1.0.0" + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", + "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=4" } }, - "is-fullwidth-code-point": { + "node_modules/prettier-linter-helpers": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "dependencies": { + "fast-diff": "^1.1.2" + }, + "engines": { + "node": ">=6.0.0" } }, - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "requires": { - "is-extglob": "^2.1.0" + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } + "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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true, + "peer": true }, - "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==", - "requires": { - "isobject": "^3.0.1" + "node_modules/process-on-spawn": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", + "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", + "dev": true, + "dependencies": { + "fromentries": "^1.2.0" + }, + "engines": { + "node": ">=8" } }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "is-subset": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-subset/-/is-subset-0.1.1.tgz", - "integrity": "sha1-ilkRfZMt4d4A8kX83TnOQ/HpOaY=" - }, - "is-text-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-text-path/-/is-text-path-1.0.1.tgz", - "integrity": "sha1-Thqg+1G/vLPpJogAE5cgLBd1tm4=", - "requires": { - "text-extensions": "^1.0.0" + "node_modules/promise": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", + "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", + "dev": true, + "peer": true, + "dependencies": { + "asap": "~2.0.6" } }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + "node_modules/promise-polyfill": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.0.tgz", + "integrity": "sha512-OzSf6gcCUQ01byV4BgwyUCswlaQQ6gzXc23aLQWhicvfX9kfsUiUhgt3CCQej8jDnl8/PhGF31JdHX2/MzF3WA==", + "dev": true }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.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==", + "dev": true, + "peer": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" } }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" - }, - "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", - "requires": { - "graceful-fs": "^4.1.6" + "node_modules/propagate": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", + "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", + "dev": true, + "engines": { + "node": ">= 8" } }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" + "node_modules/ps-tree": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", + "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", + "dev": true, + "dependencies": { + "event-stream": "=3.3.4" + }, + "bin": { + "ps-tree": "bin/ps-tree.js" + }, + "engines": { + "node": ">= 0.10" } }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" - }, - "lazy-cache": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "requires": { - "invert-kv": "^1.0.0" + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" } }, - "lerna": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/lerna/-/lerna-3.2.1.tgz", - "integrity": "sha512-nHa/TgRLOHlBm+NfeW62ffVO7hY7wJxnu6IJmZA3lrSmRlqrXZk2BPvnq0FSaCinVYjW0w0XeSNZdRKR//HAwQ==", - "requires": { - "@lerna/add": "^3.2.0", - "@lerna/bootstrap": "^3.2.0", - "@lerna/changed": "^3.2.0", - "@lerna/clean": "^3.1.3", - "@lerna/cli": "^3.2.0", - "@lerna/create": "^3.1.3", - "@lerna/diff": "^3.1.3", - "@lerna/exec": "^3.1.3", - "@lerna/import": "^3.1.3", - "@lerna/init": "^3.1.3", - "@lerna/link": "^3.1.4", - "@lerna/list": "^3.1.3", - "@lerna/publish": "^3.2.1", - "@lerna/run": "^3.1.3", - "@lerna/version": "^3.2.0", - "import-local": "^1.0.0", - "npmlog": "^4.1.2" - } - }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" + "node_modules/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", + "dev": true, + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" } }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "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, + "engines": { + "node": ">=0.9" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" - }, - "lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=" - }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" - }, - "lodash.template": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", - "integrity": "sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A==", - "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.templatesettings": "^4.0.0" + "node_modules/querystring": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.1.tgz", + "integrity": "sha512-wkvS7mL/JMugcup3/rMitHmd9ecIGd2lhFhK9N3UUQ450h66d1r3Y9nvXzQAW1Lq+wyx61k/1pfKS5KuKiyEbg==", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.4.x" } }, - "lodash.templatesettings": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz", - "integrity": "sha1-K01OlbpEDZFf8IvImeRVNmZxMxY=", - "requires": { - "lodash._reinterpolate": "~3.0.0" + "node_modules/queue": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz", + "integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==", + "dev": true, + "peer": true, + "dependencies": { + "inherits": "~2.0.3" } }, - "longest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" - }, - "loud-rejection": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", - "requires": { - "currently-unhandled": "^0.4.1", - "signal-exit": "^3.0.0" - } + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "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" + } + ] }, - "lru-cache": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" } }, - "make-dir": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", - "integrity": "sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ==", - "requires": { - "pify": "^3.0.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" } }, - "make-fetch-happen": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-4.0.1.tgz", - "integrity": "sha512-7R5ivfy9ilRJ1EMKIOziwrns9fGeAD4bAha8EB7BIiBBLHm2KeTUGCrICFt2rbHfzheTLynv50GnNTK1zDTrcQ==", - "requires": { - "agentkeepalive": "^3.4.1", - "cacache": "^11.0.1", - "http-cache-semantics": "^3.8.1", - "http-proxy-agent": "^2.1.0", - "https-proxy-agent": "^2.2.1", - "lru-cache": "^4.1.2", - "mississippi": "^3.0.0", - "node-fetch-npm": "^2.0.2", - "promise-retry": "^1.1.1", - "socks-proxy-agent": "^4.0.0", - "ssri": "^6.0.0" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" - }, - "map-obj": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-2.0.0.tgz", - "integrity": "sha1-plzSkIepJZi4eRJXpSPgISIqwfk=" - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "requires": { - "object-visit": "^1.0.0" + "node_modules/raw-body/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": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" } }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "requires": { - "mimic-fn": "^1.0.0" + "node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dev": true, + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "meow": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/meow/-/meow-4.0.1.tgz", - "integrity": "sha512-xcSBHD5Z86zaOc+781KrupuHAzeGXSLtiAOmBsiLDiPSaYSB6hdew2ng9EBAnZ62jagG9MHAOdxpDi/lWBFJ/A==", - "requires": { - "camelcase-keys": "^4.0.0", - "decamelize-keys": "^1.0.0", - "loud-rejection": "^1.0.0", - "minimist": "^1.1.3", - "minimist-options": "^3.0.1", - "normalize-package-data": "^2.3.4", - "read-pkg-up": "^3.0.0", - "redent": "^2.0.0", - "trim-newlines": "^2.0.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + "node_modules/react-devtools-core": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-5.3.1.tgz", + "integrity": "sha512-7FSb9meX0btdBQLwdFOwt6bGqvRPabmVMMslv8fgoSPqXyuGpgQe36kx8gR86XPw7aV1yVouTp6fyZ0EH+NfUw==", + "dev": true, + "peer": true, + "dependencies": { + "shell-quote": "^1.6.1", + "ws": "^7" + } + }, + "node_modules/react-devtools-core/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true }, - "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" - } + "utf-8-validate": { + "optional": true } } }, - "merge2": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.2.2.tgz", - "integrity": "sha512-bgM8twH86rWni21thii6WCMQMRMmwqqdW3sGWi9IipnVAszdLXRjwDwAnyrVXo6DuP3AjRMMttZKUB48QWIFGg==" - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "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, + "peer": true + }, + "node_modules/react-native": { + "version": "0.75.2", + "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.75.2.tgz", + "integrity": "sha512-pP+Yswd/EurzAlKizytRrid9LJaPJzuNldc+o5t01md2VLHym8V7FWH2z9omFKtFTer8ERg0fAhG1fpd0Qq6bQ==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/create-cache-key-function": "^29.6.3", + "@react-native-community/cli": "14.0.0", + "@react-native-community/cli-platform-android": "14.0.0", + "@react-native-community/cli-platform-ios": "14.0.0", + "@react-native/assets-registry": "0.75.2", + "@react-native/codegen": "0.75.2", + "@react-native/community-cli-plugin": "0.75.2", + "@react-native/gradle-plugin": "0.75.2", + "@react-native/js-polyfills": "0.75.2", + "@react-native/normalize-colors": "0.75.2", + "@react-native/virtualized-lists": "0.75.2", + "abort-controller": "^3.0.0", + "anser": "^1.4.9", + "ansi-regex": "^5.0.0", + "base64-js": "^1.5.1", + "chalk": "^4.0.0", + "event-target-shim": "^5.0.1", + "flow-enums-runtime": "^0.0.6", + "glob": "^7.1.1", + "invariant": "^2.2.4", + "jest-environment-node": "^29.6.3", + "jsc-android": "^250231.0.0", + "memoize-one": "^5.0.0", + "metro-runtime": "^0.80.3", + "metro-source-map": "^0.80.3", + "mkdirp": "^0.5.1", + "nullthrows": "^1.1.1", + "pretty-format": "^26.5.2", + "promise": "^8.3.0", + "react-devtools-core": "^5.3.1", + "react-refresh": "^0.14.0", + "regenerator-runtime": "^0.13.2", + "scheduler": "0.24.0-canary-efb381bbf-20230505", + "semver": "^7.1.3", + "stacktrace-parser": "^0.1.10", + "whatwg-fetch": "^3.0.0", + "ws": "^6.2.2", + "yargs": "^17.6.2" + }, + "bin": { + "react-native": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/react": "^18.2.6", + "react": "^18.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } } }, - "mime-db": { - "version": "1.36.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", - "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==" + "node_modules/react-native/node_modules/@jest/types": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", + "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", + "dev": true, + "peer": true, + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + }, + "engines": { + "node": ">= 10.14.2" + } }, - "mime-types": { - "version": "2.1.20", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", - "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", - "requires": { - "mime-db": "~1.36.0" + "node_modules/react-native/node_modules/@types/yargs": { + "version": "15.0.19", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.19.tgz", + "integrity": "sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==", + "dev": true, + "peer": true, + "dependencies": { + "@types/yargs-parser": "*" } }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + "node_modules/react-native/node_modules/pretty-format": { + "version": "26.6.2", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", + "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", + "dev": true, + "peer": true, + "dependencies": { + "@jest/types": "^26.6.2", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/react-native/node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "peer": true + }, + "node_modules/react-native/node_modules/ws": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.3.tgz", + "integrity": "sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==", + "dev": true, + "peer": true, + "dependencies": { + "async-limiter": "~1.0.0" + } }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" + "node_modules/react-refresh": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", + "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" } }, - "minimist": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.1.0.tgz", - "integrity": "sha1-md9lelJXTCHJBXSX33QnkLK0wN4=" + "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, + "peer": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } }, - "minimist-options": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-3.0.2.tgz", - "integrity": "sha512-FyBrT/d0d4+uiZRbqznPXqw3IpZZG3gl3wKWiX784FycUKVwBt0uLBFkQrtE4tZOrgo78nZp2jnKz3L65T5LdQ==", - "requires": { - "arrify": "^1.0.1", - "is-plain-obj": "^1.1.0" - } - }, - "minipass": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.4.tgz", - "integrity": "sha512-mlouk1OHlaUE8Odt1drMtG1bAJA4ZA6B/ehysgV0LUIrDHdKgo1KorZq3pK0b/7Z7LJIQ12MNM6aC+Tn6lUZ5w==", - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - }, - "dependencies": { - "yallist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", - "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" - } + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" } }, - "mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", - "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - } - }, - "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "requires": { - "is-plain-object": "^2.0.4" - } - } + "node_modules/readline": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", + "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==", + "dev": true, + "peer": true + }, + "node_modules/recast": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.21.5.tgz", + "integrity": "sha512-hjMmLaUXAm1hIuTqOdeYObMslq/q+Xff6QE3Y2P+uoHAg2nmVlLBps2hzh1UJDdMtDTMXOFewK6ky51JQIeECg==", + "dev": true, + "peer": true, + "dependencies": { + "ast-types": "0.15.2", + "esprima": "~4.0.0", + "source-map": "~0.6.1", + "tslib": "^2.0.1" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true, + "peer": true + }, + "node_modules/regenerate-unicode-properties": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz", + "integrity": "sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q==", + "dev": true, + "peer": true, + "dependencies": { + "regenerate": "^1.4.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true, + "peer": true + }, + "node_modules/regenerator-transform": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.2.tgz", + "integrity": "sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/runtime": "^7.8.4" } }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" + "node_modules/regexpu-core": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.3.2.tgz", + "integrity": "sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/regjsgen": "^0.8.0", + "regenerate": "^1.4.2", + "regenerate-unicode-properties": "^10.1.0", + "regjsparser": "^0.9.1", + "unicode-match-property-ecmascript": "^2.0.0", + "unicode-match-property-value-ecmascript": "^2.1.0" }, + "engines": { + "node": ">=4" + } + }, + "node_modules/regjsparser": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", + "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", + "dev": true, + "peer": true, "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - } + "jsesc": "~0.5.0" + }, + "bin": { + "regjsparser": "bin/parser" } }, - "modify-values": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz", - "integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==" + "node_modules/regjsparser/node_modules/jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", + "dev": true, + "peer": true, + "bin": { + "jsesc": "bin/jsesc" + } }, - "moment": { - "version": "2.22.2", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", - "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" + "node_modules/release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", + "dev": true, + "dependencies": { + "es6-error": "^4.0.1" + }, + "engines": { + "node": ">=4" + } }, - "move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", - "requires": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, - "ms": { + "node_modules/require-main-filename": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true }, - "multimatch": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", - "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=", - "requires": { - "array-differ": "^1.0.0", - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "minimatch": "^3.0.0" - } - }, - "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "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.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.6.tgz", + "integrity": "sha512-njhxM7mV12JfufShqGy3Rz8j11RPdLy4xi15UurGJeoHLfJpVXKdh3ueuOqbYUcDZnffr6X739JBo5LzyahEsw==", + "dev": true, + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + "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-fetch-npm": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz", - "integrity": "sha512-nJIxm1QmAj4v3nfCvEeCrYSoVwXyxLnaPBK5W1W5DGEJwjlKuC2VEUycGw5oxk+4zZahRrB84PUJJgEmhFTDFw==", - "requires": { - "encoding": "^0.1.11", - "json-parse-better-errors": "^1.0.0", - "safe-buffer": "^5.1.1" + "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==", + "dev": true, + "peer": true, + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" } }, - "node-gyp": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-3.8.0.tgz", - "integrity": "sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA==", - "requires": { - "fstream": "^1.0.0", - "glob": "^7.0.3", - "graceful-fs": "^4.1.2", - "mkdirp": "^0.5.0", - "nopt": "2 || 3", - "npmlog": "0 || 1 || 2 || 3 || 4", - "osenv": "0", - "request": "^2.87.0", - "rimraf": "2", - "semver": "~5.3.0", - "tar": "^2.0.0", - "which": "1" - }, - "dependencies": { - "semver": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" - } + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" } }, - "nopt": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", - "requires": { - "abbrev": "1" + "node_modules/rfdc": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", + "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", + "dev": true + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "2.79.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", - "requires": { - "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" + "node_modules/rollup-plugin-terser": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-5.3.1.tgz", + "integrity": "sha512-1pkwkervMJQGFYvM9nscrUoncPwiKR/K+bHdjv6PFgRo3cgPHoRT83y2Aa3GvINj4539S15t/tpFPb775TDs6w==", + "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-terser", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.5.5", + "jest-worker": "^24.9.0", + "rollup-pluginutils": "^2.8.2", + "serialize-javascript": "^4.0.0", + "terser": "^4.6.2" + }, + "peerDependencies": { + "rollup": ">=0.66.0 <3" } }, - "npm-bundled": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.5.tgz", - "integrity": "sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g==" + "node_modules/rollup-plugin-terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true }, - "npm-lifecycle": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/npm-lifecycle/-/npm-lifecycle-2.1.0.tgz", - "integrity": "sha512-QbBfLlGBKsktwBZLj6AviHC6Q9Y3R/AY4a2PYSIRhSKSS0/CxRyD/PfxEX6tPeOCXQgMSNdwGeECacstgptc+g==", - "requires": { - "byline": "^5.0.0", - "graceful-fs": "^4.1.11", - "node-gyp": "^3.8.0", - "resolve-from": "^4.0.0", - "slide": "^1.1.6", - "uid-number": "0.0.6", - "umask": "^1.1.0", - "which": "^1.3.1" + "node_modules/rollup-plugin-terser/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" } }, - "npm-package-arg": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-6.1.0.tgz", - "integrity": "sha512-zYbhP2k9DbJhA0Z3HKUePUgdB1x7MfIfKssC+WLPFMKTBZKpZh5m13PgexJjCq6KW7j17r0jHWcCpxEqnnncSA==", - "requires": { - "hosted-git-info": "^2.6.0", - "osenv": "^0.1.5", - "semver": "^5.5.0", - "validate-npm-package-name": "^3.0.0" + "node_modules/rollup-plugin-terser/node_modules/jest-worker": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", + "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", + "dev": true, + "dependencies": { + "merge-stream": "^2.0.0", + "supports-color": "^6.1.0" + }, + "engines": { + "node": ">= 6" } }, - "npm-packlist": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.1.11.tgz", - "integrity": "sha512-CxKlZ24urLkJk+9kCm48RTQ7L4hsmgSVzEk0TLGPzzyuFxD7VNgy5Sl24tOLMzQv773a/NeJ1ce1DKeacqffEA==", - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" + "node_modules/rollup-plugin-terser/node_modules/supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=6" } }, - "npm-pick-manifest": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-2.1.0.tgz", - "integrity": "sha512-q9zLP8cTr8xKPmMZN3naxp1k/NxVFsjxN6uWuO1tiw9gxg7wZWQ/b5UTfzD0ANw2q1lQxdLKTeCCksq+bPSgbQ==", - "requires": { - "npm-package-arg": "^6.0.0", - "semver": "^5.4.1" + "node_modules/rollup-plugin-terser/node_modules/terser": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", + "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", + "dev": true, + "dependencies": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=6.0.0" } }, - "npm-registry-fetch": { - "version": "3.8.0", - "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-3.8.0.tgz", - "integrity": "sha512-hrw8UMD+Nob3Kl3h8Z/YjmKamb1gf7D1ZZch2otrIXM3uFLB5vjEY6DhMlq80z/zZet6eETLbOXcuQudCB3Zpw==", - "requires": { - "JSONStream": "^1.3.4", - "bluebird": "^3.5.1", - "figgy-pudding": "^3.4.1", - "lru-cache": "^4.1.3", - "make-fetch-happen": "^4.0.1", - "npm-package-arg": "^6.1.0" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "requires": { - "path-key": "^2.0.0" + "node_modules/rollup-plugin-typescript2": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.27.3.tgz", + "integrity": "sha512-gmYPIFmALj9D3Ga1ZbTZAKTXq1JKlTQBtj299DXhqYz9cL3g/AQfUvbb2UhH+Nf++cCq941W2Mv7UcrcgLzJJg==", + "dev": true, + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "find-cache-dir": "^3.3.1", + "fs-extra": "8.1.0", + "resolve": "1.17.0", + "tslib": "2.0.1" + }, + "peerDependencies": { + "rollup": ">=1.26.3", + "typescript": ">=2.4.0" } }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" + "node_modules/rollup-plugin-typescript2/node_modules/resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "dependencies": { + "path-parse": "^1.0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + "node_modules/rollup-plugin-typescript2/node_modules/tslib": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", + "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", + "dev": true + }, + "node_modules/rollup-pluginutils": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", + "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", + "dev": true, + "dependencies": { + "estree-walker": "^0.6.1" + } }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "node_modules/rollup-pluginutils/node_modules/estree-walker": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", + "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", + "dev": true }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } + { + "type": "consulting", + "url": "https://feross.org/support" } + ], + "dependencies": { + "queue-microtask": "^1.2.2" } }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "requires": { - "isobject": "^3.0.0" - } + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "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/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, - "object.pick": { + "node_modules/samsam": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "requires": { - "isobject": "^3.0.1" + "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", + "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", + "deprecated": "This package has been deprecated in favour of @sinonjs/samsam", + "dev": true + }, + "node_modules/scheduler": { + "version": "0.24.0-canary-efb381bbf-20230505", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.24.0-canary-efb381bbf-20230505.tgz", + "integrity": "sha512-ABvovCDe/k9IluqSh4/ISoq8tIJnW8euVAWYt5j/bg6dRnqwQwiGO1F/V4AyK96NGF/FB04FhOUDuWj8IKfABA==", + "dev": true, + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" } }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" + "node_modules/schema-utils": { + "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", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "requires": { - "mimic-fn": "^1.0.0" + "node_modules/selfsigned": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", + "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", + "dev": true, + "peer": true, + "dependencies": { + "@types/node-forge": "^1.3.0", + "node-forge": "^1" + }, + "engines": { + "node": ">=10" } }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" + "node_modules/semver": { + "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" }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dev": true, + "peer": true, "dependencies": { - "minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=" - } + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" } }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + "node_modules/send/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, + "peer": true, + "dependencies": { + "ms": "2.0.0" + } }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", - "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" - }, - "dependencies": { - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - } + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true, + "peer": true + }, + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "dev": true, + "peer": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" } }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "peer": true }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" + "node_modules/serialize-error": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", + "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" } }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + "node_modules/serialize-javascript": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", + "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "requires": { - "p-try": "^1.0.0" + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dev": true, + "peer": true, + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" } }, - "p-locate": { + "node_modules/set-blocking": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "requires": { - "p-limit": "^1.1.0" - } + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dev": true }, - "p-map": { + "node_modules/setprototypeof": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", - "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==" + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true }, - "p-map-series": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-map-series/-/p-map-series-1.0.0.tgz", - "integrity": "sha1-v5j+V1cFZYqeE1G++4WuTB8Hvco=", - "requires": { - "p-reduce": "^1.0.0" + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "peer": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" } }, - "p-pipe": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-pipe/-/p-pipe-1.2.0.tgz", - "integrity": "sha1-SxoROZoRUgpneQ7loMHViB1r7+k=" + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } }, - "p-reduce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-reduce/-/p-reduce-1.0.0.tgz", - "integrity": "sha1-GMKw3ZNqRpClKfgjH1ig/bakffo=" + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + "node_modules/shell-quote": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.1.tgz", + "integrity": "sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==", + "dev": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "p-waterfall": { + "node_modules/side-channel-list": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-waterfall/-/p-waterfall-1.0.0.tgz", - "integrity": "sha1-ftlLPOszMngjU69qrhGqn8I1uwA=", - "requires": { - "p-reduce": "^1.0.0" - } - }, - "pacote": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/pacote/-/pacote-9.1.0.tgz", - "integrity": "sha512-AFXaSWhOtQf3jHqEvg+ZYH/dfT8TKq6TKspJ4qEFwVVuh5aGvMIk6SNF8vqfzz+cBceDIs9drOcpBbrPai7i+g==", - "requires": { - "bluebird": "^3.5.1", - "cacache": "^11.0.2", - "figgy-pudding": "^3.2.1", - "get-stream": "^3.0.0", - "glob": "^7.1.2", - "lru-cache": "^4.1.3", - "make-fetch-happen": "^4.0.1", - "minimatch": "^3.0.4", - "minipass": "^2.3.3", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "normalize-package-data": "^2.4.0", - "npm-package-arg": "^6.1.0", - "npm-packlist": "^1.1.10", - "npm-pick-manifest": "^2.1.0", - "npm-registry-fetch": "^3.0.0", - "osenv": "^0.1.5", - "promise-inflight": "^1.0.1", - "promise-retry": "^1.1.1", - "protoduck": "^5.0.0", - "rimraf": "^2.6.2", - "safe-buffer": "^5.1.2", - "semver": "^5.5.0", - "ssri": "^6.0.0", - "tar": "^4.4.3", - "unique-filename": "^1.1.0", - "which": "^1.3.0" - }, - "dependencies": { - "chownr": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.2.tgz", - "integrity": "sha512-GkfeAQh+QNy3wquu9oIZr6SS5x7wGdSgNQvD10X3r+AZr1Oys22HW8kAmDMvNg2+Dm0TeGaEuO8gFwdBXxwO8A==" - }, - "minizlib": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", - "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", - "requires": { - "minipass": "^2.2.1" - } - }, - "tar": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz", - "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==", - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.3.5", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - }, - "dependencies": { - "minipass": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", - "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "yallist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" - } - } - }, - "yallist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", - "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" - } + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "parallel-transform": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.1.0.tgz", - "integrity": "sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY=", - "requires": { - "cyclist": "~0.2.2", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "parse-github-repo-url": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/parse-github-repo-url/-/parse-github-repo-url-1.4.1.tgz", - "integrity": "sha1-nn2LslKmy2ukJZUGC3v23z28H1A=" + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true + }, + "node_modules/sinon": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-2.4.1.tgz", + "integrity": "sha512-vFTrO9Wt0ECffDYIPSP/E5bBugt0UjcBQOfQUMh66xzkyPEnhl/vM2LRZi2ajuTdkH07sA6DzrM6KvdvGIH8xw==", + "deprecated": "16.1.1", + "dev": true, + "dependencies": { + "diff": "^3.1.0", + "formatio": "1.2.0", + "lolex": "^1.6.0", + "native-promise-only": "^0.8.1", + "path-to-regexp": "^1.7.0", + "samsam": "^1.1.3", + "text-encoding": "0.6.4", + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=0.1.103" } }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + "node_modules/sinon/node_modules/lolex": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", + "integrity": "sha512-/bpxDL56TG5LS5zoXxKqA6Ro5tkOS5M8cm/7yQcwLIKIcM2HR5fjjNCaIhJNv96SEk4hNGSafYMZK42Xv5fihQ==", + "dev": true }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true, + "peer": true }, - "path-exists": { + "node_modules/slash": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "node_modules/slice-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", + "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "astral-regex": "^1.0.0", + "is-fullwidth-code-point": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "peer": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/slice-ansi/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "peer": true, + "dependencies": { + "color-name": "1.1.3" + } }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + "node_modules/slice-ansi/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "peer": true }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "requires": { - "pify": "^3.0.0" + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/socket.io": { + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz", + "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==", + "dev": true, + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.3.2", + "engine.io": "~6.5.2", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" } }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" + "node_modules/socket.io-adapter": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", + "dev": true, + "dependencies": { + "debug": "~4.3.4", + "ws": "~8.17.1" + } }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" + "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" + } }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "requires": { - "pinkie": "^2.0.0" + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "requires": { - "find-up": "^2.1.0" + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "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==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", + "dev": true }, - "process-nextick-args": { + "node_modules/spawn-wrap": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" - }, - "promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=" - }, - "promise-retry": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-1.1.1.tgz", - "integrity": "sha1-ZznpaOMFHaIM5kl/srUPaRHfPW0=", - "requires": { - "err-code": "^1.0.0", - "retry": "^0.10.0" + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", + "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", + "dev": true, + "dependencies": { + "foreground-child": "^2.0.0", + "is-windows": "^1.0.2", + "make-dir": "^3.0.0", + "rimraf": "^3.0.0", + "signal-exit": "^3.0.2", + "which": "^2.0.1" + }, + "engines": { + "node": ">=8" } }, - "promzard": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/promzard/-/promzard-0.3.0.tgz", - "integrity": "sha1-JqXW7ox97kyxIggwWs+5O6OCqe4=", - "requires": { - "read": "1" + "node_modules/spawn-wrap/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "proto-list": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=" - }, - "protoduck": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/protoduck/-/protoduck-5.0.0.tgz", - "integrity": "sha512-agsGWD8/RZrS4ga6v82Fxb0RHIS2RZnbsSue6A9/MBRhB/jcqOANAMNrqM9900b8duj+Gx+T/JMy5IowDoO/hQ==", - "requires": { - "genfun": "^4.0.1" + "node_modules/spawn-wrap/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" } }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + "node_modules/split": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", + "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", + "dev": true, + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" + } }, - "psl": { - "version": "1.1.29", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", - "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==" + "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 }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "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==", + "dev": true, + "peer": true, + "dependencies": { + "escape-string-regexp": "^2.0.0" + }, + "engines": { + "node": ">=10" } }, - "pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.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==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/stackframe": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", + "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", + "dev": true, + "peer": true + }, + "node_modules/stacktrace-parser": { + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", + "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", + "dev": true, + "peer": true, "dependencies": { - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } + "type-fest": "^0.7.1" + }, + "engines": { + "node": ">=6" } }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" - }, - "quick-lru": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", - "integrity": "sha1-Q2CxfGETatOAeDl/8RQW4Ybc+7g=" + "node_modules/stacktrace-parser/node_modules/type-fest": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", + "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } }, - "read": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", - "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", - "requires": { - "mute-stream": "~0.0.4" + "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/std-env": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", + "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", + "dev": true, + "license": "MIT" + }, + "node_modules/stream-combiner": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", + "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", + "dev": true, + "dependencies": { + "duplexer": "~0.1.1" } }, - "read-cmd-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-1.0.1.tgz", - "integrity": "sha1-LV0Vd4ajfAVdIgd8MsU/gynpHHs=", - "requires": { - "graceful-fs": "^4.1.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": { + "date-format": "^4.0.14", + "debug": "^4.3.4", + "fs-extra": "^8.1.0" + }, + "engines": { + "node": ">=8.0" } }, - "read-package-json": { - "version": "2.0.13", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.0.13.tgz", - "integrity": "sha512-/1dZ7TRZvGrYqE0UAfN6qQb5GYBsNcqS1C0tNK601CFOJmtHI7NIGXwetEPU/OtoFHZL3hDxm4rolFFVE9Bnmg==", - "requires": { - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "json-parse-better-errors": "^1.0.1", - "normalize-package-data": "^2.0.0", - "slash": "^1.0.0" + "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, + "peer": true, + "dependencies": { + "safe-buffer": "~5.2.0" } }, - "read-package-tree": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.2.1.tgz", - "integrity": "sha512-2CNoRoh95LxY47LvqrehIAfUVda2JbuFE/HaGYs42bNrGG+ojbw1h3zOcPcQ+1GQ3+rkzNndZn85u1XyZ3UsIA==", - "requires": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "once": "^1.3.0", - "read-package-json": "^2.0.0", - "readdir-scoped-modules": "^1.0.0" + "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" } }, - "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", - "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" + "node_modules/string-width-cjs": { + "name": "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" } }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "requires": { - "error-ex": "^1.2.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "requires": { - "pinkie-promise": "^2.0.0" - } - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "requires": { - "is-utf8": "^0.2.0" - } - } + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "readdir-scoped-modules": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.0.2.tgz", - "integrity": "sha1-n6+jfShr5dksuuve4DDcm19AZ0c=", - "requires": { - "debuglog": "^1.0.1", - "dezalgo": "^1.0.0", - "graceful-fs": "^4.1.2", - "once": "^1.3.0" + "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==", + "dev": true, + "engines": { + "node": ">=8" } }, - "redent": { + "node_modules/strip-final-newline": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-2.0.0.tgz", - "integrity": "sha1-wbIAe0LVfrE4kHmzyDM2OdXhzKo=", - "requires": { - "indent-string": "^3.0.0", - "strip-indent": "^2.0.0" + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" } }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" - }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "requires": { - "is-finite": "^1.0.0" - } - }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" - }, - "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", - "requires": { - "resolve-from": "^3.0.0" - }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "dev": true, + "peer": true + }, + "node_modules/sudo-prompt": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", + "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==", + "dev": true, + "peer": true + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" - } + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "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/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/temp": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", + "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", + "dev": true, + "peer": true, + "dependencies": { + "rimraf": "~2.6.2" + }, + "engines": { + "node": ">=6.0.0" + } }, - "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", - "requires": { - "onetime": "^2.0.0", - "signal-exit": "^3.0.2" + "node_modules/temp-fs": { + "version": "0.9.9", + "resolved": "https://registry.npmjs.org/temp-fs/-/temp-fs-0.9.9.tgz", + "integrity": "sha512-WfecDCR1xC9b0nsrzSaxPf3ZuWeWLUWblW4vlDQAa1biQaKHiImHnJfeQocQe/hXKMcolRzgkcVX/7kK4zoWbw==", + "dev": true, + "dependencies": { + "rimraf": "~2.5.2" + }, + "engines": { + "node": ">=0.8.0" } }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + "node_modules/temp-fs/node_modules/rimraf": { + "version": "2.5.4", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", + "integrity": "sha512-Lw7SHMjssciQb/rRz7JyPIy9+bbUshEucPoLRvWqy09vC5zQixl8Uet+Zl+SROBB/JMWHJRdCk1qdxNWHNMvlQ==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.0.5" + }, + "bin": { + "rimraf": "bin.js" + } }, - "retry": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.10.1.tgz", - "integrity": "sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q=" + "node_modules/temp/node_modules/rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } }, - "right-align": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", - "requires": { - "align-text": "^0.1.1" + "node_modules/terser": { + "version": "5.31.6", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.6.tgz", + "integrity": "sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg==", + "dev": true, + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" } }, - "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "requires": { - "glob": "^7.0.5" + "node_modules/terser-webpack-plugin": { + "version": "5.3.9", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz", + "integrity": "sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.17", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.1", + "terser": "^5.16.8" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } } }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "requires": { - "is-promise": "^2.1.0" + "node_modules/terser-webpack-plugin/node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" } }, - "run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", - "requires": { - "aproba": "^1.1.1" + "node_modules/terser-webpack-plugin/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==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" } }, - "rxjs": { - "version": "5.5.11", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.11.tgz", - "integrity": "sha512-3bjO7UwWfA2CV7lmwYMBzj4fQ6Cq+ftHc2MvUe+WMS7wcdJ1LosDWmdjPQanYp2dBRj572p7PeU81JUxHKOcBA==", - "requires": { - "symbol-observable": "1.0.1" + "node_modules/terser-webpack-plugin/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" } }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "requires": { - "ret": "~0.1.10" + "node_modules/terser/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "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==", + "dev": true, + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } }, - "semver": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", - "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==" + "node_modules/text-encoding": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", + "integrity": "sha512-hJnc6Qg3dWoOMkqP53F0dzRIgtmsAge09kxUIqGrEUS4qr5rWLckGYaQAVr+opBrIMRErGgy6f5aPnyPpyGRfg==", + "deprecated": "no longer maintained", + "dev": true }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true }, - "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } - } + "node_modules/throat": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", + "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", + "dev": true, + "peer": true }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "requires": { - "shebang-regex": "^1.0.0" + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true + }, + "node_modules/through2": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", + "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "dev": true, + "peer": true, + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } }, - "shebang-regex": { + "node_modules/through2/node_modules/isarray": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "dev": true, + "peer": true }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=" + "node_modules/through2/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dev": true, + "peer": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } }, - "slide": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", - "integrity": "sha1-VusCfWW00tzmyy4tMsTUr8nh1wc=" + "node_modules/through2/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "peer": true }, - "smart-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.0.1.tgz", - "integrity": "sha512-RFqinRVJVcCAL9Uh1oVqE6FZkqsyLiVOYEZ20TqIOjuX7iFVJ+zsbs4RIghnw/pTs7mZvt8ZHhvm1ZUrR4fykg==" - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, + "node_modules/through2/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "peer": true, "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } + "safe-buffer": "~5.1.0" } }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } - } + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.2.tgz", + "integrity": "sha512-al6n+QEANGFOMf/dmUMsuS5/r9B06uwlyNjZZql/zv8J7ybHCgoihBNORZCY2mzUuAnomQa2JdhyHKzZxPCrFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" } }, - "socks": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.2.1.tgz", - "integrity": "sha512-0GabKw7n9mI46vcNrVfs0o6XzWzjVa3h6GaSo2UPxtWAROXUWavfJWh1M4PR5tnE0dcnQXZIDFP4yrAysLze/w==", - "requires": { - "ip": "^1.1.5", - "smart-buffer": "^4.0.1" + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" } }, - "socks-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz", - "integrity": "sha512-Kezx6/VBguXOsEe5oU3lXYyKMi4+gva72TwJ7pQY5JfqUx2nMk7NXA6z/mpNqIlfQjWYVfeuNvQjexiTaTn6Nw==", - "requires": { - "agent-base": "~4.2.0", - "socks": "~2.2.0" + "node_modules/tinyspy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz", + "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" } }, - "sort-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-2.0.0.tgz", - "integrity": "sha1-ZYU1WEhh7JfXMNbPQYIuH1ZoQSg=", - "requires": { - "is-plain-obj": "^1.0.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" } }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "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, + "peer": true }, - "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", - "requires": { - "atob": "^2.1.1", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" } }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + "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/ts-loader": { + "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", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } }, - "spdx-correct": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" + "node_modules/ts-node": { + "version": "8.10.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", + "integrity": "sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==", + "dev": true, + "dependencies": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.17", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "typescript": ">=2.7" } }, - "spdx-exceptions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==" + "node_modules/ts-node/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/ts-node/node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" + "node_modules/tsconfig-paths": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz", + "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==", + "dev": true, + "dependencies": { + "json5": "^2.2.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + }, + "engines": { + "node": ">=6" } }, - "spdx-license-ids": { + "node_modules/tsconfig-paths/node_modules/strip-bom": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==" - }, - "split": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", - "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", - "requires": { - "through": "2" + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" } }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "requires": { - "extend-shallow": "^3.0.0" + "node_modules/tslib": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "dev": true, + "peer": true + }, + "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, + "dependencies": { + "tslib": "^1.8.1" + }, + "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" } }, - "split2": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", - "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", - "requires": { - "through2": "^2.0.2" - } + "node_modules/tsutils/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 }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" - }, - "sshpk": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz", - "integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=", - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", - "requires": { - "figgy-pudding": "^3.5.1" + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "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", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - } + "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" } }, - "stream-each": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", - "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", - "requires": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" + "node_modules/typedarray-to-buffer": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", + "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", + "dev": true, + "dependencies": { + "is-typedarray": "^1.0.0" } }, - "stream-shift": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz", - "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=" + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "node_modules/unicode-canonical-property-names-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", + "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" + "node_modules/unicode-match-property-ecmascript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", + "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", + "dev": true, + "peer": true, + "dependencies": { + "unicode-canonical-property-names-ecmascript": "^2.0.0", + "unicode-property-aliases-ecmascript": "^2.0.0" + }, + "engines": { + "node": ">=4" } }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" + "node_modules/unicode-match-property-value-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz", + "integrity": "sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" } }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + "node_modules/unicode-property-aliases-ecmascript": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", + "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } }, - "strip-eof": { + "node_modules/unpipe": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" - }, - "strip-indent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", - "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=" + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } }, - "strong-log-transformer": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/strong-log-transformer/-/strong-log-transformer-1.0.6.tgz", - "integrity": "sha1-9/uTdYpppXEUAYEnfuoMLrEwH6M=", - "requires": { - "byline": "^5.0.0", - "duplexer": "^0.1.1", - "minimist": "^0.1.0", - "moment": "^2.6.0", - "through": "^2.3.4" + "node_modules/update-browserslist-db": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.2", + "picocolors": "^1.0.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" } }, - "symbol-observable": { + "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, + "peer": true + }, + "node_modules/utils-merge": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", - "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=" - }, - "tar": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.2.tgz", - "integrity": "sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA==", - "requires": { - "block-stream": "*", - "fstream": "^1.0.12", - "inherits": "2" - }, - "dependencies": { - "fstream": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", - "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", - "requires": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" - } + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "dev": true, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "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, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vite": { + "version": "5.4.18", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.18.tgz", + "integrity": "sha512-1oDcnEp3lVyHCuQ2YFelM4Alm2o91xNoMncRm1U7S+JdYfYOvbiGZ3/CxGttrOu2M/KcGz7cRC2DoNUA6urmMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true } } }, - "temp-dir": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-1.0.0.tgz", - "integrity": "sha1-CnwOom06Oa+n4OvqnB/AvE2qAR0=" - }, - "temp-write": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/temp-write/-/temp-write-3.4.0.tgz", - "integrity": "sha1-jP9jD7fp2gXwR8dM5M5NaFRX1JI=", - "requires": { - "graceful-fs": "^4.1.2", - "is-stream": "^1.1.0", - "make-dir": "^1.0.0", - "pify": "^3.0.0", - "temp-dir": "^1.0.0", - "uuid": "^3.0.1" - } - }, - "text-extensions": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/text-extensions/-/text-extensions-1.7.0.tgz", - "integrity": "sha512-AKXZeDq230UaSzaO5s3qQUZOaC7iKbzq0jOFL614R7d9R593HLqAOL0cYoqLdkNrjBSOdmoQI06yigq1TSBXAg==" - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" - }, - "through2": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", - "requires": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" + "node_modules/vite-node": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz", + "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.7", + "es-module-lexer": "^1.5.4", + "pathe": "^1.1.2", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "requires": { - "os-tmpdir": "~1.0.2" + "node_modules/vite-node/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } + "node_modules/vite-node/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite/node_modules/@types/estree": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", + "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite/node_modules/rollup": { + "version": "4.40.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.40.0.tgz", + "integrity": "sha512-Noe455xmA96nnqH5piFtLobsGbCij7Tu+tb3c1vYjNbTkfzGqXqQXG3wJaYXkRZuQ0vEYN4bhwg7QnIrqB5B+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.7" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.40.0", + "@rollup/rollup-android-arm64": "4.40.0", + "@rollup/rollup-darwin-arm64": "4.40.0", + "@rollup/rollup-darwin-x64": "4.40.0", + "@rollup/rollup-freebsd-arm64": "4.40.0", + "@rollup/rollup-freebsd-x64": "4.40.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.40.0", + "@rollup/rollup-linux-arm-musleabihf": "4.40.0", + "@rollup/rollup-linux-arm64-gnu": "4.40.0", + "@rollup/rollup-linux-arm64-musl": "4.40.0", + "@rollup/rollup-linux-loongarch64-gnu": "4.40.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-gnu": "4.40.0", + "@rollup/rollup-linux-riscv64-musl": "4.40.0", + "@rollup/rollup-linux-s390x-gnu": "4.40.0", + "@rollup/rollup-linux-x64-gnu": "4.40.0", + "@rollup/rollup-linux-x64-musl": "4.40.0", + "@rollup/rollup-win32-arm64-msvc": "4.40.0", + "@rollup/rollup-win32-ia32-msvc": "4.40.0", + "@rollup/rollup-win32-x64-msvc": "4.40.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/vitest": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz", + "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "2.1.9", + "@vitest/mocker": "2.1.9", + "@vitest/pretty-format": "^2.1.9", + "@vitest/runner": "2.1.9", + "@vitest/snapshot": "2.1.9", + "@vitest/spy": "2.1.9", + "@vitest/utils": "2.1.9", + "chai": "^5.1.2", + "debug": "^4.3.7", + "expect-type": "^1.1.0", + "magic-string": "^0.30.12", + "pathe": "^1.1.2", + "std-env": "^3.8.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.1", + "tinypool": "^1.0.1", + "tinyrainbow": "^1.2.0", + "vite": "^5.0.0", + "vite-node": "2.1.9", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "2.1.9", + "@vitest/ui": "2.1.9", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true } } }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" + "node_modules/vitest/node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/vitest/node_modules/chai": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" } }, - "to-regex-range": { + "node_modules/vitest/node_modules/check-error": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/vitest/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" + "node_modules/vitest/node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" } }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "requires": { - "punycode": "^2.1.0" - }, + "node_modules/vitest/node_modules/loupe": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "dev": true, + "license": "MIT" + }, + "node_modules/vitest/node_modules/magic-string": { + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", + "dev": true, + "license": "MIT", "dependencies": { - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" - } + "@jridgewell/sourcemap-codec": "^1.5.0" } }, - "trim-newlines": { + "node_modules/vitest/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/vitest/node_modules/pathval": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-2.0.0.tgz", - "integrity": "sha1-tAPQuRvlDDMd/EuC7s6yLD3hbSA=" + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } }, - "trim-off-newlines": { + "node_modules/vlq": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-off-newlines/-/trim-off-newlines-1.0.1.tgz", - "integrity": "sha1-n5up2e+odkw4dpi8v+sshI8RrbM=" + "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", + "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", + "dev": true, + "peer": true }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "requires": { - "safe-buffer": "^5.0.1" + "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/walker": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", + "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", + "dev": true, + "peer": true, + "dependencies": { + "makeerror": "1.0.12" } }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" - }, - "typescript": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz", - "integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==" - }, - "uglify-js": { - "version": "2.8.29", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", - "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", - "requires": { - "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0", - "yargs": "~3.10.0" + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" } }, - "uglify-to-browserify": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", - "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=" - }, - "uid-number": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/uid-number/-/uid-number-0.0.6.tgz", - "integrity": "sha1-DqEOgDXo61uOREnwbaHHMGY7qoE=" + "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, + "peer": true, + "dependencies": { + "defaults": "^1.0.3" + } }, - "umask": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/umask/-/umask-1.1.0.tgz", - "integrity": "sha1-8pzr8B31F5ErtY/5xOUP3o4zMg0=" + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } }, - "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" - } + "node_modules/webpack": { + "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", + "@types/estree": "^1.0.0", + "@webassemblyjs/ast": "^1.11.5", + "@webassemblyjs/wasm-edit": "^1.11.5", + "@webassemblyjs/wasm-parser": "^1.11.5", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.9.0", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.15.0", + "es-module-lexer": "^1.2.1", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.2.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.3.7", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true } } }, - "unique-filename": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.0.tgz", - "integrity": "sha1-0F8v5AMlYIcfMOk8vnNe6iAVFPM=", - "requires": { - "unique-slug": "^2.0.0" + "node_modules/webpack-merge": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", + "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", + "dev": true, + "dependencies": { + "lodash": "^4.17.15" } }, - "unique-slug": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.0.tgz", - "integrity": "sha1-22Z258fMBimHj/GWCXx4hVrp9Ks=", - "requires": { - "imurmurhash": "^0.1.4" + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" } }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" - } + "node_modules/webpack/node_modules/@types/estree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.1.tgz", + "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", + "dev": true + }, + "node_modules/whatwg-fetch": { + "version": "3.6.20", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz", + "integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==", + "dev": true, + "peer": true + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" } }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "dev": true }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" } }, - "validate-npm-package-name": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", - "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", - "requires": { - "builtins": "^1.0.3" + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", - "requires": { - "defaults": "^1.0.3" + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/ws": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "webidl-conversions": { + "node_modules/xtend": { "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" - }, - "whatwg-url": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", - "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.4" } }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { - "isexe": "^2.0.0" + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" } }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "requires": { - "string-width": "^1.0.2 || 2" + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yaml": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", + "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==", + "dev": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" } }, - "window-size": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", - "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=" - }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=" + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "peer": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12" } }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } }, - "write-file-atomic": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", - "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" + "node_modules/yargs-unparser/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" } }, - "write-json-file": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/write-json-file/-/write-json-file-2.3.0.tgz", - "integrity": "sha1-K2TIozAE1UuGmMdtWFp3zrYdoy8=", - "requires": { - "detect-indent": "^5.0.0", - "graceful-fs": "^4.1.2", - "make-dir": "^1.0.0", - "pify": "^3.0.0", - "sort-keys": "^2.0.0", - "write-file-atomic": "^2.0.0" - } - }, - "write-pkg": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/write-pkg/-/write-pkg-3.2.0.tgz", - "integrity": "sha512-tX2ifZ0YqEFOF1wjRW2Pk93NLsj02+n1UP5RvO6rCs0K6R2g1padvf006cY74PQJKMGS2r42NK7FD0dG6Y6paw==", - "requires": { - "sort-keys": "^2.0.0", - "write-json-file": "^2.2.0" - } - }, - "xregexp": { + "node_modules/yargs-unparser/node_modules/decamelize": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.0.0.tgz", - "integrity": "sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg==" - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + "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" + } }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" - }, - "yargs": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", - "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", - "requires": { - "camelcase": "^1.0.2", - "cliui": "^2.1.0", - "decamelize": "^1.0.0", - "window-size": "0.1.0" - } - }, - "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", - "requires": { - "camelcase": "^4.1.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" - } + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } } } diff --git a/package.json b/package.json new file mode 100644 index 000000000..f094c577e --- /dev/null +++ b/package.json @@ -0,0 +1,178 @@ +{ + "name": "@optimizely/optimizely-sdk", + "version": "6.2.0", + "description": "JavaScript SDK for Optimizely Feature Experimentation, Optimizely Full Stack (legacy), and Optimizely Rollouts", + "main": "./dist/index.node.min.js", + "browser": "./dist/index.browser.es.min.js", + "react-native": "./dist/index.react_native.min.js", + "types": "./dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "node": { + "import": "./dist/index.node.es.min.mjs", + "require": "./dist/index.node.min.js" + }, + "react-native": { + "import": "./dist/index.react_native.es.min.js", + "require": "./dist/index.react_native.min.js" + }, + "browser": { + "import": "./dist/index.browser.es.min.js", + "require": "./dist/index.browser.min.js" + }, + "default": { + "import": "./dist/index.node.es.min.mjs", + "require": "./dist/index.node.min.js" + } + }, + "./node": { + "types": "./dist/index.d.ts", + "import": "./dist/index.node.es.min.mjs", + "require": "./dist/index.node.min.js" + }, + "./browser": { + "types": "./dist/index.d.ts", + "import": "./dist/index.browser.es.min.js", + "require": "./dist/index.browser.min.js" + }, + "./react_native": { + "types": "./dist/index.d.ts", + "default": "./dist/index.react_native.min.js", + "import": "./dist/index.react_native.es.min.js", + "require": "./dist/index.react_native.min.js" + }, + "./universal": { + "types": "./dist/index.universal.d.ts", + "import": "./dist/index.universal.es.min.js", + "require": "./dist/index.universal.min.js" + }, + "./ua_parser": { + "types": "./dist/odp/ua_parser/ua_parser.d.ts", + "import": "./dist/ua_parser.es.min.js", + "require": "./dist/ua_parser.min.js" + } + }, + "scripts": { + "clean": "rm -rf dist", + "clean:win": "(if exist dist rd /s/q dist)", + "lint": "tsc --noEmit && eslint 'lib/**/*.js' 'lib/**/*.ts'", + "test-vitest": "vitest run", + "test-mocha": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' mocha -r ts-node/register -r tsconfig-paths/register -r lib/tests/exit_on_unhandled_rejection.js 'lib/**/*.tests.ts' 'lib/**/*.tests.js'", + "test": "npm run test-mocha && npm run test-vitest", + "posttest": "npm run lint", + "test-ci": "npm run test-xbrowser && npm run test-umdbrowser", + "test-xbrowser": "karma start karma.bs.conf.js --single-run", + "test-umdbrowser": "npm run build-browser-umd && karma start karma.umd.conf.js --single-run", + "test-karma-local": "karma start karma.local_chrome.bs.conf.js && npm run build-browser-umd && karma start karma.local_chrome.umd.conf.js", + "prebuild": "npm run clean", + "build": "npm run genmsg && rollup -c && cp dist/index.browser.d.ts dist/index.d.ts", + "build:win": "npm run genmsg && rollup -c && type nul > dist/optimizely.lite.es.d.ts && type nul > dist/optimizely.lite.es.min.d.ts && type nul > dist/optimizely.lite.min.d.ts", + "build-browser-umd": "rollup -c --config-umd", + "coveralls": "nyc --reporter=lcov npm test", + "prepare": "npm run build", + "prepublishOnly": "npm test", + "postbuild:win": "@powershell copy \"dist/index.lite.d.ts\" \"dist/optimizely.lite.es.d.ts\" && @powershell copy \"dist/index.lite.d.ts\" \"dist/optimizely.lite.es.min.d.ts\" && @powershell copy \"dist/index.lite.d.ts\" \"dist/optimizely.lite.min.d.ts\"", + "genmsg": "jiti message_generator ./lib/message/error_message.ts ./lib/message/log_message.ts" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/optimizely/javascript-sdk.git" + }, + "license": "Apache-2.0", + "engines": { + "node": ">=18.0.0" + }, + "keywords": [ + "optimizely" + ], + "bugs": { + "url": "https://github.com/optimizely/javascript-sdk/issues" + }, + "homepage": "https://github.com/optimizely/javascript-sdk", + "dependencies": { + "decompress-response": "^7.0.0", + "json-schema": "^0.4.0", + "murmurhash": "^2.0.1", + "uuid": "^10.0.0" + }, + "devDependencies": { + "@react-native-async-storage/async-storage": "^2", + "@react-native-community/netinfo": "^11.3.2", + "@rollup/plugin-commonjs": "^11.0.2", + "@rollup/plugin-node-resolve": "^7.1.1", + "@types/chai": "^4.2.11", + "@types/mocha": "^5.2.7", + "@types/nise": "^1.4.0", + "@types/node": "^18.7.18", + "@types/ua-parser-js": "^0.7.36", + "@types/uuid": "^10.0.0", + "@typescript-eslint/eslint-plugin": "^5.33.0", + "@typescript-eslint/parser": "^5.33.0", + "@vitest/coverage-istanbul": "^2.0.5", + "chai": "^4.2.0", + "coveralls-next": "^4.2.0", + "eslint": "^8.21.0", + "eslint-config-prettier": "^6.10.0", + "eslint-plugin-prettier": "^3.1.2", + "happy-dom": "^16.6.0", + "jiti": "^2.4.1", + "karma": "^6.4.0", + "karma-browserstack-launcher": "^1.5.1", + "karma-chai": "^0.1.0", + "karma-chrome-launcher": "^2.1.1", + "karma-mocha": "^2.0.1", + "karma-webpack": "^5.0.1", + "lodash": "^4.17.11", + "mocha": "^10.2.0", + "mocha-lcov-reporter": "^1.3.0", + "nise": "^1.4.10", + "nock": "11.9.1", + "nyc": "^15.0.1", + "prettier": "^1.19.1", + "promise-polyfill": "8.1.0", + "rollup": "2.79.2", + "rollup-plugin-terser": "^5.3.0", + "rollup-plugin-typescript2": "^0.27.1", + "sinon": "^2.3.1", + "ts-loader": "^9.3.1", + "ts-node": "^8.10.2", + "tsconfig-paths": "^4.2.0", + "typescript": "^4.7.4", + "vitest": "^2.0.5", + "webpack": "^5.74.0" + }, + "peerDependencies": { + "@react-native-async-storage/async-storage": ">=1.0.0 <3.0.0", + "@react-native-community/netinfo": ">=5.0.0 <12.0.0", + "fast-text-encoding": "^1.0.6", + "react-native-get-random-values": "^1.11.0", + "ua-parser-js": "^1.0.38" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + }, + "@react-native-community/netinfo": { + "optional": true + }, + "react-native-get-random-values": { + "optional": true + }, + "fast-text-encoding": { + "optional": true + }, + "ua-parser-js": { + "optional": true + } + }, + "publishConfig": { + "access": "public" + }, + "files": [ + "dist/" + ], + "nyc": { + "temp-dir": "coverage/raw" + } +} diff --git a/packages/datafile-manager/.eslintrc.js b/packages/datafile-manager/.eslintrc.js deleted file mode 100644 index dbbba4a4f..000000000 --- a/packages/datafile-manager/.eslintrc.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2020 Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -module.exports = { - parserOptions: { - ecmaVersion: 2015, - sourceType: 'module', - }, - env: { - browser: true, - es6: true, - jest: true, - mocha: true, - node: true, - }, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'prettier/@typescript-eslint', - - // Prettier should always go last so it can trump any rules above - 'plugin:prettier/recommended', - 'prettier/react', - ], - rules: { - '@typescript-eslint/ban-ts-ignore': 'off', - '@typescript-eslint/camelcase': 'off', - '@typescript-eslint/no-empty-function': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off' - }, -}; diff --git a/packages/datafile-manager/.gitignore b/packages/datafile-manager/.gitignore deleted file mode 100644 index a65b41774..000000000 --- a/packages/datafile-manager/.gitignore +++ /dev/null @@ -1 +0,0 @@ -lib diff --git a/packages/datafile-manager/.nvmrc b/packages/datafile-manager/.nvmrc deleted file mode 100644 index e338b8659..000000000 --- a/packages/datafile-manager/.nvmrc +++ /dev/null @@ -1 +0,0 @@ -v10 diff --git a/packages/datafile-manager/.prettierrc b/packages/datafile-manager/.prettierrc deleted file mode 100644 index 62301e2e3..000000000 --- a/packages/datafile-manager/.prettierrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "printWidth": 120, - "tabWidth": 2, - "useTabs": false, - "semi": true, - "singleQuote": true, - "trailingComma": "es5", - "bracketSpacing": true, - "jsxBracketSameLine": false -} diff --git a/packages/datafile-manager/CHANGELOG.md b/packages/datafile-manager/CHANGELOG.md deleted file mode 100644 index ae7ff4125..000000000 --- a/packages/datafile-manager/CHANGELOG.md +++ /dev/null @@ -1,108 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). - -## [Unreleased] -Changes that have landed but are not yet released. - -## [0.9.5] - June 6, 2022 - -### Changed -- Changed typescript types file path from `lib/index.d.ts` to `lib/index.node.d.ts`. - -## [0.9.4] - February 2, 2022 - -### Changed -- Made `@react-native-async-storage/async-storage` an optional peer dependency. - -## [0.9.3] - January 31, 2022 - -### Changed -- Reverted changes in `0.9.2` and made `@react-native-async-storage/async-storage` a peer dependency again. Making it an optional dependency did not work as expected. - -## [0.9.2] - January 28, 2022 - -### Changed -- Made `@react-native-async-storage/async-storage` an optional dependency. - -## [0.9.1] - October 13, 2021 - -### Fixed -- Downgrade version of typescript to `3.8.x` to fix stubbing issue. -- Update version of logging to `0.3.1` to fix stubbing issue. - -## [0.9.0] - October 8, 2021 - -### Changed -- Update `@optimizely/js-sdk-logging` to `0.3.0`. - -## [0.8.1] - May 25, 2021 - -### Fixed - -- Replaced the deprecated `@react-native-community/async-storage` with `@react-native-async-storage/async-storage`. - -## [0.8.0] - September 1, 2020 - -### Changed - -- Modified datafile manager to accept, process, and return the datafile's string representation instead of the datafile object. -- Remove JSON parsing of response received from datafile fetch request - - Responsibility of validating the datafile now solely belongs to the project config manager -- Modified React Native async storage cache and persistent value cache implementation to store strings instead of objects as values. - -## [0.7.0] - July 28, 2020 - -### Changed - -- Move React Native async storage implementation to datafile manager - -## [0.6.0] - June 8, 2020 - -### New Features -- Added support for authenticated datafiles. `NodeDatafileManager` now accepts `datafileAccessToken` to be able to fetch authenticated datafiles. - -## [0.5.0] - April 17, 2020 - -### Breaking Changes -- Removed `StaticDatafileManager` from all top level exports -- Dropped support for Node.js version <8 - -### Fixed - -- Node datafile manager requests use gzip,deflate compression - -## [0.4.0] - June 12, 2019 - -### Changed -- Changed name of top-level exports in index.node.ts and index.browser.ts from `DatafileManager` to `HttpPollingDatafileManager`, to avoid name conflict with `DatafileManager` interface - -## [0.3.0] - May 13, 2019 - -### New Features -- Added 60 second timeout for all requests - -### Changed -- Changed value for node in engines in package.json from >=4.0.0 to >=6.0.0 -- Updated polling behavior: - - Start update interval timer immediately after request - - When update interval timer fires during request, wait until request completes, then immediately start next request -- Remove `timeoutFactory` property from `HTTPPollingDatafileManager` constructor argument object - -## [0.2.0] - April 9, 2019 - -### Changed -- Increase max error count in backoff controller (can now delay requests for up to 512 seconds) [(#17)](https://github.com/optimizely/javascript-sdk-dev/pull/17) -- Change expected format of `urlTemplate` to be sprintf-compatible (`%s` is replaced with `sdkKey`) [(#17)](https://github.com/optimizely/javascript-sdk-dev/pull/17) -- Promise returned from `onReady` is resolved immediately when `datafile` provided in constructor [(#14)](https://github.com/optimizely/javascript-sdk-dev/pull/14) -- Emit update event whenever datafile changes, not only if `autoUpdate` is true [(#14)](https://github.com/optimizely/javascript-sdk-dev/pull/14) - -### Fixed - -- Fix for Node.js requests when `urlTemplate` contains a port [(#18)](https://github.com/optimizely/javascript-sdk-dev/pull/18) - -## [0.1.0] - March 4, 2019 - -Initial release diff --git a/packages/datafile-manager/LICENSE b/packages/datafile-manager/LICENSE deleted file mode 100644 index b9f80c5bd..000000000 --- a/packages/datafile-manager/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2016-2017, Optimizely, Inc. and contributors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/datafile-manager/README.md b/packages/datafile-manager/README.md deleted file mode 100644 index db9dc0b7e..000000000 --- a/packages/datafile-manager/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# Javascript SDK Datafile Manager - -This package provides datafile manager implementations for Node.js, browsers, and React Native. - -## Requirements -In general, an ES5-compatible environment is required, as well as `Promise` (must be polyfilled if absent). - -Platform-specific minimum supported versions: - -- Node.js: `8` -- React Native: `0.61.5` - -## Installation - -```sh -npm i @optimizely/js-sdk-datafile-manager -``` - -For React Native, installation of peer dependency `@react-native-async-storage/async-storage` is also required: -```sh -npm i @react-native-async-storage/async-storage -``` - -## Usage - -```js -const { HttpPollingDatafileManager } = require('@optimizely/js-sdk-datafile-manager') - -const manager = new HttpPollingDatafileManager({ - sdkKey: '9LCprAQyd1bs1BBXZ3nVji', - autoUpdate: true, - updateInterval: 5000, -}) -manager.start() -manager.onReady().then(() => { - const datafile = manager.get() - console.log('Manager is ready with datafile: ') - console.log(datafile) -}) -manager.on('update', ({ datafile }) => { - console.log('New datafile available: ') - console.log(datafile) -}) -``` - -## Development -- The `lint` package.json script runs ESLint and Prettier. This applies formatting and lint fixes to all `.ts` files in the `src/` directory. -- The `test` package.json script runs our Jest-based test suite. -- The `tsc` package.json script runs the TypeScript compiler to build the final scripts for publishing (into the `lib/` directory). diff --git a/packages/datafile-manager/__mocks__/@react-native-async-storage/async-storage.ts b/packages/datafile-manager/__mocks__/@react-native-async-storage/async-storage.ts deleted file mode 100644 index 32f721144..000000000 --- a/packages/datafile-manager/__mocks__/@react-native-async-storage/async-storage.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export default class AsyncStorage { - static getItem(key: string, callback?: (error?: Error, result?: string) => void): Promise<string | null> { - return new Promise((resolve, reject) => { - switch (key) { - case 'keyThatExists': - resolve('{ "name": "Awesome Object" }') - break - case 'keyThatDoesNotExist': - resolve(null) - break - case 'keyWithInvalidJsonObject': - resolve('bad json }') - break - } - }) - } - - static setItem(key: string, value: string, callback?: (error?: Error) => void): Promise<void> { - return Promise.resolve() - } -} diff --git a/packages/datafile-manager/__test__/backoffController.spec.ts b/packages/datafile-manager/__test__/backoffController.spec.ts deleted file mode 100644 index c2208982c..000000000 --- a/packages/datafile-manager/__test__/backoffController.spec.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import BackoffController from '../src/backoffController'; - -describe('backoffController', () => { - describe('getDelay', () => { - it('returns 0 from getDelay if there have been no errors', () => { - const controller = new BackoffController(); - expect(controller.getDelay()).toBe(0); - }); - - it('increases the delay returned from getDelay (up to a maximum value) after each call to countError', () => { - const controller = new BackoffController(); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(8000); - expect(controller.getDelay()).toBeLessThan(9000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(16000); - expect(controller.getDelay()).toBeLessThan(17000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(32000); - expect(controller.getDelay()).toBeLessThan(33000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(64000); - expect(controller.getDelay()).toBeLessThan(65000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(128000); - expect(controller.getDelay()).toBeLessThan(129000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(256000); - expect(controller.getDelay()).toBeLessThan(257000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(512000); - expect(controller.getDelay()).toBeLessThan(513000); - // Maximum reached - additional errors should not increase the delay further - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(512000); - expect(controller.getDelay()).toBeLessThan(513000); - }); - - it('resets the error count when reset is called', () => { - const controller = new BackoffController(); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThan(0); - controller.reset(); - expect(controller.getDelay()).toBe(0); - }); - }); -}); diff --git a/packages/datafile-manager/__test__/browserDatafileManager.spec.ts b/packages/datafile-manager/__test__/browserDatafileManager.spec.ts deleted file mode 100644 index 8906495f7..000000000 --- a/packages/datafile-manager/__test__/browserDatafileManager.spec.ts +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import BrowserDatafileManager from '../src/browserDatafileManager'; -import * as browserRequest from '../src/browserRequest'; -import { Headers, AbortableRequest } from '../src/http'; -import { advanceTimersByTime, getTimerCount } from './testUtils'; - -describe('browserDatafileManager', () => { - let makeGetRequestSpy: jest.SpyInstance<AbortableRequest, [string, Headers]>; - beforeEach(() => { - jest.useFakeTimers(); - makeGetRequestSpy = jest.spyOn(browserRequest, 'makeGetRequest'); - }); - - afterEach(() => { - jest.restoreAllMocks(); - jest.clearAllTimers(); - }); - - it('calls makeGetRequest when started', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - - const manager = new BrowserDatafileManager({ - sdkKey: '1234', - autoUpdate: false, - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy.mock.calls[0][0]).toBe('https://cdn.optimizely.com/datafiles/1234.json'); - expect(makeGetRequestSpy.mock.calls[0][1]).toEqual({}); - - await manager.onReady(); - await manager.stop(); - }); - - it('calls makeGetRequest for live update requests', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'last-modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - }), - }); - const manager = new BrowserDatafileManager({ - sdkKey: '1234', - autoUpdate: true, - }); - manager.start(); - await manager.onReady(); - await advanceTimersByTime(300000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - expect(makeGetRequestSpy.mock.calls[1][0]).toBe('https://cdn.optimizely.com/datafiles/1234.json'); - expect(makeGetRequestSpy.mock.calls[1][1]).toEqual({ - 'if-modified-since': 'Fri, 08 Mar 2019 18:57:17 GMT', - }); - - await manager.stop(); - }); - - it('defaults to false for autoUpdate', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'last-modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - }), - }); - const manager = new BrowserDatafileManager({ - sdkKey: '1234', - }); - manager.start(); - await manager.onReady(); - // Should not set a timeout for a later update - expect(getTimerCount()).toBe(0); - - await manager.stop(); - }); -}); diff --git a/packages/datafile-manager/__test__/browserRequest.spec.ts b/packages/datafile-manager/__test__/browserRequest.spec.ts deleted file mode 100644 index 3e03eb843..000000000 --- a/packages/datafile-manager/__test__/browserRequest.spec.ts +++ /dev/null @@ -1,132 +0,0 @@ -/** - * @jest-environment jsdom - */ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FakeXMLHttpRequest, FakeXMLHttpRequestStatic, fakeXhr } from 'nise'; -import { makeGetRequest } from '../src/browserRequest'; - -describe('browserRequest', () => { - describe('makeGetRequest', () => { - let mockXHR: FakeXMLHttpRequestStatic; - let xhrs: FakeXMLHttpRequest[]; - beforeEach(() => { - xhrs = []; - mockXHR = fakeXhr.useFakeXMLHttpRequest(); - mockXHR.onCreate = (req): number => xhrs.push(req); - }); - - afterEach(() => { - mockXHR.restore(); - }); - - it('makes a GET request to the argument URL', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - - expect(xhrs.length).toBe(1); - const xhr = xhrs[0]; - const { url, method } = xhr; - expect({ url, method }).toEqual({ - url: 'https://cdn.optimizely.com/datafiles/123.json', - method: 'GET', - }); - - xhr.respond(200, {}, '{"foo":"bar"}'); - - await req.responsePromise; - }); - - it('returns a 200 response back to its superclass', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - - const xhr = xhrs[0]; - xhr.respond(200, {}, '{"foo":"bar"}'); - - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - headers: {}, - body: '{"foo":"bar"}', - }); - }); - - it('returns a 404 response back to its superclass', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - - const xhr = xhrs[0]; - xhr.respond(404, {}, ''); - - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 404, - headers: {}, - body: '', - }); - }); - - it('includes headers from the headers argument in the request', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/dataifles/123.json', { - 'if-modified-since': 'Fri, 08 Mar 2019 18:57:18 GMT', - }); - - expect(xhrs.length).toBe(1); - expect(xhrs[0].requestHeaders['if-modified-since']).toBe('Fri, 08 Mar 2019 18:57:18 GMT'); - - xhrs[0].respond(404, {}, ''); - - await req.responsePromise; - }); - - it('includes headers from the response in the eventual response in the return value', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - - const xhr = xhrs[0]; - xhr.respond( - 200, - { - 'content-type': 'application/json', - 'last-modified': 'Fri, 08 Mar 2019 18:57:18 GMT', - }, - '{"foo":"bar"}' - ); - - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'content-type': 'application/json', - 'last-modified': 'Fri, 08 Mar 2019 18:57:18 GMT', - }, - }); - }); - - it('returns a rejected promise when there is a request error', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - xhrs[0].error(); - await expect(req.responsePromise).rejects.toThrow(); - }); - - it('sets a timeout on the request object', () => { - const onCreateMock = jest.fn(); - mockXHR.onCreate = onCreateMock; - makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - expect(onCreateMock).toBeCalledTimes(1); - expect(onCreateMock.mock.calls[0][0].timeout).toBe(60000); - }); - }); -}); diff --git a/packages/datafile-manager/__test__/eventEmitter.spec.ts b/packages/datafile-manager/__test__/eventEmitter.spec.ts deleted file mode 100644 index a4f1c9683..000000000 --- a/packages/datafile-manager/__test__/eventEmitter.spec.ts +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import EventEmitter from '../src/eventEmitter'; - -describe('event_emitter', () => { - describe('on', () => { - let emitter: EventEmitter; - beforeEach(() => { - emitter = new EventEmitter(); - }); - - it('can add a listener for the update event', () => { - const listener = jest.fn(); - emitter.on('update', listener); - emitter.emit('update', { datafile: 'abcd' }); - expect(listener).toBeCalledTimes(1); - }); - - it('passes the argument from emit to the listener', () => { - const listener = jest.fn(); - emitter.on('update', listener); - emitter.emit('update', { datafile: 'abcd' }); - expect(listener).toBeCalledWith({ datafile: 'abcd' }); - }); - - it('returns a dispose function that removes the listener', () => { - const listener = jest.fn(); - const disposer = emitter.on('update', listener); - disposer(); - emitter.emit('update', { datafile: 'efgh' }); - expect(listener).toBeCalledTimes(0); - }); - - it('can add several listeners for the update event', () => { - const listener1 = jest.fn(); - const listener2 = jest.fn(); - const listener3 = jest.fn(); - emitter.on('update', listener1); - emitter.on('update', listener2); - emitter.on('update', listener3); - emitter.emit('update', { datafile: 'abcd' }); - expect(listener1).toBeCalledTimes(1); - expect(listener2).toBeCalledTimes(1); - expect(listener3).toBeCalledTimes(1); - }); - - it('can add several listeners and remove only some of them', () => { - const listener1 = jest.fn(); - const listener2 = jest.fn(); - const listener3 = jest.fn(); - const disposer1 = emitter.on('update', listener1); - const disposer2 = emitter.on('update', listener2); - emitter.on('update', listener3); - emitter.emit('update', { datafile: 'abcd' }); - expect(listener1).toBeCalledTimes(1); - expect(listener2).toBeCalledTimes(1); - expect(listener3).toBeCalledTimes(1); - disposer1(); - disposer2(); - emitter.emit('update', { datafile: 'efgh' }); - expect(listener1).toBeCalledTimes(1); - expect(listener2).toBeCalledTimes(1); - expect(listener3).toBeCalledTimes(2); - }); - - it('can add listeners for different events and remove only some of them', () => { - const readyListener = jest.fn(); - const updateListener = jest.fn(); - const readyDisposer = emitter.on('ready', readyListener); - const updateDisposer = emitter.on('update', updateListener); - emitter.emit('ready'); - expect(readyListener).toBeCalledTimes(1); - expect(updateListener).toBeCalledTimes(0); - emitter.emit('update', { datafile: 'abcd' }); - expect(readyListener).toBeCalledTimes(1); - expect(updateListener).toBeCalledTimes(1); - readyDisposer(); - emitter.emit('ready'); - expect(readyListener).toBeCalledTimes(1); - expect(updateListener).toBeCalledTimes(1); - emitter.emit('update', { datafile: 'efgh' }); - expect(readyListener).toBeCalledTimes(1); - expect(updateListener).toBeCalledTimes(2); - updateDisposer(); - emitter.emit('update', { datafile: 'ijkl' }); - expect(readyListener).toBeCalledTimes(1); - expect(updateListener).toBeCalledTimes(2); - }); - - it('can remove all listeners', () => { - const readyListener = jest.fn(); - const updateListener = jest.fn(); - emitter.on('ready', readyListener); - emitter.on('update', updateListener); - emitter.removeAllListeners(); - emitter.emit('update', { datafile: 'abcd' }); - emitter.emit('ready'); - expect(readyListener).toBeCalledTimes(0); - expect(updateListener).toBeCalledTimes(0); - }); - }); -}); diff --git a/packages/datafile-manager/__test__/httpPollingDatafileManager.spec.ts b/packages/datafile-manager/__test__/httpPollingDatafileManager.spec.ts deleted file mode 100644 index 79b700380..000000000 --- a/packages/datafile-manager/__test__/httpPollingDatafileManager.spec.ts +++ /dev/null @@ -1,711 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import HttpPollingDatafileManager from '../src/httpPollingDatafileManager'; -import { Headers, AbortableRequest, Response } from '../src/http'; -import { DatafileManagerConfig } from '../src/datafileManager'; -import { advanceTimersByTime, getTimerCount } from './testUtils'; -import PersistentKeyValueCache from '../src/persistentKeyValueCache'; - -jest.mock('../src/backoffController', () => { - return jest.fn().mockImplementation(() => { - const getDelayMock = jest.fn().mockImplementation(() => 0); - return { - getDelay: getDelayMock, - countError: jest.fn(), - reset: jest.fn(), - }; - }); -}); - -import BackoffController from '../src/backoffController'; - -// Test implementation: -// - Does not make any real requests: just resolves with queued responses (tests push onto queuedResponses) -class TestDatafileManager extends HttpPollingDatafileManager { - queuedResponses: (Response | Error)[] = []; - - responsePromises: Promise<Response>[] = []; - - simulateResponseDelay = false; - - // Need these unsued vars for the mock call types to work (being able to check calls) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - makeGetRequest(url: string, headers: Headers): AbortableRequest { - const nextResponse: Error | Response | undefined = this.queuedResponses.pop(); - let responsePromise: Promise<Response>; - if (nextResponse === undefined) { - responsePromise = Promise.reject('No responses queued'); - } else if (nextResponse instanceof Error) { - responsePromise = Promise.reject(nextResponse); - } else { - if (this.simulateResponseDelay) { - // Actual response will have some delay. This is required to get expected behavior for caching. - responsePromise = new Promise(resolve => setTimeout(() => resolve(nextResponse), 50)); - } else { - responsePromise = Promise.resolve(nextResponse); - } - } - this.responsePromises.push(responsePromise); - return { responsePromise, abort: jest.fn() }; - } - - getConfigDefaults(): Partial<DatafileManagerConfig> { - return {}; - } -} - -const testCache: PersistentKeyValueCache = { - get(key: string): Promise<string> { - let val = ''; - switch (key) { - case 'opt-datafile-keyThatExists': - val = JSON.stringify({ name: 'keyThatExists' }); - break; - } - return Promise.resolve(val); - }, - - set(): Promise<void> { - return Promise.resolve(); - }, - - contains(): Promise<boolean> { - return Promise.resolve(false); - }, - - remove(): Promise<void> { - return Promise.resolve(); - }, -}; - -describe('httpPollingDatafileManager', () => { - beforeEach(() => { - jest.useFakeTimers(); - }); - - let manager: TestDatafileManager; - afterEach(async () => { - if (manager) { - manager.stop(); - } - jest.clearAllMocks(); - jest.restoreAllMocks(); - jest.clearAllTimers(); - }); - - describe('when constructed with sdkKey and datafile and autoUpdate: true,', () => { - beforeEach(() => { - manager = new TestDatafileManager({ datafile: JSON.stringify({ foo: 'abcd' }), sdkKey: '123', autoUpdate: true }); - }); - - it('returns the passed datafile from get', () => { - expect(JSON.parse(manager.get())).toEqual({ foo: 'abcd' }); - }); - - it('after being started, fetches the datafile, updates itself, and updates itself again after a timeout', async () => { - manager.queuedResponses.push( - { - statusCode: 200, - body: '{"fooz": "barz"}', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - } - ); - const updateFn = jest.fn(); - manager.on('update', updateFn); - manager.start(); - expect(manager.responsePromises.length).toBe(1); - await manager.responsePromises[0]; - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - updateFn.mockReset(); - - await advanceTimersByTime(300000); - - expect(manager.responsePromises.length).toBe(2); - await manager.responsePromises[1]; - expect(updateFn).toBeCalledTimes(1); - expect(updateFn.mock.calls[0][0]).toEqual({ datafile: '{"fooz": "barz"}' }); - expect(JSON.parse(manager.get())).toEqual({ fooz: 'barz' }); - }); - }); - - describe('when constructed with sdkKey and datafile and autoUpdate: false,', () => { - beforeEach(() => { - manager = new TestDatafileManager({ - datafile: JSON.stringify({ foo: 'abcd' }), - sdkKey: '123', - autoUpdate: false, - }); - }); - - it('returns the passed datafile from get', () => { - expect(JSON.parse(manager.get())).toEqual({ foo: 'abcd' }); - }); - - it('after being started, fetches the datafile, updates itself once, but does not schedule a future update', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - expect(manager.responsePromises.length).toBe(1); - await manager.responsePromises[0]; - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - expect(getTimerCount()).toBe(0); - }); - }); - - describe('when constructed with sdkKey and autoUpdate: true', () => { - beforeEach(() => { - manager = new TestDatafileManager({ sdkKey: '123', updateInterval: 1000, autoUpdate: true }); - }); - - describe('initial state', () => { - it('returns null from get before becoming ready', () => { - expect(manager.get()).toEqual(''); - }); - }); - - describe('started state', () => { - it('passes the default datafile URL to the makeGetRequest method', async () => { - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy.mock.calls[0][0]).toBe('https://cdn.optimizely.com/datafiles/123.json'); - await manager.onReady(); - }); - - it('after being started, fetches the datafile and resolves onReady', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - }); - - describe('live updates', () => { - it('sets a timeout to update again after the update interval', async () => { - manager.queuedResponses.push( - { - statusCode: 200, - body: '{"foo3": "bar3"}', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo4": "bar4"}', - headers: {}, - } - ); - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - await manager.responsePromises[0]; - await advanceTimersByTime(1000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - }); - - it('emits update events after live updates', async () => { - manager.queuedResponses.push( - { - statusCode: 200, - body: '{"foo3": "bar3"}', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo2": "bar2"}', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - } - ); - - const updateFn = jest.fn(); - manager.on('update', updateFn); - - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - expect(updateFn).toBeCalledTimes(0); - - await advanceTimersByTime(1000); - await manager.responsePromises[1]; - expect(updateFn).toBeCalledTimes(1); - expect(updateFn.mock.calls[0][0]).toEqual({ datafile: '{"foo2": "bar2"}' }); - expect(JSON.parse(manager.get())).toEqual({ foo2: 'bar2' }); - - updateFn.mockReset(); - - await advanceTimersByTime(1000); - await manager.responsePromises[2]; - expect(updateFn).toBeCalledTimes(1); - expect(updateFn.mock.calls[0][0]).toEqual({ datafile: '{"foo3": "bar3"}' }); - expect(JSON.parse(manager.get())).toEqual({ foo3: 'bar3' }); - }); - - describe('when the update interval time fires before the request is complete', () => { - it('waits until the request is complete before making the next request', async () => { - let resolveResponsePromise: (resp: Response) => void; - const responsePromise: Promise<Response> = new Promise(res => { - resolveResponsePromise = res; - }); - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest').mockReturnValueOnce({ - abort() {}, - responsePromise, - }); - - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - - await advanceTimersByTime(1000); - expect(makeGetRequestSpy).toBeCalledTimes(1); - - resolveResponsePromise!({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - await responsePromise; - await advanceTimersByTime(0); - expect(makeGetRequestSpy).toBeCalledTimes(2); - }); - }); - - it('cancels a pending timeout when stop is called', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - - manager.start(); - await manager.onReady(); - - expect(getTimerCount()).toBe(1); - manager.stop(); - expect(getTimerCount()).toBe(0); - }); - - it('cancels reactions to a pending fetch when stop is called', async () => { - manager.queuedResponses.push( - { - statusCode: 200, - body: '{"foo2": "bar2"}', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - } - ); - - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - - await advanceTimersByTime(1000); - - expect(manager.responsePromises.length).toBe(2); - manager.stop(); - await manager.responsePromises[1]; - // Should not have updated datafile since manager was stopped - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - }); - - it('calls abort on the current request if there is a current request when stop is called', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo2": "bar2"}', - headers: {}, - }); - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); - manager.start(); - const currentRequest = makeGetRequestSpy.mock.results[0]; - expect(currentRequest.type).toBe('return'); - expect(currentRequest.value.abort).toBeCalledTimes(0); - manager.stop(); - expect(currentRequest.value.abort).toBeCalledTimes(1); - }); - - it('can fail to become ready on the initial request, but succeed after a later polling update', async () => { - manager.queuedResponses.push( - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }, - { - statusCode: 404, - body: '', - headers: {}, - } - ); - - manager.start(); - expect(manager.responsePromises.length).toBe(1); - await manager.responsePromises[0]; - // Not ready yet due to first request failed, but should have queued a live update - expect(getTimerCount()).toBe(1); - // Trigger the update, should fetch the next response which should succeed, then we get ready - advanceTimersByTime(1000); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - }); - - describe('newness checking', () => { - it('does not update if the response status is 304', async () => { - manager.queuedResponses.push( - { - statusCode: 304, - body: '', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: { - 'Last-Modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - } - ); - - const updateFn = jest.fn(); - manager.on('update', updateFn); - - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - // First response promise was for the initial 200 response - expect(manager.responsePromises.length).toBe(1); - // Trigger the queued update - await advanceTimersByTime(1000); - // Second response promise is for the 304 response - expect(manager.responsePromises.length).toBe(2); - await manager.responsePromises[1]; - // Since the response was 304, updateFn should not have been called - expect(updateFn).toBeCalledTimes(0); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - }); - - it('sends if-modified-since using the last observed response last-modified', async () => { - manager.queuedResponses.push( - { - statusCode: 304, - body: '', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: { - 'Last-Modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - } - ); - manager.start(); - await manager.onReady(); - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); - await advanceTimersByTime(1000); - expect(makeGetRequestSpy).toBeCalledTimes(1); - const firstCall = makeGetRequestSpy.mock.calls[0]; - const headers = firstCall[1]; - expect(headers).toEqual({ - 'if-modified-since': 'Fri, 08 Mar 2019 18:57:17 GMT', - }); - }); - }); - - describe('backoff', () => { - it('uses the delay from the backoff controller getDelay method when greater than updateInterval', async () => { - const BackoffControllerMock = (BackoffController as unknown) as jest.Mock<BackoffController, []>; - const getDelayMock = BackoffControllerMock.mock.results[0].value.getDelay; - getDelayMock.mockImplementationOnce(() => 5432); - - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); - - manager.queuedResponses.push({ - statusCode: 404, - body: '', - headers: {}, - }); - manager.start(); - await manager.responsePromises[0]; - expect(makeGetRequestSpy).toBeCalledTimes(1); - - // Should not make another request after 1 second because the error should have triggered backoff - advanceTimersByTime(1000); - expect(makeGetRequestSpy).toBeCalledTimes(1); - - // But after another 5 seconds, another request should be made - await advanceTimersByTime(5000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - }); - - it('calls countError on the backoff controller when a non-success status code response is received', async () => { - manager.queuedResponses.push({ - statusCode: 404, - body: '', - headers: {}, - }); - manager.start(); - await manager.responsePromises[0]; - const BackoffControllerMock = (BackoffController as unknown) as jest.Mock<BackoffController, []>; - expect(BackoffControllerMock.mock.results[0].value.countError).toBeCalledTimes(1); - }); - - it('calls countError on the backoff controller when the response promise rejects', async () => { - manager.queuedResponses.push(new Error('Connection failed')); - manager.start(); - try { - await manager.responsePromises[0]; - } catch (e) { - //empty - } - const BackoffControllerMock = (BackoffController as unknown) as jest.Mock<BackoffController, []>; - expect(BackoffControllerMock.mock.results[0].value.countError).toBeCalledTimes(1); - }); - - it('calls reset on the backoff controller when a success status code response is received', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: { - 'Last-Modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - }); - manager.start(); - const BackoffControllerMock = (BackoffController as unknown) as jest.Mock<BackoffController, []>; - // Reset is called in start - we want to check that it is also called after the response, so reset the mock here - BackoffControllerMock.mock.results[0].value.reset.mockReset(); - await manager.onReady(); - expect(BackoffControllerMock.mock.results[0].value.reset).toBeCalledTimes(1); - }); - - it('resets the backoff controller when start is called', async () => { - const BackoffControllerMock = (BackoffController as unknown) as jest.Mock<BackoffController, []>; - manager.start(); - expect(BackoffControllerMock.mock.results[0].value.reset).toBeCalledTimes(1); - try { - await manager.responsePromises[0]; - } catch (e) { - // empty - } - }); - }); - }); - }); - }); - - describe('when constructed with sdkKey and autoUpdate: false', () => { - beforeEach(() => { - manager = new TestDatafileManager({ sdkKey: '123', autoUpdate: false }); - }); - - it('after being started, fetches the datafile and resolves onReady', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - }); - - it('does not schedule a live update after ready', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - const updateFn = jest.fn(); - manager.on('update', updateFn); - manager.start(); - await manager.onReady(); - expect(getTimerCount()).toBe(0); - }); - - // TODO: figure out what's wrong with this test - it.skip('rejects the onReady promise if the initial request promise rejects', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.makeGetRequest = (): AbortableRequest => ({ - abort(): void {}, - responsePromise: Promise.reject(new Error('Could not connect')), - }); - manager.start(); - let didReject = false; - try { - await manager.onReady(); - } catch (e) { - didReject = true; - } - expect(didReject).toBe(true); - }); - }); - - describe('when constructed with sdkKey and a valid urlTemplate', () => { - beforeEach(() => { - manager = new TestDatafileManager({ - sdkKey: '456', - updateInterval: 1000, - urlTemplate: 'https://localhost:5556/datafiles/%s', - }); - }); - - it('uses the urlTemplate to create the url passed to the makeGetRequest method', async () => { - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy.mock.calls[0][0]).toBe('https://localhost:5556/datafiles/456'); - await manager.onReady(); - }); - }); - - describe('when constructed with an update interval below the minimum', () => { - beforeEach(() => { - manager = new TestDatafileManager({ sdkKey: '123', updateInterval: 500, autoUpdate: true }); - }); - - it('uses the default update interval', async () => { - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); - - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo3": "bar3"}', - headers: {}, - }); - - manager.start(); - await manager.onReady(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - await advanceTimersByTime(300000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - }); - }); - - describe('when constructed with a cache implementation having an already cached datafile', () => { - beforeEach(() => { - manager = new TestDatafileManager({ - sdkKey: 'keyThatExists', - updateInterval: 500, - autoUpdate: true, - cache: testCache, - }); - manager.simulateResponseDelay = true; - }); - - it('uses cached version of datafile first and resolves the promise while network throws error and no update event is triggered', async () => { - manager.queuedResponses.push(new Error('Connection Error')); - const updateFn = jest.fn(); - manager.on('update', updateFn); - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ name: 'keyThatExists' }); - await advanceTimersByTime(50); - expect(JSON.parse(manager.get())).toEqual({ name: 'keyThatExists' }); - expect(updateFn).toBeCalledTimes(0); - }); - - it('uses cached datafile, resolves ready promise, fetches new datafile from network and triggers update event', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - - const updateFn = jest.fn(); - manager.on('update', updateFn); - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ name: 'keyThatExists' }); - expect(updateFn).toBeCalledTimes(0); - await advanceTimersByTime(50); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - expect(updateFn).toBeCalledTimes(1); - }); - - it('sets newly recieved datafile in to cache', async () => { - const cacheSetSpy = jest.spyOn(testCache, 'set'); - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - await manager.onReady(); - await advanceTimersByTime(50); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - expect(cacheSetSpy.mock.calls[0][0]).toEqual('opt-datafile-keyThatExists'); - expect(JSON.parse(cacheSetSpy.mock.calls[0][1])).toEqual({ foo: 'bar' }); - }); - }); - - describe('when constructed with a cache implementation without an already cached datafile', () => { - beforeEach(() => { - manager = new TestDatafileManager({ - sdkKey: 'keyThatDoesExists', - updateInterval: 500, - autoUpdate: true, - cache: testCache, - }); - manager.simulateResponseDelay = true; - }); - - it('does not find cached datafile, fetches new datafile from network, resolves promise and does not trigger update event', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - - const updateFn = jest.fn(); - manager.on('update', updateFn); - manager.start(); - await advanceTimersByTime(50); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - expect(updateFn).toBeCalledTimes(0); - }); - }); -}); diff --git a/packages/datafile-manager/__test__/nodeDatafileManager.spec.ts b/packages/datafile-manager/__test__/nodeDatafileManager.spec.ts deleted file mode 100644 index 451e478f5..000000000 --- a/packages/datafile-manager/__test__/nodeDatafileManager.spec.ts +++ /dev/null @@ -1,186 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import NodeDatafileManager from '../src/nodeDatafileManager'; -import * as nodeRequest from '../src/nodeRequest'; -import { Headers, AbortableRequest } from '../src/http'; -import { advanceTimersByTime, getTimerCount } from './testUtils'; - -describe('nodeDatafileManager', () => { - let makeGetRequestSpy: jest.SpyInstance<AbortableRequest, [string, Headers]>; - beforeEach(() => { - jest.useFakeTimers(); - makeGetRequestSpy = jest.spyOn(nodeRequest, 'makeGetRequest'); - }); - - afterEach(() => { - jest.restoreAllMocks(); - jest.clearAllTimers(); - }); - - it('calls nodeEnvironment.makeGetRequest when started', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - - const manager = new NodeDatafileManager({ - sdkKey: '1234', - autoUpdate: false, - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy.mock.calls[0][0]).toBe('https://cdn.optimizely.com/datafiles/1234.json'); - expect(makeGetRequestSpy.mock.calls[0][1]).toEqual({}); - - await manager.onReady(); - await manager.stop(); - }); - - it('calls nodeEnvironment.makeGetRequest for live update requests', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'last-modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - autoUpdate: true, - }); - manager.start(); - await manager.onReady(); - await advanceTimersByTime(300000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - expect(makeGetRequestSpy.mock.calls[1][0]).toBe('https://cdn.optimizely.com/datafiles/1234.json'); - expect(makeGetRequestSpy.mock.calls[1][1]).toEqual({ - 'if-modified-since': 'Fri, 08 Mar 2019 18:57:17 GMT', - }); - - await manager.stop(); - }); - - it('defaults to true for autoUpdate', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'last-modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - }); - manager.start(); - await manager.onReady(); - // Should set a timeout for a later update - expect(getTimerCount()).toBe(1); - await advanceTimersByTime(300000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - - await manager.stop(); - }); - - it('uses authenticated default datafile url when auth token is provided', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - datafileAccessToken: 'abcdefgh', - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy).toBeCalledWith( - 'https://config.optimizely.com/datafiles/auth/1234.json', - expect.anything() - ); - await manager.stop(); - }); - - it('uses public default datafile url when auth token is not provided', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy).toBeCalledWith('https://cdn.optimizely.com/datafiles/1234.json', expect.anything()); - await manager.stop(); - }); - - it('adds authorization header with bearer token when auth token is provided', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - datafileAccessToken: 'abcdefgh', - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy).toBeCalledWith(expect.anything(), { Authorization: 'Bearer abcdefgh' }); - await manager.stop(); - }); - - it('prefers user provided url template over defaults', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - datafileAccessToken: 'abcdefgh', - urlTemplate: 'https://myawesomeurl/', - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy).toBeCalledWith('https://myawesomeurl/', expect.anything()); - await manager.stop(); - }); -}); diff --git a/packages/datafile-manager/__test__/nodeRequest.spec.ts b/packages/datafile-manager/__test__/nodeRequest.spec.ts deleted file mode 100644 index 075907113..000000000 --- a/packages/datafile-manager/__test__/nodeRequest.spec.ts +++ /dev/null @@ -1,216 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import nock from 'nock'; -import zlib from 'zlib'; -import { makeGetRequest } from '../src/nodeRequest'; -import { advanceTimersByTime } from './testUtils'; - -beforeAll(() => { - nock.disableNetConnect(); -}); - -afterAll(() => { - nock.enableNetConnect(); -}); - -describe('nodeEnvironment', () => { - const host = 'https://cdn.optimizely.com/'; - const path = '/datafiles/123.json'; - - afterEach(async () => { - nock.cleanAll(); - }); - - describe('makeGetRequest', () => { - it('returns a 200 response back to its superclass', async () => { - const scope = nock(host) - .get(path) - .reply(200, '{"foo":"bar"}'); - const req = makeGetRequest(`${host}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }); - scope.done(); - }); - - it('returns a 404 response back to its superclass', async () => { - const scope = nock(host) - .get(path) - .reply(404, ''); - const req = makeGetRequest(`${host}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 404, - body: '', - headers: {}, - }); - scope.done(); - }); - - it('includes headers from the headers argument in the request', async () => { - const scope = nock(host) - .matchHeader('if-modified-since', 'Fri, 08 Mar 2019 18:57:18 GMT') - .get(path) - .reply(304, ''); - const req = makeGetRequest(`${host}${path}`, { - 'if-modified-since': 'Fri, 08 Mar 2019 18:57:18 GMT', - }); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 304, - body: '', - headers: {}, - }); - scope.done(); - }); - - it('adds an Accept-Encoding request header and unzips a gzipped response body', async () => { - const scope = nock(host) - .matchHeader('accept-encoding', 'gzip,deflate') - .get(path) - .reply(200, () => zlib.gzipSync('{"foo":"bar"}'), { 'content-encoding': 'gzip' }); - const req = makeGetRequest(`${host}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toMatchObject({ - statusCode: 200, - body: '{"foo":"bar"}', - }); - scope.done(); - }); - - it('includes headers from the response in the eventual response in the return value', async () => { - const scope = nock(host) - .get(path) - .reply( - 200, - { foo: 'bar' }, - { - 'last-modified': 'Fri, 08 Mar 2019 18:57:18 GMT', - } - ); - const req = makeGetRequest(`${host}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'content-type': 'application/json', - 'last-modified': 'Fri, 08 Mar 2019 18:57:18 GMT', - }, - }); - scope.done(); - }); - - it('handles a URL with a query string', async () => { - const pathWithQuery = '/datafiles/123.json?from_my_app=true'; - const scope = nock(host) - .get(pathWithQuery) - .reply(200, { foo: 'bar' }); - const req = makeGetRequest(`${host}${pathWithQuery}`, {}); - await req.responsePromise; - scope.done(); - }); - - it('handles a URL with http protocol (not https)', async () => { - const httpHost = 'http://cdn.optimizely.com/'; - const scope = nock(httpHost) - .get(path) - .reply(200, '{"foo":"bar"}'); - const req = makeGetRequest(`${httpHost}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }); - scope.done(); - }); - - it('returns a rejected response promise when the URL protocol is unsupported', async () => { - const invalidProtocolUrl = 'ftp://something/datafiles/123.json'; - const req = makeGetRequest(invalidProtocolUrl, {}); - await expect(req.responsePromise).rejects.toThrow(); - }); - - it('returns a rejected promise when there is a request error', async () => { - const scope = nock(host) - .get(path) - .replyWithError({ - message: 'Connection error', - code: 'CONNECTION_ERROR', - }); - const req = makeGetRequest(`${host}${path}`, {}); - await expect(req.responsePromise).rejects.toThrow(); - scope.done(); - }); - - it('handles a url with a host and a port', async () => { - const hostWithPort = 'http://datafiles:3000/'; - const path = '/12/345.json'; - const scope = nock(hostWithPort) - .get(path) - .reply(200, '{"foo":"bar"}'); - const req = makeGetRequest(`${hostWithPort}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }); - scope.done(); - }); - - describe('timeout', () => { - beforeEach(() => { - jest.useFakeTimers(); - }); - - afterEach(() => { - jest.clearAllTimers(); - }); - - it('rejects the response promise and aborts the request when the response is not received before the timeout', async () => { - const scope = nock(host) - .get(path) - .delay(61000) - .reply(200, '{"foo":"bar"}'); - - const abortEventListener = jest.fn(); - let emittedReq: any; - const requestListener = (request: any): void => { - emittedReq = request; - emittedReq.once('abort', abortEventListener); - }; - scope.on('request', requestListener); - - const req = makeGetRequest(`${host}${path}`, {}); - await advanceTimersByTime(60000); - await expect(req.responsePromise).rejects.toThrow(); - expect(abortEventListener).toBeCalledTimes(1); - - scope.done(); - if (emittedReq) { - emittedReq.off('abort', abortEventListener); - } - scope.off('request', requestListener); - }); - }); - }); -}); diff --git a/packages/datafile-manager/__test__/reactNativeAsyncStorageCache.spec.ts b/packages/datafile-manager/__test__/reactNativeAsyncStorageCache.spec.ts deleted file mode 100644 index 0401a2065..000000000 --- a/packages/datafile-manager/__test__/reactNativeAsyncStorageCache.spec.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import ReactNativeAsyncStorageCache from '../src/reactNativeAsyncStorageCache'; - -describe('reactNativeAsyncStorageCache', () => { - let cacheInstance: ReactNativeAsyncStorageCache; - - beforeEach(() => { - cacheInstance = new ReactNativeAsyncStorageCache(); - }); - - describe('get', function() { - it('should return correct string when item is found in cache', function() { - return cacheInstance.get('keyThatExists').then(v => expect(JSON.parse(v)).toEqual({ name: 'Awesome Object' })); - }); - - it('should return empty string if item is not found in cache', function() { - return cacheInstance.get('keyThatDoesNotExist').then(v => expect(v).toEqual('')); - }); - }); - - describe('set', function() { - it('should resolve promise if item was successfully set in the cache', function() { - const testObj = { name: 'Awesome Object' }; - return cacheInstance.set('testKey', JSON.stringify(testObj)); - }); - }); - - describe('contains', function() { - it('should return true if value with key exists', function() { - return cacheInstance.contains('keyThatExists').then(v => expect(v).toBeTruthy()); - }); - - it('should return false if value with key does not exist', function() { - return cacheInstance.contains('keyThatDoesNotExist').then(v => expect(v).toBeFalsy()); - }); - }); -}); diff --git a/packages/datafile-manager/jest.config.js b/packages/datafile-manager/jest.config.js deleted file mode 100644 index e1a14d3fc..000000000 --- a/packages/datafile-manager/jest.config.js +++ /dev/null @@ -1,211 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -// For a detailed explanation regarding each configuration property, visit: -// https://jestjs.io/docs/en/configuration.html - -module.exports = { - // All imported modules in your tests should be mocked automatically - // automock: false, - - // Stop running tests after `n` failures - // bail: 0, - - // Respect "browser" field in package.json when resolving modules - // browser: false, - - // The directory where Jest should store its cached dependency information - // cacheDirectory: "/private/var/folders/rz/km3gywzs7h9c5wq67w1gy9l8dc02_d/T/jest_7f76tp", - - // Automatically clear mock calls and instances between every test - clearMocks: true, - - // Indicates whether the coverage information should be collected while executing the test - // collectCoverage: false, - - // An array of glob patterns indicating a set of files for which coverage information should be collected - // collectCoverageFrom: null, - - // The directory where Jest should output its coverage files - // coverageDirectory: null, - - // An array of regexp pattern strings used to skip coverage collection - // coveragePathIgnorePatterns: [ - // "/node_modules/" - // ], - - // A list of reporter names that Jest uses when writing coverage reports - // coverageReporters: [ - // "json", - // "text", - // "lcov", - // "clover" - // ], - - // An object that configures minimum threshold enforcement for coverage results - // coverageThreshold: null, - - // A path to a custom dependency extractor - // dependencyExtractor: null, - - // Make calling deprecated APIs throw helpful error messages - // errorOnDeprecated: false, - - // Force coverage collection from ignored files usin a array of glob patterns - // forceCoverageMatch: [], - - // A path to a module which exports an async function that is triggered once before all test suites - // globalSetup: null, - - // A path to a module which exports an async function that is triggered once after all test suites - // globalTeardown: null, - - // A set of global variables that need to be available in all test environments - // globals: {}, - - // An array of directory names to be searched recursively up from the requiring module's location - // moduleDirectories: [ - // "node_modules" - // ], - - // An array of file extensions your modules use - // moduleFileExtensions: [ - // "js", - // "json", - // "jsx", - // "ts", - // "tsx", - // "node" - // ], - "moduleFileExtensions": [ - "ts", - "js", - "json", - "node" - ], - - // A map from regular expressions to module names that allow to stub out resources with a single module - // moduleNameMapper: {}, - - // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader - // modulePathIgnorePatterns: [], - - // Activates notifications for test results - // notify: false, - - // An enum that specifies notification mode. Requires { notify: true } - // notifyMode: "failure-change", - - // A preset that is used as a base for Jest's configuration - // preset: null, - - // Run tests from one or more projects - // projects: null, - - // Use this configuration option to add custom reporters to Jest - // reporters: undefined, - - // Automatically reset mock state between every test - // resetMocks: false, - - // Reset the module registry before running each individual test - // resetModules: false, - - // A path to a custom resolver - // resolver: null, - - // Automatically restore mock state between every test - // restoreMocks: false, - - // The root directory that Jest should scan for tests and modules within - // rootDir: null, - - // A list of paths to directories that Jest should use to search for files in - // roots: [ - // "<rootDir>" - // ], - - // Allows you to use a custom runner instead of Jest's default test runner - // runner: "jest-runner", - - // The paths to modules that run some code to configure or set up the testing environment before each test - // setupFiles: [], - - // A list of paths to modules that run some code to configure or set up the testing framework before each test - // setupFilesAfterEnv: [], - - // A list of paths to snapshot serializer modules Jest should use for snapshot testing - // snapshotSerializers: [], - - // The test environment that will be used for testing - testEnvironment: "node", - - // Options that will be passed to the testEnvironment - // testEnvironmentOptions: {}, - - // Adds a location field to test results - // testLocationInResults: false, - - // The glob patterns Jest uses to detect test files - // testMatch: [ - // "**/__tests__/**/*.[jt]s?(x)", - // "**/?(*.)+(spec|test).[tj]s?(x)" - // ], - - // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped - // testPathIgnorePatterns: [ - // "/node_modules/" - // ], - - // The regexp pattern or array of patterns that Jest uses to detect test files - // testRegex: [], - "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.ts$", - - // This option allows the use of a custom results processor - // testResultsProcessor: null, - - // This option allows use of a custom test runner - // testRunner: "jasmine2", - - // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href - // testURL: "http://localhost/", - - // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" - // timers: "real", - - // A map from regular expressions to paths to transformers - // transform: null, - "transform": { - "^.+\\.ts$": "ts-jest" - }, - - // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation - // transformIgnorePatterns: [ - // "/node_modules/" - // ], - - // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them - // unmockedModulePathPatterns: undefined, - - // Indicates whether each individual test should be reported during the run - // verbose: null, - - // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode - // watchPathIgnorePatterns: [], - - // Whether to use watchman for file crawling - // watchman: true, -}; diff --git a/packages/datafile-manager/package-lock.json b/packages/datafile-manager/package-lock.json deleted file mode 100644 index f957b9f3c..000000000 --- a/packages/datafile-manager/package-lock.json +++ /dev/null @@ -1,5355 +0,0 @@ -{ - "name": "@optimizely/js-sdk-datafile-manager", - "version": "0.9.5", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.12.13" - } - }, - "@babel/compat-data": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.0.tgz", - "integrity": "sha512-vu9V3uMM/1o5Hl5OekMUowo3FqXLJSw+s+66nt0fSWVWTtmosdzn45JHOB3cPtZoe6CTBDzvSw0RdOY85Q37+Q==", - "dev": true - }, - "@babel/core": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.3.tgz", - "integrity": "sha512-jB5AmTKOCSJIZ72sd78ECEhuPiDMKlQdDI/4QRI6lzYATx5SSogS1oQA2AoPecRCknm30gHi2l+QVvNUu3wZAg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.3", - "@babel/helper-compilation-targets": "^7.13.16", - "@babel/helper-module-transforms": "^7.14.2", - "@babel/helpers": "^7.14.0", - "@babel/parser": "^7.14.3", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.2", - "@babel/types": "^7.14.2", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "@babel/generator": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.3.tgz", - "integrity": "sha512-bn0S6flG/j0xtQdz3hsjJ624h3W0r3llttBMfyHX3YrZ/KtLYr15bjA0FXkgW7FpvrDuTuElXeVjiKlYRpnOFA==", - "dev": true, - "requires": { - "@babel/types": "^7.14.2", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "@babel/helper-compilation-targets": { - "version": "7.13.16", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.16.tgz", - "integrity": "sha512-3gmkYIrpqsLlieFwjkGgLaSHmhnvlAYzZLlYVjlW+QwI+1zE17kGxuJGmIqDQdYp56XdmGeD+Bswx0UTyG18xA==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.13.15", - "@babel/helper-validator-option": "^7.12.17", - "browserslist": "^4.14.5", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "@babel/helper-function-name": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz", - "integrity": "sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.14.2" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", - "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", - "dev": true, - "requires": { - "@babel/types": "^7.13.12" - } - }, - "@babel/helper-module-imports": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", - "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", - "dev": true, - "requires": { - "@babel/types": "^7.13.12" - } - }, - "@babel/helper-module-transforms": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.2.tgz", - "integrity": "sha512-OznJUda/soKXv0XhpvzGWDnml4Qnwp16GN+D/kZIdLsWoHj05kyu8Rm5kXmMef+rVJZ0+4pSGLkeixdqNUATDA==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.13.12", - "@babel/helper-replace-supers": "^7.13.12", - "@babel/helper-simple-access": "^7.13.12", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/helper-validator-identifier": "^7.14.0", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.2", - "@babel/types": "^7.14.2" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.13.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", - "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", - "dev": true - }, - "@babel/helper-replace-supers": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.3.tgz", - "integrity": "sha512-Rlh8qEWZSTfdz+tgNV/N4gz1a0TMNwCUcENhMjHTHKp3LseYH5Jha0NSlyTQWMnjbYcwFt+bqAMqSLHVXkQ6UA==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.13.12", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.14.2", - "@babel/types": "^7.14.2" - } - }, - "@babel/helper-simple-access": { - "version": "7.13.12", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz", - "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==", - "dev": true, - "requires": { - "@babel/types": "^7.13.12" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", - "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==", - "dev": true - }, - "@babel/helpers": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.0.tgz", - "integrity": "sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg==", - "dev": true, - "requires": { - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.14.0" - } - }, - "@babel/highlight": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", - "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.0", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@babel/parser": { - "version": "7.14.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.3.tgz", - "integrity": "sha512-7MpZDIfI7sUC5zWo2+foJ50CSI5lcqDehZ0lVgIhSi4bFEk94fLAKlF3Q0nzSQQ+ca0lm+O6G9ztKVBeu8PMRQ==", - "dev": true - }, - "@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, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@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==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@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==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@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==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@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==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-top-level-await": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz", - "integrity": "sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.12.13.tgz", - "integrity": "sha512-cHP3u1JiUiG2LFDKbXnwVad81GvfyIOmCD6HIEId6ojrY0Drfy2q1jw7BwN7dE84+kTnBjLkXoL3IEy/3JPu2w==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "@babel/traverse": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz", - "integrity": "sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.14.2", - "@babel/helper-function-name": "^7.14.2", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.14.2", - "@babel/types": "^7.14.2", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "dependencies": { - "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 - } - } - }, - "@babel/types": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.2.tgz", - "integrity": "sha512-SdjAG/3DikRHpUOjxZgnkbR11xUlyDMUFJdvnIgZEE16mqmY0BINMmc4//JMJglEmn6i7sq6p+mGrFWyZ98EEw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.14.0", - "to-fast-properties": "^2.0.0" - } - }, - "@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 - }, - "@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==", - "dev": true, - "requires": { - "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" - }, - "dependencies": { - "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 - } - } - }, - "@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 - }, - "@jest/console": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-27.0.1.tgz", - "integrity": "sha512-50E6nN2F5cAXn1lDljn0gE9F0WFXHYz/u0EeR7sOt4nbRPNli34ckbl6CUDaDABJbHt62DYnyQAIB3KgdzwKDw==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^27.0.1", - "jest-util": "^27.0.1", - "slash": "^3.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "@jest/core": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-27.0.1.tgz", - "integrity": "sha512-PiCbKSMf6t8PEfY3MAd0Ldn3aJAt5T+UcaFkAfMZ1VZgas35+fXk5uHIjAQHQLNIHZWX19TLv0wWNT03yvrw6w==", - "dev": true, - "requires": { - "@jest/console": "^27.0.1", - "@jest/reporters": "^27.0.1", - "@jest/test-result": "^27.0.1", - "@jest/transform": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^27.0.1", - "jest-config": "^27.0.1", - "jest-haste-map": "^27.0.1", - "jest-message-util": "^27.0.1", - "jest-regex-util": "^27.0.1", - "jest-resolve": "^27.0.1", - "jest-resolve-dependencies": "^27.0.1", - "jest-runner": "^27.0.1", - "jest-runtime": "^27.0.1", - "jest-snapshot": "^27.0.1", - "jest-util": "^27.0.1", - "jest-validate": "^27.0.1", - "jest-watcher": "^27.0.1", - "micromatch": "^4.0.4", - "p-each-series": "^2.1.0", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "@jest/environment": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-27.0.1.tgz", - "integrity": "sha512-nG+r3uSs2pOTsdhgt6lUm4ZGJLRcTc6HZIkrFsVpPcdSqEpJehEny9r9y2Bmhkn8fKXWdGCYJKF3i4nKO0HSmA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/node": "*", - "jest-mock": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "@jest/fake-timers": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.0.1.tgz", - "integrity": "sha512-3CyLJQnHzKI4TCJSCo+I9TzIHjSK4RrNEk93jFM6Q9+9WlSJ3mpMq/p2YuKMe0SiHKbmZOd5G/Ll5ofF9Xkw9g==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "@sinonjs/fake-timers": "^7.0.2", - "@types/node": "*", - "jest-message-util": "^27.0.1", - "jest-mock": "^27.0.1", - "jest-util": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "@jest/globals": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-27.0.1.tgz", - "integrity": "sha512-80ZCzgopysKdpp5EOglgjApKxiNDR96PG4PwngB4fTwZ4qqqSKo0EwGwQIhl16szQ1M2xCVYmr9J6KelvnABNQ==", - "dev": true, - "requires": { - "@jest/environment": "^27.0.1", - "@jest/types": "^27.0.1", - "expect": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "@jest/reporters": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-27.0.1.tgz", - "integrity": "sha512-lZbJWuS1h/ytKERfu1D6tEQ4PuQ7+15S4+HrSzHR0i7AGVT1WRo49h4fZqxASOp7AQCupUVtPJNZDkaG9ZXy0g==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^27.0.1", - "@jest/test-result": "^27.0.1", - "@jest/transform": "^27.0.1", - "@jest/types": "^27.0.1", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.3", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^27.0.1", - "jest-resolve": "^27.0.1", - "jest-util": "^27.0.1", - "jest-worker": "^27.0.1", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^7.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "@jest/source-map": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-27.0.1.tgz", - "integrity": "sha512-yMgkF0f+6WJtDMdDYNavmqvbHtiSpwRN2U/W+6uztgfqgkq/PXdKPqjBTUF1RD/feth4rH5N3NW0T5+wIuln1A==", - "dev": true, - "requires": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" - } - }, - "@jest/test-result": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-27.0.1.tgz", - "integrity": "sha512-5aa+ibX2dsGSDLKaQMZb453MqjJU/CRVumebXfaJmuzuGE4qf87yQ2QZ6PEpEtBwVUEgrJCzi3jLCRaUbksSuw==", - "dev": true, - "requires": { - "@jest/console": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "@jest/test-sequencer": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.0.1.tgz", - "integrity": "sha512-yK2c2iruJ35WgH4KH8whS72uH+FASJUrzwxzNKTzLAEWmNpWKNEPOsSEKsHynvz78bLHafrTg4adN7RrYNbEOA==", - "dev": true, - "requires": { - "@jest/test-result": "^27.0.1", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.0.1", - "jest-runner": "^27.0.1", - "jest-runtime": "^27.0.1" - } - }, - "@jest/transform": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-27.0.1.tgz", - "integrity": "sha512-LC95VpT6wMnQ96dRJDlUiAnW/90zyh4+jS30szI/5AsfS0qwSlr/O4TPcGoD2WVaVMfo6KvR+brvOtGyMHaNhA==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/types": "^27.0.1", - "babel-plugin-istanbul": "^6.0.0", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.0.1", - "jest-regex-util": "^27.0.1", - "jest-util": "^27.0.1", - "micromatch": "^4.0.4", - "pirates": "^4.0.1", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@optimizely/js-sdk-logging": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-logging/-/js-sdk-logging-0.3.1.tgz", - "integrity": "sha512-K71Jf283FP0E4oXehcXTTM3gvgHZHr7FUrIsw//0mdJlotHJT4Nss4hE0CWPbBxO7LJAtwNnO+VIA/YOcO4vHg==", - "requires": { - "@optimizely/js-sdk-utils": "^0.4.0" - } - }, - "@optimizely/js-sdk-utils": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-utils/-/js-sdk-utils-0.4.0.tgz", - "integrity": "sha512-QG2oytnITW+VKTJK+l0RxjaS5VrA6W+AZMzpeg4LCB4Rn4BEKtF+EcW/5S1fBDLAviGq/0TLpkjM3DlFkJ9/Gw==", - "requires": { - "uuid": "^3.3.2" - } - }, - "@react-native-async-storage/async-storage": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.15.4.tgz", - "integrity": "sha512-pC0MS6UBuv/YiVAxtzi7CgUed8oCQNYMtGt0yb/I9fI/BWTiJK5cj4YtW2XtL95K5IuvPX/6uGWaouZ8KqXwdg==", - "dev": true, - "requires": { - "deep-assign": "^3.0.0" - } - }, - "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-7.1.1.tgz", - "integrity": "sha512-am34LJf0N2nON/PT9G7pauA+xjcwX9P6x31m4hBgfUeSXYRZBRv/R6EcdWs8iV4XJjPO++NTsrj7ua/cN2s6ZA==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@sinonjs/formatio": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", - "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" - } - }, - "@sinonjs/samsam": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", - "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.3.0", - "array-from": "^2.1.1", - "lodash": "^4.17.15" - } - }, - "@sinonjs/text-encoding": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", - "dev": true - }, - "@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true - }, - "@types/babel__core": { - "version": "7.1.14", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.14.tgz", - "integrity": "sha512-zGZJzzBUVDo/eV6KgbE0f0ZI7dInEYvo12Rb70uNQDshC3SkRMb67ja0GgRHZgAX3Za6rhaWlvbDO8rrGyAb1g==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@types/babel__generator": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.2.tgz", - "integrity": "sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ==", - "dev": true, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@types/babel__template": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.0.tgz", - "integrity": "sha512-NTPErx4/FiPCGScH7foPyr+/1Dkzkni+rHiYHHoTjvwou7AQzJkNeD60A9CXRy+ZEN2B1bggmkTMCDb+Mv5k+A==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.11.1", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.11.1.tgz", - "integrity": "sha512-Vs0hm0vPahPMYi9tDjtP66llufgO3ST16WXaSTtDGEl9cewAl3AibmxWw6TINOqHPT9z0uABKAYjT9jNSg4npw==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==", - "dev": true - }, - "@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "26.0.23", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.23.tgz", - "integrity": "sha512-ZHLmWMJ9jJ9PTiT58juykZpL7KjwJywFN3Rr2pTSkyQfydf/rk22yS7W8p5DaVUMQ2BQC7oYiU3FjbTM/mYrOA==", - "dev": true, - "requires": { - "jest-diff": "^26.0.0", - "pretty-format": "^26.0.0" - } - }, - "@types/json-schema": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", - "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", - "dev": true - }, - "@types/nise": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@types/nise/-/nise-1.4.0.tgz", - "integrity": "sha512-DPxmjiDwubsNmguG5X4fEJ+XCyzWM3GXWsqQlvUcjJKa91IOoJUy51meDr0GkzK64qqNcq85ymLlyjoct9tInw==", - "dev": true - }, - "@types/nock": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/@types/nock/-/nock-9.3.1.tgz", - "integrity": "sha512-eOVHXS5RnWOjTVhu3deCM/ruy9E6JCgeix2g7wpFiekQh3AaEAK1cz43tZDukKmtSmQnwvSySq7ubijCA32I7Q==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/node": { - "version": "11.15.54", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.15.54.tgz", - "integrity": "sha512-1RWYiq+5UfozGsU6MwJyFX6BtktcT10XRjvcAQmskCtMcW3tPske88lM/nHv7BQG1w9KBXI1zPGuu5PnNCX14g==", - "dev": true - }, - "@types/prettier": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.2.3.tgz", - "integrity": "sha512-PijRCG/K3s3w1We6ynUKdxEc5AcuuH3NBmMDP8uvKVp6X43UY7NQlTzczakXP3DJR0F4dfNQIGjU2cUeRYs2AA==", - "dev": true - }, - "@types/stack-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", - "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", - "dev": true - }, - "@types/yargs": { - "version": "15.0.13", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", - "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "20.2.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz", - "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==", - "dev": true - }, - "@typescript-eslint/eslint-plugin": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.10.1.tgz", - "integrity": "sha512-PQg0emRtzZFWq6PxBcdxRH3QIQiyFO3WCVpRL3fgj5oQS3CDs3AeAKfv4DxNhzn8ITdNJGJ4D3Qw8eAJf3lXeQ==", - "dev": true, - "requires": { - "@typescript-eslint/experimental-utils": "3.10.1", - "debug": "^4.1.1", - "functional-red-black-tree": "^1.0.1", - "regexpp": "^3.0.0", - "semver": "^7.3.2", - "tsutils": "^3.17.1" - } - }, - "@typescript-eslint/experimental-utils": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-3.10.1.tgz", - "integrity": "sha512-DewqIgscDzmAfd5nOGe4zm6Bl7PKtMG2Ad0KG8CUZAHlXfAKTF9Ol5PXhiMh39yRL2ChRH1cuuUGOcVyyrhQIw==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.3", - "@typescript-eslint/types": "3.10.1", - "@typescript-eslint/typescript-estree": "3.10.1", - "eslint-scope": "^5.0.0", - "eslint-utils": "^2.0.0" - } - }, - "@typescript-eslint/parser": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-3.10.1.tgz", - "integrity": "sha512-Ug1RcWcrJP02hmtaXVS3axPPTTPnZjupqhgj+NnZ6BCkwSImWk/283347+x9wN+lqOdK9Eo3vsyiyDHgsmiEJw==", - "dev": true, - "requires": { - "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/experimental-utils": "3.10.1", - "@typescript-eslint/types": "3.10.1", - "@typescript-eslint/typescript-estree": "3.10.1", - "eslint-visitor-keys": "^1.1.0" - } - }, - "@typescript-eslint/types": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-3.10.1.tgz", - "integrity": "sha512-+3+FCUJIahE9q0lDi1WleYzjCwJs5hIsbugIgnbB+dSCYUxl8L6PwmsyOPFZde2hc1DlTo/xnkOgiTLSyAbHiQ==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-3.10.1.tgz", - "integrity": "sha512-QbcXOuq6WYvnB3XPsZpIwztBoquEYLXh2MtwVU+kO8jgYCiv4G5xrSP/1wg4tkvrEE+esZVquIPX/dxPlePk1w==", - "dev": true, - "requires": { - "@typescript-eslint/types": "3.10.1", - "@typescript-eslint/visitor-keys": "3.10.1", - "debug": "^4.1.1", - "glob": "^7.1.6", - "is-glob": "^4.0.1", - "lodash": "^4.17.15", - "semver": "^7.3.2", - "tsutils": "^3.17.1" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-3.10.1.tgz", - "integrity": "sha512-9JgC82AaQeglebjZMgYR5wgmfUdUc+EitGUUMW8u2nDckaeimzW+VsoLV6FoimPv2id3VQzfjwBxEMVz08ameQ==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", - "dev": true - }, - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - }, - "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "acorn-jsx": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "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==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - }, - "dependencies": { - "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==", - "dev": true - } - } - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", - "dev": true - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "babel-jest": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-27.0.1.tgz", - "integrity": "sha512-aWFD7OGQjk3Y8MdZKf1XePlQvHnjMVJQjIq9WKrlAjz9by703kJ45Jxhp26JwnovoW71YYz5etuqRl8wMcIv0w==", - "dev": true, - "requires": { - "@jest/transform": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^27.0.1", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "slash": "^3.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "babel-plugin-istanbul": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", - "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^4.0.0", - "test-exclude": "^6.0.0" - } - }, - "babel-plugin-jest-hoist": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.0.1.tgz", - "integrity": "sha512-sqBF0owAcCDBVEDtxqfYr2F36eSHdx7lAVGyYuOBRnKdD6gzcy0I0XrAYCZgOA3CRrLhmR+Uae9nogPzmAtOfQ==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - } - }, - "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, - "requires": { - "@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" - } - }, - "babel-preset-jest": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.0.1.tgz", - "integrity": "sha512-nIBIqCEpuiyhvjQs2mVNwTxQQa2xk70p9Dd/0obQGBf8FBzbnI8QhQKzLsWMN2i6q+5B0OcWDtrboBX5gmOLyA==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^27.0.1", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "browserslist": { - "version": "4.16.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", - "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001219", - "colorette": "^1.2.2", - "electron-to-chromium": "^1.3.723", - "escalade": "^3.1.1", - "node-releases": "^1.1.71" - } - }, - "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, - "requires": { - "fast-json-stable-stringify": "2.x" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30001230", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001230.tgz", - "integrity": "sha512-5yBd5nWCBS+jWKTcHOzXwo5xzcj4ePE/yjtkZyUV1BTUmrBaA9MRGC+e7mxnqXSA90CmCA8L3eKLaSUkt099IQ==", - "dev": true - }, - "chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - } - }, - "chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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 - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true - }, - "ci-info": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz", - "integrity": "sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==", - "dev": true - }, - "cjs-module-lexer": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.1.tgz", - "integrity": "sha512-jVamGdJPDeuQilKhvVn1h3knuMOZzr8QDnpk+M9aMlCaMkTDd6fBWPhiDqFvFZ07pL0liqabAiuy8SY4jGHeaw==", - "dev": true - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "dev": true - }, - "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, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "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 - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "colorette": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", - "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - } - } - }, - "data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "requires": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - } - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "decimal.js": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", - "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==", - "dev": true - }, - "decompress-response": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", - "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", - "requires": { - "mimic-response": "^2.0.0" - } - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", - "dev": true - }, - "deep-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/deep-assign/-/deep-assign-3.0.0.tgz", - "integrity": "sha512-YX2i9XjJ7h5q/aQ/IM9PEwEnDqETAIYbggmdDB3HLTlSgo1CxPsj6pvhPG68rq6SVE0+p+6Ywsm5fTYNrYtBWw==", - "dev": true, - "requires": { - "is-obj": "^1.0.0" - } - }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, - "deep-equal": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", - "integrity": "sha512-yd9c5AdiqVcR+JjcwUQb9DkhJc8ngNr0MahEBGvDiJw8puWab2yZlh+nkasOnZP+EGTAP6rRp2JzJhJZzvNF8g==", - "dev": true, - "requires": { - "is-arguments": "^1.0.4", - "is-date-object": "^1.0.1", - "is-regex": "^1.0.4", - "object-is": "^1.0.1", - "object-keys": "^1.1.1", - "regexp.prototype.flags": "^1.2.0" - } - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "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==", - "dev": true - }, - "diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", - "dev": true - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "requires": { - "webidl-conversions": "^5.0.0" - }, - "dependencies": { - "webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true - } - } - }, - "electron-to-chromium": { - "version": "1.3.740", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.740.tgz", - "integrity": "sha512-Mi2m55JrX2BFbNZGKYR+2ItcGnR4O5HhrvgoRRyZQlaMGQULqDhoGkLWHzJoshSzi7k1PUofxcDbNhlFrDZNhg==", - "dev": true - }, - "emittery": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", - "integrity": "sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==", - "dev": true - }, - "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 - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "eslint": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-6.8.0.tgz", - "integrity": "sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "ajv": "^6.10.0", - "chalk": "^2.1.0", - "cross-spawn": "^6.0.5", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^1.4.3", - "eslint-visitor-keys": "^1.1.0", - "espree": "^6.1.2", - "esquery": "^1.0.1", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "inquirer": "^7.0.0", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.14", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.3", - "progress": "^2.0.0", - "regexpp": "^2.0.1", - "semver": "^6.1.2", - "strip-ansi": "^5.2.0", - "strip-json-comments": "^3.0.1", - "table": "^5.2.3", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "eslint-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz", - "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "regexpp": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", - "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "eslint-config-prettier": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz", - "integrity": "sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==", - "dev": true, - "requires": { - "get-stdin": "^6.0.0" - } - }, - "eslint-plugin-prettier": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.0.tgz", - "integrity": "sha512-UDK6rJT6INSfcOo545jiaOwB701uAIt2/dR7WnFQoGCVl1/EMqdANBmwUaqqQ45aXprsTGzSa39LI1PyuRBxxw==", - "dev": true, - "requires": { - "prettier-linter-helpers": "^1.0.0" - } - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^1.1.0" - } - }, - "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 - }, - "espree": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-6.2.1.tgz", - "integrity": "sha512-ysCxRQY3WaXJz9tdbWOwuWr5Y/XrPTGX9Kiz3yoUXwW0VZ4w30HTkQLaGx/+ttFjF8i+ACbArnB4ce68a9m5hw==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.1.0" - } - }, - "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 - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "execa": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", - "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "expect": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.0.1.tgz", - "integrity": "sha512-hjKwLeAvKUiq0Plha1dmzOH1FGEwJC9njbT993cq4PK9r58/+3NM+WDqFVGcPuRH7XTjmbIeHQBzp2faDrPhjQ==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "ansi-styles": "^5.0.0", - "jest-get-type": "^27.0.1", - "jest-matcher-utils": "^27.0.1", - "jest-message-util": "^27.0.1", - "jest-regex-util": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "jest-get-type": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.1.tgz", - "integrity": "sha512-9Tggo9zZbu0sHKebiAijyt1NM77Z0uO4tuWOxUCujAiSeXv30Vb5D4xVF4UR4YWNapcftj+PbByU54lKD7/xMg==", - "dev": true - } - } - }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", - "dev": true, - "requires": { - "flat-cache": "^2.0.1" - } - }, - "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, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", - "dev": true, - "requires": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - } - }, - "flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", - "dev": true - }, - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "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 - }, - "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 - }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "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 - }, - "get-stdin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", - "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", - "dev": true - }, - "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 - }, - "glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "requires": { - "type-fest": "^0.8.1" - } - }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "dev": true - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "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 - }, - "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", - "dev": true - }, - "html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "requires": { - "whatwg-encoding": "^1.0.5" - } - }, - "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 - }, - "http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "requires": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "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 - }, - "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, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true - }, - "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, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "import-local": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", - "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.19", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "is-arguments": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", - "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", - "dev": true, - "requires": { - "call-bind": "^1.0.0" - } - }, - "is-ci": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz", - "integrity": "sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ==", - "dev": true, - "requires": { - "ci-info": "^3.1.1" - } - }, - "is-core-module": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", - "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", - "dev": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-date-object": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.4.tgz", - "integrity": "sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A==", - "dev": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "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 - }, - "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==", - "dev": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "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 - }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true - }, - "is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "is-regex": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.3.tgz", - "integrity": "sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-symbols": "^1.0.2" - } - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "dev": true - }, - "istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "requires": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "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, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - } - }, - "istanbul-lib-source-maps": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", - "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", - "dev": true, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - } - }, - "istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", - "dev": true, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest/-/jest-27.0.1.tgz", - "integrity": "sha512-lFEoUdXjbGAIxk/gZhcv98xOaH1hjqG5R/PQHs5GBfIK5iL3tnXCjHQf4HQLVZZ2rcXML3oeVg9+XrRZbooBdQ==", - "dev": true, - "requires": { - "@jest/core": "^27.0.1", - "import-local": "^3.0.2", - "jest-cli": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "jest-cli": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-27.0.1.tgz", - "integrity": "sha512-plDsQQwpkKK1SZ5L5xqMa7v/sTwB5LTIeSJqb+cV+4EMlThdUQfg8jwMfHX8jHuUc9TPGLcdoZeBuZcGGn3Rlg==", - "dev": true, - "requires": { - "@jest/core": "^27.0.1", - "@jest/test-result": "^27.0.1", - "@jest/types": "^27.0.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "jest-config": "^27.0.1", - "jest-util": "^27.0.1", - "jest-validate": "^27.0.1", - "prompts": "^2.0.1", - "yargs": "^16.0.3" - } - } - } - }, - "jest-changed-files": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.0.1.tgz", - "integrity": "sha512-Y/4AnqYNcUX/vVgfkmvSA3t7rcg+t8m3CsSGlU+ra8kjlVW5ZqXcBZY/NUew2Mo8M+dn0ApKl+FmGGT1JV5dVA==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "execa": "^5.0.0", - "throat": "^6.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "jest-circus": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-27.0.1.tgz", - "integrity": "sha512-Tz3ytmrsgxWlTwSyPYb8StF9J2IMjLlbBMKAjhL2UU9/0ZpYb2JiEGjXaAhnGauQRbbpyFbSH3yj5HIbdurmwQ==", - "dev": true, - "requires": { - "@jest/environment": "^27.0.1", - "@jest/test-result": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "expect": "^27.0.1", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.0.1", - "jest-matcher-utils": "^27.0.1", - "jest-message-util": "^27.0.1", - "jest-runner": "^27.0.1", - "jest-runtime": "^27.0.1", - "jest-snapshot": "^27.0.1", - "jest-util": "^27.0.1", - "pretty-format": "^27.0.1", - "stack-utils": "^2.0.3", - "throat": "^6.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "pretty-format": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.1.tgz", - "integrity": "sha512-qE+0J6c/gd+R6XTcQgPJMc5hMJNsxzSF5p8iZSbMZ7GQzYGlSLNkh2P80Wa2dbF4gEVUsJEgcrBY+1L2/j265w==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - } - } - } - }, - "jest-config": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-27.0.1.tgz", - "integrity": "sha512-V8O6+CZjGF0OMq4kxVR29ztV/LQqlAAcJLw7a94RndfRXkha4U84n50yZCXiPWtAHHTmb3g1y52US6rGPxA+3w==", - "dev": true, - "requires": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^27.0.1", - "@jest/types": "^27.0.1", - "babel-jest": "^27.0.1", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "is-ci": "^3.0.0", - "jest-circus": "^27.0.1", - "jest-environment-jsdom": "^27.0.1", - "jest-environment-node": "^27.0.1", - "jest-get-type": "^27.0.1", - "jest-jasmine2": "^27.0.1", - "jest-regex-util": "^27.0.1", - "jest-resolve": "^27.0.1", - "jest-util": "^27.0.1", - "jest-validate": "^27.0.1", - "micromatch": "^4.0.4", - "pretty-format": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "jest-get-type": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.1.tgz", - "integrity": "sha512-9Tggo9zZbu0sHKebiAijyt1NM77Z0uO4tuWOxUCujAiSeXv30Vb5D4xVF4UR4YWNapcftj+PbByU54lKD7/xMg==", - "dev": true - }, - "pretty-format": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.1.tgz", - "integrity": "sha512-qE+0J6c/gd+R6XTcQgPJMc5hMJNsxzSF5p8iZSbMZ7GQzYGlSLNkh2P80Wa2dbF4gEVUsJEgcrBY+1L2/j265w==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - } - } - } - }, - "jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - } - }, - "jest-docblock": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.0.1.tgz", - "integrity": "sha512-TA4+21s3oebURc7VgFV4r7ltdIJ5rtBH1E3Tbovcg7AV+oLfD5DcJ2V2vJ5zFA9sL5CFd/d2D6IpsAeSheEdrA==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-27.0.1.tgz", - "integrity": "sha512-uJTK/aZ05HsdKkfXucAT5+/1DIURnTRv34OSxn1HWHrD+xu9eDX5Xgds09QSvg/mU01VS5upuHTDKG3W+r0rQA==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "chalk": "^4.0.0", - "jest-get-type": "^27.0.1", - "jest-util": "^27.0.1", - "pretty-format": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "jest-get-type": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.1.tgz", - "integrity": "sha512-9Tggo9zZbu0sHKebiAijyt1NM77Z0uO4tuWOxUCujAiSeXv30Vb5D4xVF4UR4YWNapcftj+PbByU54lKD7/xMg==", - "dev": true - }, - "pretty-format": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.1.tgz", - "integrity": "sha512-qE+0J6c/gd+R6XTcQgPJMc5hMJNsxzSF5p8iZSbMZ7GQzYGlSLNkh2P80Wa2dbF4gEVUsJEgcrBY+1L2/j265w==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - } - } - } - }, - "jest-environment-jsdom": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.0.1.tgz", - "integrity": "sha512-lesU8T9zkjgLaLpUFmFDgchu6/2OCoXm52nN6UumR063Hb+1TJdI7ihgM86+G01Ay86Lyr+K/FAR6yIIOviH3Q==", - "dev": true, - "requires": { - "@jest/environment": "^27.0.1", - "@jest/fake-timers": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/node": "*", - "jest-mock": "^27.0.1", - "jest-util": "^27.0.1", - "jsdom": "^16.6.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "jest-environment-node": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.0.1.tgz", - "integrity": "sha512-/p94lo0hx+hbKUw1opnRFUPPsjncRBEUU+2Dh7BuxX8Nr4rRiTivLYgXzo79FhaeMYV0uiV5WAbHBq6xC11JJg==", - "dev": true, - "requires": { - "@jest/environment": "^27.0.1", - "@jest/fake-timers": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/node": "*", - "jest-mock": "^27.0.1", - "jest-util": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true - }, - "jest-haste-map": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.0.1.tgz", - "integrity": "sha512-ioCuobr4z90H1Pz8+apz2vfz63387apzAoawm/9IIOndarDfRkjLURdLOe//AI5jUQmjVRg+WiL92339kqlCmA==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^27.0.1", - "jest-serializer": "^27.0.1", - "jest-util": "^27.0.1", - "jest-worker": "^27.0.1", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "jest-jasmine2": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.0.1.tgz", - "integrity": "sha512-o8Ist0o970QDDm/R2o9UDbvNxq8A0++FTFQ0z9OnieJwS1nDH6H7WBDYAGPTdmnla7kbW41oLFPvhmjJE4mekg==", - "dev": true, - "requires": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^27.0.1", - "@jest/source-map": "^27.0.1", - "@jest/test-result": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^27.0.1", - "is-generator-fn": "^2.0.0", - "jest-each": "^27.0.1", - "jest-matcher-utils": "^27.0.1", - "jest-message-util": "^27.0.1", - "jest-runtime": "^27.0.1", - "jest-snapshot": "^27.0.1", - "jest-util": "^27.0.1", - "pretty-format": "^27.0.1", - "throat": "^6.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "pretty-format": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.1.tgz", - "integrity": "sha512-qE+0J6c/gd+R6XTcQgPJMc5hMJNsxzSF5p8iZSbMZ7GQzYGlSLNkh2P80Wa2dbF4gEVUsJEgcrBY+1L2/j265w==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - } - } - } - }, - "jest-leak-detector": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.0.1.tgz", - "integrity": "sha512-SQ/lRhfmnV3UuiaKIjwNXCaW2yh1rTMAL4n4Cl4I4gU0X2LoIc6Ogxe4UKM/J6Ld2uzc4gDGVYc5lSdpf6WjYw==", - "dev": true, - "requires": { - "jest-get-type": "^27.0.1", - "pretty-format": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "jest-get-type": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.1.tgz", - "integrity": "sha512-9Tggo9zZbu0sHKebiAijyt1NM77Z0uO4tuWOxUCujAiSeXv30Vb5D4xVF4UR4YWNapcftj+PbByU54lKD7/xMg==", - "dev": true - }, - "pretty-format": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.1.tgz", - "integrity": "sha512-qE+0J6c/gd+R6XTcQgPJMc5hMJNsxzSF5p8iZSbMZ7GQzYGlSLNkh2P80Wa2dbF4gEVUsJEgcrBY+1L2/j265w==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - } - } - } - }, - "jest-matcher-utils": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.0.1.tgz", - "integrity": "sha512-NauNU+olKhPzLlsRnTOYFGk/MK5QFYl9ZzkrtfsY4eCq4SB3Bcl03UL44VdnlN5S/uFn4H2jwvRY1y6nSDTX3g==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^27.0.1", - "jest-get-type": "^27.0.1", - "pretty-format": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "diff-sequences": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.1.tgz", - "integrity": "sha512-XPLijkfJUh/PIBnfkcSHgvD6tlYixmcMAn3osTk6jt+H0v/mgURto1XUiD9DKuGX5NDoVS6dSlA23gd9FUaCFg==", - "dev": true - }, - "jest-diff": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.0.1.tgz", - "integrity": "sha512-DQ3OgfJgoGWVTYo4qnYW/Jg5mpYFS2QW9BLxA8bs12ZRN1K8QPZtWeYvUPohQFs3CHX3JLTndGg3jyxdL5THFQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^27.0.1", - "jest-get-type": "^27.0.1", - "pretty-format": "^27.0.1" - } - }, - "jest-get-type": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.1.tgz", - "integrity": "sha512-9Tggo9zZbu0sHKebiAijyt1NM77Z0uO4tuWOxUCujAiSeXv30Vb5D4xVF4UR4YWNapcftj+PbByU54lKD7/xMg==", - "dev": true - }, - "pretty-format": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.1.tgz", - "integrity": "sha512-qE+0J6c/gd+R6XTcQgPJMc5hMJNsxzSF5p8iZSbMZ7GQzYGlSLNkh2P80Wa2dbF4gEVUsJEgcrBY+1L2/j265w==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - } - } - } - }, - "jest-message-util": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.0.1.tgz", - "integrity": "sha512-w8BfON2GwWORkos8BsxcwwQrLkV2s1ENxSRXK43+6yuquDE2hVxES/jrFqOArpP1ETVqqMmktU6iGkG8ncVzeA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.0.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.4", - "pretty-format": "^27.0.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "pretty-format": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.1.tgz", - "integrity": "sha512-qE+0J6c/gd+R6XTcQgPJMc5hMJNsxzSF5p8iZSbMZ7GQzYGlSLNkh2P80Wa2dbF4gEVUsJEgcrBY+1L2/j265w==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - } - } - } - }, - "jest-mock": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-27.0.1.tgz", - "integrity": "sha512-fXCSZQDT5hUcAUy8OBnB018x7JFOMQnz4XfpSKEbfpWzL6o5qaLRhgf2Qg2NPuVKmC/fgOf33Edj8wjF4I24CQ==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "@types/node": "*" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true - }, - "jest-regex-util": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.0.1.tgz", - "integrity": "sha512-6nY6QVcpTgEKQy1L41P4pr3aOddneK17kn3HJw6SdwGiKfgCGTvH02hVXL0GU8GEKtPH83eD2DIDgxHXOxVohQ==", - "dev": true - }, - "jest-resolve": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.0.1.tgz", - "integrity": "sha512-Q7QQ0OZ7z6D5Dul0MrsexlKalU8ZwexBfHLSu1qYPgphvUm6WO1b/xUnipU3e+uW1riDzMcJeJVYbdQ37hBHeg==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "chalk": "^4.0.0", - "escalade": "^3.1.1", - "graceful-fs": "^4.2.4", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^27.0.1", - "resolve": "^1.20.0", - "slash": "^3.0.0" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "jest-resolve-dependencies": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.0.1.tgz", - "integrity": "sha512-ly1x5mEf21f3IVWbUNwIz/ePLtv4QdhYuQIVSVDqxx7yzAwhhdu0DJo7UNiEYKQY7Im48wfbNdOUpo7euFUXBQ==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "jest-regex-util": "^27.0.1", - "jest-snapshot": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "jest-runner": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-27.0.1.tgz", - "integrity": "sha512-DUNizlD2D7J80G3VOrwfbtb7KYxiftMng82HNcKwTW0W3AwwNuBeq+1exoCnLO7Mxh7NP+k/1XQBlzLpjr/CnA==", - "dev": true, - "requires": { - "@jest/console": "^27.0.1", - "@jest/environment": "^27.0.1", - "@jest/test-result": "^27.0.1", - "@jest/transform": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.8.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-config": "^27.0.1", - "jest-docblock": "^27.0.1", - "jest-haste-map": "^27.0.1", - "jest-leak-detector": "^27.0.1", - "jest-message-util": "^27.0.1", - "jest-resolve": "^27.0.1", - "jest-runtime": "^27.0.1", - "jest-util": "^27.0.1", - "jest-worker": "^27.0.1", - "source-map-support": "^0.5.6", - "throat": "^6.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "jest-runtime": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.0.1.tgz", - "integrity": "sha512-ImcrbQtpCUp8X9Rm4ky3j1GG9cqIKZJvXGZyB5cHEapGPTmg7wvvNooLmKragEe61/p/bhw1qO68Y0/9BSsBBg==", - "dev": true, - "requires": { - "@jest/console": "^27.0.1", - "@jest/environment": "^27.0.1", - "@jest/fake-timers": "^27.0.1", - "@jest/globals": "^27.0.1", - "@jest/source-map": "^27.0.1", - "@jest/test-result": "^27.0.1", - "@jest/transform": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^27.0.1", - "jest-message-util": "^27.0.1", - "jest-mock": "^27.0.1", - "jest-regex-util": "^27.0.1", - "jest-resolve": "^27.0.1", - "jest-snapshot": "^27.0.1", - "jest-util": "^27.0.1", - "jest-validate": "^27.0.1", - "slash": "^3.0.0", - "strip-bom": "^4.0.0", - "yargs": "^16.0.3" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "jest-serializer": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.0.1.tgz", - "integrity": "sha512-svy//5IH6bfQvAbkAEg1s7xhhgHTtXu0li0I2fdKHDsLP2P2MOiscPQIENQep8oU2g2B3jqLyxKKzotZOz4CwQ==", - "dev": true, - "requires": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - } - }, - "jest-snapshot": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.0.1.tgz", - "integrity": "sha512-HgKmSebDB3rswugREeh+nKrxJEVZE12K7lZ2MuwfFZT6YmiH0TlofsL2YmiLsCsG5KH5ZcLYYpF5bDrvtVx/Xg==", - "dev": true, - "requires": { - "@babel/core": "^7.7.2", - "@babel/generator": "^7.7.2", - "@babel/parser": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.0.0", - "@jest/transform": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^27.0.1", - "graceful-fs": "^4.2.4", - "jest-diff": "^27.0.1", - "jest-get-type": "^27.0.1", - "jest-haste-map": "^27.0.1", - "jest-matcher-utils": "^27.0.1", - "jest-message-util": "^27.0.1", - "jest-resolve": "^27.0.1", - "jest-util": "^27.0.1", - "natural-compare": "^1.4.0", - "pretty-format": "^27.0.1", - "semver": "^7.3.2" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "diff-sequences": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.0.1.tgz", - "integrity": "sha512-XPLijkfJUh/PIBnfkcSHgvD6tlYixmcMAn3osTk6jt+H0v/mgURto1XUiD9DKuGX5NDoVS6dSlA23gd9FUaCFg==", - "dev": true - }, - "jest-diff": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.0.1.tgz", - "integrity": "sha512-DQ3OgfJgoGWVTYo4qnYW/Jg5mpYFS2QW9BLxA8bs12ZRN1K8QPZtWeYvUPohQFs3CHX3JLTndGg3jyxdL5THFQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^27.0.1", - "jest-get-type": "^27.0.1", - "pretty-format": "^27.0.1" - } - }, - "jest-get-type": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.1.tgz", - "integrity": "sha512-9Tggo9zZbu0sHKebiAijyt1NM77Z0uO4tuWOxUCujAiSeXv30Vb5D4xVF4UR4YWNapcftj+PbByU54lKD7/xMg==", - "dev": true - }, - "pretty-format": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.1.tgz", - "integrity": "sha512-qE+0J6c/gd+R6XTcQgPJMc5hMJNsxzSF5p8iZSbMZ7GQzYGlSLNkh2P80Wa2dbF4gEVUsJEgcrBY+1L2/j265w==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - } - } - } - }, - "jest-util": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.0.1.tgz", - "integrity": "sha512-lEw3waSmEOO4ZkwkUlFSvg4es1+8+LIkSGxp/kF60K0+vMR3Dv3O2HMZhcln9NHqSQzpVbsDT6OeMzUPW7DfRg==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^3.0.0", - "picomatch": "^2.2.3" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "jest-validate": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-27.0.1.tgz", - "integrity": "sha512-zvmPRcfTkqTZuHveIKAI2nbkUc3SDXjWVJULknPLGF5bdxOGSeGZg7f/Uw0MUVOkCOaspcHnsPCgZG0pqmg71g==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^27.0.1", - "leven": "^3.1.0", - "pretty-format": "^27.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - }, - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true - }, - "jest-get-type": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.0.1.tgz", - "integrity": "sha512-9Tggo9zZbu0sHKebiAijyt1NM77Z0uO4tuWOxUCujAiSeXv30Vb5D4xVF4UR4YWNapcftj+PbByU54lKD7/xMg==", - "dev": true - }, - "pretty-format": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.0.1.tgz", - "integrity": "sha512-qE+0J6c/gd+R6XTcQgPJMc5hMJNsxzSF5p8iZSbMZ7GQzYGlSLNkh2P80Wa2dbF4gEVUsJEgcrBY+1L2/j265w==", - "dev": true, - "requires": { - "@jest/types": "^27.0.1", - "ansi-regex": "^5.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - } - } - } - }, - "jest-watcher": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.0.1.tgz", - "integrity": "sha512-Chp9c02BN0IgEbtGreyAhGqIsOrn9a0XnzbuXOxdW1+cW0Tjh12hMzHDIdLFHpYP/TqaMTmPHaJ5KWvpCCrNFw==", - "dev": true, - "requires": { - "@jest/test-result": "^27.0.1", - "@jest/types": "^27.0.1", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^27.0.1", - "string-length": "^4.0.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.0.1.tgz", - "integrity": "sha512-8A25RRV4twZutsx2D+7WphnDsp7If9Yu6ko0Gxwrwv8BiWESFzka34+Aa2kC8w9xewt7SDuCUSZ6IiAFVj3PRg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.3.tgz", - "integrity": "sha512-YlFfTGS+zqCgXuXNV26rOIeETOkXnGQXP/pjjL9P0gO/EP9jTmc7pUBhx+jVEIxpq41RX33GQ7N3DzOSfZoglQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - } - } - }, - "jest-worker": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.0.1.tgz", - "integrity": "sha512-NhHqClI3owOjmS8dBhQMKHZ2rrT0sBTpqGitp9nMX5AAjVXd+15o4v96uBEMhoywaLKN+5opcKBlXwAoADZolA==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "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, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsdom": { - "version": "16.6.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.6.0.tgz", - "integrity": "sha512-Ty1vmF4NHJkolaEmdjtxTfSfkdb8Ywarwf63f+F8/mDD1uLSSWDxDuMiZxiPhwunLrn9LOSVItWj4bLYsLN3Dg==", - "dev": true, - "requires": { - "abab": "^2.0.5", - "acorn": "^8.2.4", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.3.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.1", - "domexception": "^2.0.1", - "escodegen": "^2.0.0", - "form-data": "^3.0.0", - "html-encoding-sniffer": "^2.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "6.0.1", - "saxes": "^5.0.1", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.5.0", - "ws": "^7.4.5", - "xml-name-validator": "^3.0.0" - }, - "dependencies": { - "acorn": { - "version": "8.2.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.2.4.tgz", - "integrity": "sha512-Ibt84YwBDDA890eDiDCEqcbwvHlBvzzDkU2cGBBDDI1QWT12jTiXIOn2CIw5KK4i6N5Z2HUxwYjzriDyqaqqZg==", - "dev": true - } - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "just-extend": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", - "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", - "dev": true - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true - }, - "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 - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lolex": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", - "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "dev": true, - "requires": { - "tmpl": "1.0.x" - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "micromatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "mime-db": { - "version": "1.47.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.47.0.tgz", - "integrity": "sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw==", - "dev": true - }, - "mime-types": { - "version": "2.1.30", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.30.tgz", - "integrity": "sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg==", - "dev": true, - "requires": { - "mime-db": "1.47.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "mimic-response": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", - "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "nise": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", - "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", - "dev": true, - "requires": { - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "lolex": "^5.0.1", - "path-to-regexp": "^1.7.0" - } - }, - "nock": { - "version": "10.0.6", - "resolved": "https://registry.npmjs.org/nock/-/nock-10.0.6.tgz", - "integrity": "sha512-b47OWj1qf/LqSQYnmokNWM8D88KvUl2y7jT0567NB3ZBAZFz2bWp2PC81Xn7u8F2/vJxzkzNZybnemeFa7AZ2w==", - "dev": true, - "requires": { - "chai": "^4.1.2", - "debug": "^4.1.0", - "deep-equal": "^1.0.0", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.5", - "mkdirp": "^0.5.0", - "propagate": "^1.0.0", - "qs": "^6.5.1", - "semver": "^5.5.0" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - } - } - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "dev": true - }, - "node-releases": { - "version": "1.1.72", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.72.tgz", - "integrity": "sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==", - "dev": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - }, - "dependencies": { - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - } - } - }, - "nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, - "object-inspect": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.10.3.tgz", - "integrity": "sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw==", - "dev": true - }, - "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, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", - "dev": true - }, - "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, - "requires": { - "p-try": "^2.0.0" - } - }, - "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, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse5": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", - "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", - "dev": true - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "requires": { - "isarray": "0.0.1" - } - }, - "pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true - }, - "pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "dev": true, - "requires": { - "node-modules-regexp": "^1.0.0" - } - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", - "dev": true - }, - "prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "requires": { - "fast-diff": "^1.1.2" - } - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true - }, - "prompts": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.1.tgz", - "integrity": "sha512-EQyfIuO2hPDsX1L/blblV+H7I0knhgAd82cVneCwcdND9B8AuCDuRcBH6yIcG4dFzlOUqbazQqwGjx5xmsNLuQ==", - "dev": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "propagate": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/propagate/-/propagate-1.0.0.tgz", - "integrity": "sha1-AMLa7t2iDofjeCs0Stuhzd1q1wk=", - "dev": true - }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.1.tgz", - "integrity": "sha512-M528Hph6wsSVOBiYUnGf+K/7w0hNshs/duGsNXPUCLH5XAqjEtiPGwNONLV0tBH8NoGb0mvD5JubnUTrujKDTg==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true - }, - "regexp.prototype.flags": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz", - "integrity": "sha512-JiBdRBq91WlY7uRJ0ds7R+dU02i6LKi8r3BuQhNXn+kmeLN+EfHhfjqMRis1zJxnlu88hq/4dx0P2OP3APRTOA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3" - } - }, - "regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", - "dev": true - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "dev": true, - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - }, - "dependencies": { - "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 - } - } - }, - "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 - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "dev": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true - }, - "rxjs": { - "version": "6.6.7", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", - "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "dev": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "requires": { - "xmlchars": "^2.2.0" - } - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, - "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - } - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "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==", - "dev": true - } - } - }, - "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, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "requires": { - "ansi-regex": "^4.1.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - } - } - }, - "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==", - "dev": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.2.0.tgz", - "integrity": "sha512-6sXEzV5+I5j8Bmq9/vUphGRM/RJNT9SCURJLjwfOg51heRtguGWDzcaBlgAzKhQa0EVNpPEKzQuBwZ8S8WaCeQ==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - } - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", - "dev": true, - "requires": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" - }, - "dependencies": { - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - } - } - }, - "terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } - }, - "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==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "throat": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", - "integrity": "sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", - "dev": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "tough-cookie": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", - "dev": true, - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.1.2" - } - }, - "tr46": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz", - "integrity": "sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==", - "dev": true, - "requires": { - "punycode": "^2.1.1" - } - }, - "ts-jest": { - "version": "27.0.1", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-27.0.1.tgz", - "integrity": "sha512-03qAt77QjhxyM5Bt2KrrT1WbdumiwLz989sD3IUznSp3GIFQrx76kQqSMLF7ynnxrF3/1ipzABnHxMlU8PD4Vw==", - "dev": true, - "requires": { - "bs-logger": "0.x", - "buffer-from": "1.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^27.0.0", - "json5": "2.x", - "lodash": "4.x", - "make-error": "1.x", - "mkdirp": "1.x", - "semver": "7.x", - "yargs-parser": "20.x" - }, - "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - } - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - } - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "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 - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", - "dev": true - }, - "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 - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - }, - "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 - }, - "v8-to-istanbul": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz", - "integrity": "sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true - } - } - }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "requires": { - "xml-name-validator": "^3.0.0" - } - }, - "walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "dev": true, - "requires": { - "makeerror": "1.0.x" - } - }, - "webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "whatwg-url": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.5.0.tgz", - "integrity": "sha512-fy+R77xWv0AiqfLl4nuGUlQ3/6b5uNfQ4WAbGQVMYshCTCCPK9psC1nWh3XHuxGVCtlcDDQPQW1csmmIQo+fwg==", - "dev": true, - "requires": { - "lodash": "^4.7.0", - "tr46": "^2.0.2", - "webidl-conversions": "^6.1.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "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 - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "ws": { - "version": "7.4.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz", - "integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==", - "dev": true - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "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" - } - }, - "yargs-parser": { - "version": "20.2.7", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.7.tgz", - "integrity": "sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==", - "dev": true - } - } -} diff --git a/packages/datafile-manager/package.json b/packages/datafile-manager/package.json deleted file mode 100644 index 77c534256..000000000 --- a/packages/datafile-manager/package.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "name": "@optimizely/js-sdk-datafile-manager", - "version": "0.9.5", - "description": "Optimizely Full Stack Datafile Manager", - "repository": { - "type": "git", - "url": "git+https://github.com/optimizely/javascript-sdk.git", - "directory": "packages/datafile-manager" - }, - "license": "Apache-2.0", - "engines": { - "node": ">=8.0.0" - }, - "main": "lib/index.node.js", - "browser": "lib/index.browser.js", - "react-native": "lib/index.react_native.js", - "types": "lib/index.node.d.ts", - "directories": { - "lib": "lib", - "test": "__test__" - }, - "files": [ - "lib" - ], - "publishConfig": { - "access": "public" - }, - "devDependencies": { - "@react-native-async-storage/async-storage": "^1.2.0", - "@types/jest": "^26.0.3", - "@types/nise": "^1.4.0", - "@types/nock": "^9.3.1", - "@types/node": "^11.11.7", - "@typescript-eslint/eslint-plugin": "^3.3.0", - "@typescript-eslint/parser": "^3.3.0", - "eslint": "^6.8.0", - "eslint-config-prettier": "^6.10.0", - "eslint-plugin-prettier": "^3.1.2", - "jest": "^27.0.1", - "nise": "^1.4.10", - "nock": "^10.0.6", - "prettier": "^1.19.1", - "ts-jest": "^27.0.1", - "typescript": "^4.7.4" - }, - "dependencies": { - "@optimizely/js-sdk-logging": "^0.3.1", - "@optimizely/js-sdk-utils": "^0.4.0", - "decompress-response": "^4.2.1" - }, - "peerDependencies": { - "@react-native-async-storage/async-storage": "^1.2.0" - }, - "peerDependenciesMeta": { - "@react-native-async-storage/async-storage": { - "optional": true - } - }, - "scripts": { - "clean": "rm -rf lib", - "lint": "tsc --noEmit && eslint --fix 'src/**/*.ts' '__test__/**/*.ts'", - "test": "jest", - "posttest": "npm run lint", - "prebuild": "npm run clean", - "build": "tsc", - "prepare": "npm run build", - "prepublishOnly": "npm test" - } -} diff --git a/packages/datafile-manager/src/backoffController.ts b/packages/datafile-manager/src/backoffController.ts deleted file mode 100644 index 8021f8cbd..000000000 --- a/packages/datafile-manager/src/backoffController.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { BACKOFF_BASE_WAIT_SECONDS_BY_ERROR_COUNT } from './config'; - -function randomMilliseconds(): number { - return Math.round(Math.random() * 1000); -} - -export default class BackoffController { - private errorCount = 0; - - getDelay(): number { - if (this.errorCount === 0) { - return 0; - } - const baseWaitSeconds = - BACKOFF_BASE_WAIT_SECONDS_BY_ERROR_COUNT[ - Math.min(BACKOFF_BASE_WAIT_SECONDS_BY_ERROR_COUNT.length - 1, this.errorCount) - ]; - return baseWaitSeconds * 1000 + randomMilliseconds(); - } - - countError(): void { - if (this.errorCount < BACKOFF_BASE_WAIT_SECONDS_BY_ERROR_COUNT.length - 1) { - this.errorCount++; - } - } - - reset(): void { - this.errorCount = 0; - } -} diff --git a/packages/datafile-manager/src/browserDatafileManager.ts b/packages/datafile-manager/src/browserDatafileManager.ts deleted file mode 100644 index cc7cb046a..000000000 --- a/packages/datafile-manager/src/browserDatafileManager.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { makeGetRequest } from './browserRequest'; -import HttpPollingDatafileManager from './httpPollingDatafileManager'; -import { Headers, AbortableRequest } from './http'; -import { DatafileManagerConfig } from './datafileManager'; - -export default class BrowserDatafileManager extends HttpPollingDatafileManager { - protected makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest { - return makeGetRequest(reqUrl, headers); - } - - protected getConfigDefaults(): Partial<DatafileManagerConfig> { - return { - autoUpdate: false, - }; - } -} diff --git a/packages/datafile-manager/src/browserRequest.ts b/packages/datafile-manager/src/browserRequest.ts deleted file mode 100644 index 2165903fc..000000000 --- a/packages/datafile-manager/src/browserRequest.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { AbortableRequest, Response, Headers } from './http'; -import { REQUEST_TIMEOUT_MS } from './config'; -import { getLogger } from '@optimizely/js-sdk-logging'; - -const logger = getLogger('DatafileManager'); - -const GET_METHOD = 'GET'; -const READY_STATE_DONE = 4; - -function parseHeadersFromXhr(req: XMLHttpRequest): Headers { - const allHeadersString = req.getAllResponseHeaders(); - - if (allHeadersString === null) { - return {}; - } - - const headerLines = allHeadersString.split('\r\n'); - const headers: Headers = {}; - headerLines.forEach(headerLine => { - const separatorIndex = headerLine.indexOf(': '); - if (separatorIndex > -1) { - const headerName = headerLine.slice(0, separatorIndex); - const headerValue = headerLine.slice(separatorIndex + 2); - if (headerValue.length > 0) { - headers[headerName] = headerValue; - } - } - }); - return headers; -} - -function setHeadersInXhr(headers: Headers, req: XMLHttpRequest): void { - Object.keys(headers).forEach(headerName => { - const header = headers[headerName]; - req.setRequestHeader(headerName, header!); - }); -} - -export function makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest { - const req = new XMLHttpRequest(); - - const responsePromise: Promise<Response> = new Promise((resolve, reject) => { - req.open(GET_METHOD, reqUrl, true); - - setHeadersInXhr(headers, req); - - req.onreadystatechange = (): void => { - if (req.readyState === READY_STATE_DONE) { - const statusCode = req.status; - if (statusCode === 0) { - reject(new Error('Request error')); - return; - } - - const headers = parseHeadersFromXhr(req); - const resp: Response = { - statusCode: req.status, - body: req.responseText, - headers, - }; - resolve(resp); - } - }; - - req.timeout = REQUEST_TIMEOUT_MS; - - req.ontimeout = (): void => { - logger.error('Request timed out'); - }; - - req.send(); - }); - - return { - responsePromise, - abort(): void { - req.abort(); - }, - }; -} diff --git a/packages/datafile-manager/src/datafileManager.ts b/packages/datafile-manager/src/datafileManager.ts deleted file mode 100644 index f93c8551f..000000000 --- a/packages/datafile-manager/src/datafileManager.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import PersistentKeyValueCache from './persistentKeyValueCache'; - -export interface DatafileUpdate { - datafile: string; -} - -export interface DatafileUpdateListener { - (datafileUpdate: DatafileUpdate): void; -} - -// TODO: Replace this with the one from js-sdk-models -interface Managed { - start(): void; - - stop(): Promise<any>; -} - -export interface DatafileManager extends Managed { - get: () => string; - on: (eventName: string, listener: DatafileUpdateListener) => () => void; - onReady: () => Promise<void>; -} - -export interface DatafileManagerConfig { - autoUpdate?: boolean; - datafile?: string; - sdkKey: string; - updateInterval?: number; - urlTemplate?: string; - cache?: PersistentKeyValueCache; -} - -export interface NodeDatafileManagerConfig extends DatafileManagerConfig { - datafileAccessToken?: string; -} diff --git a/packages/datafile-manager/src/eventEmitter.ts b/packages/datafile-manager/src/eventEmitter.ts deleted file mode 100644 index bf4eee44e..000000000 --- a/packages/datafile-manager/src/eventEmitter.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export type Disposer = () => void; - -export type Listener = (arg?: any) => void; - -interface Listeners { - [index: string]: { - // index is event name - [index: string]: Listener; // index is listener id - }; -} - -export default class EventEmitter { - private listeners: Listeners = {}; - - private listenerId = 1; - - on(eventName: string, listener: Listener): Disposer { - if (!this.listeners[eventName]) { - this.listeners[eventName] = {}; - } - const currentListenerId = String(this.listenerId); - this.listenerId++; - this.listeners[eventName][currentListenerId] = listener; - return (): void => { - if (this.listeners[eventName]) { - delete this.listeners[eventName][currentListenerId]; - } - }; - } - - emit(eventName: string, arg?: any): void { - const listeners = this.listeners[eventName]; - if (listeners) { - Object.keys(listeners).forEach(listenerId => { - const listener = listeners[listenerId]; - listener(arg); - }); - } - } - - removeAllListeners(): void { - this.listeners = {}; - } -} - -// TODO: Create a typed event emitter for use in TS only (not JS) diff --git a/packages/datafile-manager/src/http.ts b/packages/datafile-manager/src/http.ts deleted file mode 100644 index 41503b1aa..000000000 --- a/packages/datafile-manager/src/http.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Headers is the interface that bridges between the abstract datafile manager and - * any Node-or-browser-specific http header types. - * It's simplified and can only store one value per header name. - * We can extend or replace this type if requirements change and we need - * to work with multiple values per header name. - */ -export interface Headers { - [header: string]: string | undefined; -} - -export interface Response { - statusCode?: number; - body: string; - headers: Headers; -} - -export interface AbortableRequest { - abort(): void; - responsePromise: Promise<Response>; -} diff --git a/packages/datafile-manager/src/httpPollingDatafileManager.ts b/packages/datafile-manager/src/httpPollingDatafileManager.ts deleted file mode 100644 index f8232c0bd..000000000 --- a/packages/datafile-manager/src/httpPollingDatafileManager.ts +++ /dev/null @@ -1,339 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getLogger } from '@optimizely/js-sdk-logging'; -import { sprintf } from '@optimizely/js-sdk-utils'; -import { DatafileManager, DatafileManagerConfig, DatafileUpdate } from './datafileManager'; -import EventEmitter, { Disposer } from './eventEmitter'; -import { AbortableRequest, Response, Headers } from './http'; -import { DEFAULT_UPDATE_INTERVAL, MIN_UPDATE_INTERVAL, DEFAULT_URL_TEMPLATE } from './config'; -import BackoffController from './backoffController'; -import PersistentKeyValueCache from './persistentKeyValueCache'; - -const logger = getLogger('DatafileManager'); - -const UPDATE_EVT = 'update'; - -function isValidUpdateInterval(updateInterval: number): boolean { - return updateInterval >= MIN_UPDATE_INTERVAL; -} - -function isSuccessStatusCode(statusCode: number): boolean { - return statusCode >= 200 && statusCode < 400; -} - -const noOpKeyValueCache: PersistentKeyValueCache = { - get(): Promise<string> { - return Promise.resolve(''); - }, - - set(): Promise<void> { - return Promise.resolve(); - }, - - contains(): Promise<boolean> { - return Promise.resolve(false); - }, - - remove(): Promise<void> { - return Promise.resolve(); - }, -}; - -export default abstract class HttpPollingDatafileManager implements DatafileManager { - // Make an HTTP get request to the given URL with the given headers - // Return an AbortableRequest, which has a promise for a Response. - // If we can't get a response, the promise is rejected. - // The request will be aborted if the manager is stopped while the request is in flight. - protected abstract makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest; - - // Return any default configuration options that should be applied - protected abstract getConfigDefaults(): Partial<DatafileManagerConfig>; - - private currentDatafile: string; - - private readonly readyPromise: Promise<void>; - - private isReadyPromiseSettled: boolean; - - private readyPromiseResolver: () => void; - - private readyPromiseRejecter: (err: Error) => void; - - private readonly emitter: EventEmitter; - - private readonly autoUpdate: boolean; - - private readonly updateInterval: number; - - private currentTimeout: any; - - private isStarted: boolean; - - private lastResponseLastModified?: string; - - private datafileUrl: string; - - private currentRequest: AbortableRequest | null; - - private backoffController: BackoffController; - - private cacheKey: string; - - private cache: PersistentKeyValueCache; - - // When true, this means the update interval timeout fired before the current - // sync completed. In that case, we should sync again immediately upon - // completion of the current request, instead of waiting another update - // interval. - private syncOnCurrentRequestComplete: boolean; - - constructor(config: DatafileManagerConfig) { - const configWithDefaultsApplied: DatafileManagerConfig = { - ...this.getConfigDefaults(), - ...config, - }; - const { - datafile, - autoUpdate = false, - sdkKey, - updateInterval = DEFAULT_UPDATE_INTERVAL, - urlTemplate = DEFAULT_URL_TEMPLATE, - cache = noOpKeyValueCache, - } = configWithDefaultsApplied; - - this.cache = cache; - this.cacheKey = 'opt-datafile-' + sdkKey; - this.isReadyPromiseSettled = false; - this.readyPromiseResolver = (): void => {}; - this.readyPromiseRejecter = (): void => {}; - this.readyPromise = new Promise((resolve, reject) => { - this.readyPromiseResolver = resolve; - this.readyPromiseRejecter = reject; - }); - - if (datafile) { - this.currentDatafile = datafile; - if (!sdkKey) { - this.resolveReadyPromise(); - } - } else { - this.currentDatafile = ''; - } - - this.isStarted = false; - - this.datafileUrl = sprintf(urlTemplate, sdkKey); - - this.emitter = new EventEmitter(); - this.autoUpdate = autoUpdate; - if (isValidUpdateInterval(updateInterval)) { - this.updateInterval = updateInterval; - } else { - logger.warn('Invalid updateInterval %s, defaulting to %s', updateInterval, DEFAULT_UPDATE_INTERVAL); - this.updateInterval = DEFAULT_UPDATE_INTERVAL; - } - this.currentTimeout = null; - this.currentRequest = null; - this.backoffController = new BackoffController(); - this.syncOnCurrentRequestComplete = false; - } - - get(): string { - return this.currentDatafile; - } - - start(): void { - if (!this.isStarted) { - logger.debug('Datafile manager started'); - this.isStarted = true; - this.backoffController.reset(); - this.setDatafileFromCacheIfAvailable(); - this.syncDatafile(); - } - } - - stop(): Promise<void> { - logger.debug('Datafile manager stopped'); - this.isStarted = false; - if (this.currentTimeout) { - clearTimeout(this.currentTimeout); - this.currentTimeout = null; - } - - this.emitter.removeAllListeners(); - - if (this.currentRequest) { - this.currentRequest.abort(); - this.currentRequest = null; - } - - return Promise.resolve(); - } - - onReady(): Promise<void> { - return this.readyPromise; - } - - on(eventName: string, listener: (datafileUpdate: DatafileUpdate) => void): Disposer { - return this.emitter.on(eventName, listener); - } - - private onRequestRejected(err: any): void { - if (!this.isStarted) { - return; - } - - this.backoffController.countError(); - - if (err instanceof Error) { - logger.error('Error fetching datafile: %s', err.message, err); - } else if (typeof err === 'string') { - logger.error('Error fetching datafile: %s', err); - } else { - logger.error('Error fetching datafile'); - } - } - - private onRequestResolved(response: Response): void { - if (!this.isStarted) { - return; - } - - if (typeof response.statusCode !== 'undefined' && isSuccessStatusCode(response.statusCode)) { - this.backoffController.reset(); - } else { - this.backoffController.countError(); - } - - this.trySavingLastModified(response.headers); - - const datafile = this.getNextDatafileFromResponse(response); - if (datafile !== '') { - logger.info('Updating datafile from response'); - this.currentDatafile = datafile; - this.cache.set(this.cacheKey, datafile); - if (!this.isReadyPromiseSettled) { - this.resolveReadyPromise(); - } else { - const datafileUpdate: DatafileUpdate = { - datafile, - }; - this.emitter.emit(UPDATE_EVT, datafileUpdate); - } - } - } - - private onRequestComplete(this: HttpPollingDatafileManager): void { - if (!this.isStarted) { - return; - } - - this.currentRequest = null; - - if (!this.isReadyPromiseSettled && !this.autoUpdate) { - // We will never resolve ready, so reject it - this.rejectReadyPromise(new Error('Failed to become ready')); - } - - if (this.autoUpdate && this.syncOnCurrentRequestComplete) { - this.syncDatafile(); - } - this.syncOnCurrentRequestComplete = false; - } - - private syncDatafile(): void { - const headers: Headers = {}; - if (this.lastResponseLastModified) { - headers['if-modified-since'] = this.lastResponseLastModified; - } - - logger.debug('Making datafile request to url %s with headers: %s', this.datafileUrl, () => JSON.stringify(headers)); - this.currentRequest = this.makeGetRequest(this.datafileUrl, headers); - - const onRequestComplete = (): void => { - this.onRequestComplete(); - }; - const onRequestResolved = (response: Response): void => { - this.onRequestResolved(response); - }; - const onRequestRejected = (err: any): void => { - this.onRequestRejected(err); - }; - this.currentRequest.responsePromise - .then(onRequestResolved, onRequestRejected) - .then(onRequestComplete, onRequestComplete); - - if (this.autoUpdate) { - this.scheduleNextUpdate(); - } - } - - private resolveReadyPromise(): void { - this.readyPromiseResolver(); - this.isReadyPromiseSettled = true; - } - - private rejectReadyPromise(err: Error): void { - this.readyPromiseRejecter(err); - this.isReadyPromiseSettled = true; - } - - private scheduleNextUpdate(): void { - const currentBackoffDelay = this.backoffController.getDelay(); - const nextUpdateDelay = Math.max(currentBackoffDelay, this.updateInterval); - logger.debug('Scheduling sync in %s ms', nextUpdateDelay); - this.currentTimeout = setTimeout(() => { - if (this.currentRequest) { - this.syncOnCurrentRequestComplete = true; - } else { - this.syncDatafile(); - } - }, nextUpdateDelay); - } - - private getNextDatafileFromResponse(response: Response): string { - logger.debug('Response status code: %s', response.statusCode); - if (typeof response.statusCode === 'undefined') { - return ''; - } - if (response.statusCode === 304) { - return ''; - } - if (isSuccessStatusCode(response.statusCode)) { - return response.body; - } - return ''; - } - - private trySavingLastModified(headers: Headers): void { - const lastModifiedHeader = headers['last-modified'] || headers['Last-Modified']; - if (typeof lastModifiedHeader !== 'undefined') { - this.lastResponseLastModified = lastModifiedHeader; - logger.debug('Saved last modified header value from response: %s', this.lastResponseLastModified); - } - } - - setDatafileFromCacheIfAvailable(): void { - this.cache.get(this.cacheKey).then(datafile => { - if (this.isStarted && !this.isReadyPromiseSettled && datafile !== '') { - logger.debug('Using datafile from cache'); - this.currentDatafile = datafile; - this.resolveReadyPromise(); - } - }); - } -} diff --git a/packages/datafile-manager/src/index.browser.ts b/packages/datafile-manager/src/index.browser.ts deleted file mode 100644 index 00780202f..000000000 --- a/packages/datafile-manager/src/index.browser.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export * from './datafileManager'; -export { default as HttpPollingDatafileManager } from './browserDatafileManager'; diff --git a/packages/datafile-manager/src/nodeDatafileManager.ts b/packages/datafile-manager/src/nodeDatafileManager.ts deleted file mode 100644 index 1952cb369..000000000 --- a/packages/datafile-manager/src/nodeDatafileManager.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getLogger } from '@optimizely/js-sdk-logging'; -import { makeGetRequest } from './nodeRequest'; -import HttpPollingDatafileManager from './httpPollingDatafileManager'; -import { Headers, AbortableRequest } from './http'; -import { NodeDatafileManagerConfig, DatafileManagerConfig } from './datafileManager'; -import { DEFAULT_URL_TEMPLATE, DEFAULT_AUTHENTICATED_URL_TEMPLATE } from './config'; - -const logger = getLogger('NodeDatafileManager'); - -export default class NodeDatafileManager extends HttpPollingDatafileManager { - private accessToken?: string; - - constructor(config: NodeDatafileManagerConfig) { - const defaultUrlTemplate = config.datafileAccessToken ? DEFAULT_AUTHENTICATED_URL_TEMPLATE : DEFAULT_URL_TEMPLATE; - super({ - ...config, - urlTemplate: config.urlTemplate || defaultUrlTemplate, - }); - this.accessToken = config.datafileAccessToken; - } - - protected makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest { - const requestHeaders = Object.assign({}, headers); - if (this.accessToken) { - logger.debug('Adding Authorization header with Bearer Token'); - requestHeaders['Authorization'] = `Bearer ${this.accessToken}`; - } - return makeGetRequest(reqUrl, requestHeaders); - } - - protected getConfigDefaults(): Partial<DatafileManagerConfig> { - return { - autoUpdate: true, - }; - } -} diff --git a/packages/datafile-manager/src/nodeRequest.ts b/packages/datafile-manager/src/nodeRequest.ts deleted file mode 100644 index 939bac536..000000000 --- a/packages/datafile-manager/src/nodeRequest.ts +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import http from 'http'; -import https from 'https'; -import url from 'url'; -import { Headers, AbortableRequest, Response } from './http'; -import { REQUEST_TIMEOUT_MS } from './config'; -import decompressResponse from 'decompress-response'; - -// Shared signature between http.request and https.request -type ClientRequestCreator = (options: http.RequestOptions) => http.ClientRequest; - -function getRequestOptionsFromUrl(url: url.UrlWithStringQuery): http.RequestOptions { - return { - hostname: url.hostname, - path: url.path, - port: url.port, - protocol: url.protocol, - }; -} - -/** - * Convert incomingMessage.headers (which has type http.IncomingHttpHeaders) into our Headers type defined in src/http.ts. - * - * Our Headers type is simplified and can't represent mutliple values for the same header name. - * - * We don't currently need multiple values support, and the consumer code becomes simpler if it can assume at-most 1 value - * per header name. - * - */ -function createHeadersFromNodeIncomingMessage(incomingMessage: http.IncomingMessage): Headers { - const headers: Headers = {}; - Object.keys(incomingMessage.headers).forEach(headerName => { - const headerValue = incomingMessage.headers[headerName]; - if (typeof headerValue === 'string') { - headers[headerName] = headerValue; - } else if (typeof headerValue === 'undefined') { - // no value provided for this header - } else { - // array - if (headerValue.length > 0) { - // We don't care about multiple values - just take the first one - headers[headerName] = headerValue[0]; - } - } - }); - return headers; -} - -function getResponseFromRequest(request: http.ClientRequest): Promise<Response> { - // TODO: When we drop support for Node 6, consider using util.promisify instead of - // constructing own Promise - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - request.abort(); - reject(new Error('Request timed out')); - }, REQUEST_TIMEOUT_MS); - - request.once('response', (incomingMessage: http.IncomingMessage) => { - if (request.aborted) { - return; - } - - const response = decompressResponse(incomingMessage); - - response.setEncoding('utf8'); - - let responseData = ''; - response.on('data', (chunk: string) => { - if (!request.aborted) { - responseData += chunk; - } - }); - - response.on('end', () => { - if (request.aborted) { - return; - } - - clearTimeout(timeout); - - resolve({ - statusCode: incomingMessage.statusCode, - body: responseData, - headers: createHeadersFromNodeIncomingMessage(incomingMessage), - }); - }); - }); - - request.on('error', (err: any) => { - clearTimeout(timeout); - - if (err instanceof Error) { - reject(err); - } else if (typeof err === 'string') { - reject(new Error(err)); - } else { - reject(new Error('Request error')); - } - }); - }); -} - -export function makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest { - // TODO: Use non-legacy URL parsing when we drop support for Node 6 - const parsedUrl = url.parse(reqUrl); - - let requester: ClientRequestCreator; - if (parsedUrl.protocol === 'http:') { - requester = http.request; - } else if (parsedUrl.protocol === 'https:') { - requester = https.request; - } else { - return { - responsePromise: Promise.reject(new Error(`Unsupported protocol: ${parsedUrl.protocol}`)), - abort(): void {}, - }; - } - - const requestOptions: http.RequestOptions = { - ...getRequestOptionsFromUrl(parsedUrl), - method: 'GET', - headers: { - ...headers, - 'accept-encoding': 'gzip,deflate', - }, - }; - - const request = requester(requestOptions); - const responsePromise = getResponseFromRequest(request); - - request.end(); - - return { - abort(): void { - request.abort(); - }, - responsePromise, - }; -} diff --git a/packages/datafile-manager/src/persistentKeyValueCache.ts b/packages/datafile-manager/src/persistentKeyValueCache.ts deleted file mode 100644 index 08dfcf1fe..000000000 --- a/packages/datafile-manager/src/persistentKeyValueCache.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * An Interface to implement a persistent key value cache which supports strings as keys and values. - */ -export default interface PersistentKeyValueCache { - /** - * Returns value stored against a key or null if not found. - * @param key - * @returns - * Resolves promise with - * 1. string if value found was stored as a string. - * 2. null if the key does not exist in the cache. - * Rejects the promise in case of an error - */ - get(key: string): Promise<string>; - - /** - * Stores string in the persistent cache against a key - * @param key - * @param val - * @returns - * Resolves promise without a value if successful - * Rejects the promise in case of an error - */ - set(key: string, val: string): Promise<void>; - - /** - * Checks if a key exists in the cache - * @param key - * Resolves promise with - * 1. true if the key exists - * 2. false if the key does not exist - * Rejects the promise in case of an error - */ - contains(key: string): Promise<boolean>; - - /** - * Removes the key value pair from cache. - * @param key - * Resolves promise without a value if successful - * Rejects the promise in case of an error - */ - remove(key: string): Promise<void>; -} diff --git a/packages/datafile-manager/src/reactNativeAsyncStorageCache.ts b/packages/datafile-manager/src/reactNativeAsyncStorageCache.ts deleted file mode 100644 index c70c10541..000000000 --- a/packages/datafile-manager/src/reactNativeAsyncStorageCache.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import AsyncStorage from '@react-native-async-storage/async-storage'; -import PersistentKeyValueCache from './persistentKeyValueCache'; - -export default class ReactNativeAsyncStorageCache implements PersistentKeyValueCache { - get(key: string): Promise<string> { - return AsyncStorage.getItem(key).then((val: string | null) => { - if (!val) { - return ''; - } - return val; - }); - } - - set(key: string, val: string): Promise<void> { - return AsyncStorage.setItem(key, val); - } - - contains(key: string): Promise<boolean> { - return AsyncStorage.getItem(key).then((val: string | null) => val !== null); - } - - remove(key: string): Promise<void> { - return AsyncStorage.removeItem(key); - } -} diff --git a/packages/datafile-manager/src/reactNativeDatafileManager.ts b/packages/datafile-manager/src/reactNativeDatafileManager.ts deleted file mode 100644 index a7a2ac44e..000000000 --- a/packages/datafile-manager/src/reactNativeDatafileManager.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { makeGetRequest } from './browserRequest'; -import HttpPollingDatafileManager from './httpPollingDatafileManager'; -import { Headers, AbortableRequest } from './http'; -import { DatafileManagerConfig } from './datafileManager'; -import ReactNativeAsyncStorageCache from './reactNativeAsyncStorageCache'; - -export default class ReactNativeDatafileManager extends HttpPollingDatafileManager { - protected makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest { - return makeGetRequest(reqUrl, headers); - } - - protected getConfigDefaults(): Partial<DatafileManagerConfig> { - return { - autoUpdate: true, - cache: new ReactNativeAsyncStorageCache(), - }; - } -} diff --git a/packages/datafile-manager/tsconfig.json b/packages/datafile-manager/tsconfig.json deleted file mode 100644 index 9c6e2911b..000000000 --- a/packages/datafile-manager/tsconfig.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./lib" - }, - "include": [ - "./src" - ], - "exclude": [ - "./lib" - ] -} diff --git a/packages/event-processor/.gitignore b/packages/event-processor/.gitignore deleted file mode 100644 index d4a125901..000000000 --- a/packages/event-processor/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -lib/ -doc/ diff --git a/packages/event-processor/CHANGELOG.md b/packages/event-processor/CHANGELOG.md deleted file mode 100644 index 9b8e2a286..000000000 --- a/packages/event-processor/CHANGELOG.md +++ /dev/null @@ -1,117 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). - -## [Unreleased] -Changes that have landed but are not yet released. - -## [0.9.5] - February 2, 2022 - -### Changed -- Made these react native peer dependencies optional. - - `@react-native-async-storage/async-storage` - - `@react-native-community/netinfo` - -## [0.9.4] - January 31, 2022 - -### Changed -- Reverted changes in `0.9.3`. Changed these back to peer dependencies. Making them optional did not work as expected. - - `@react-native-async-storage/async-storage` - - `@react-native-community/netinfo` - -## [0.9.3] - January 28, 2022 - -### Changed -- Made these react native dependencies optional. - - `@react-native-async-storage/async-storage` - - `@react-native-community/netinfo` - -## [0.9.2] - November 3, 2021 - -### Fixed -- Impression event should send empty `experimentId` and `variationKey` instead of `null` when no `experiment` or `variation` is found. - -## [0.9.1] - October 13, 2021 - -### Fixed -- Update version of logging to `0.3.1` to fix stubbing issue. - -## [0.9.0] - October 8, 2021 - -### Changed -- Update `@optimizely/js-sdk-logging` to `0.3.0`. - -## [0.8.2] - June 14th, 2021 - -### Fixed -- Update `ConversionEvent` interface to allow event `id` type null and `tags` type undefined. - -## [0.8.1] - May 25th, 2021 - -### Fixed -- Replaced the deprecated `@react-native-community/async-storage` with `@react-native-async-storage/async-storage`. - -## [0.8.0] - November 10th, 2020 - -### New Features -- Update `Visitor.Snapshot` to include `enabled` in decision metadata object to support sending flag decisions. - -## [0.7.0] - October 16th, 2020 - -### New Features -- Update `Visitor.Snapshot` to include metadata object to support sending flag decisions. - -## [0.6.0] - July 28, 2020 - -### Fixed -- Upgrade utils dependency version - -## [0.5.1] - July 22, 2020 - -### Fixed -- In event processor, reverted back the typescript version to fix stubbing issue - -## [0.5.0] - July 21, 2020 - -### New Features -- Added Offline storage support to Event processor for React Native Apps - -## [0.4.0] - February 19, 2020 - -### New Features -- Promise returned from `stop` method of `EventProcessor` now tracks the state of all in-flight dispatcher requests, not just the final request that was triggered at the time `stop` was called - -## [0.3.2] - October 21, 2019 - -- Fixed a runtime error when accessing localstorage in `PendingEventsStore` when SDK is used in a React Native application. Pending events will still not be stored when SDK is used in React Native but no error will be thrown anymore. - -## [0.3.1] - August 29, 2019 - -### Fixed -- `DefaultEventQueue` no longer enqueues additional events after being stopped. As a result, `AbstractEventProcessor` no longer processes events after being stopped. -- `DefaultEventQueue` clears its buffer after being stopped. Event duplication, which was previously possible when additional events were enqueued after the stop, is no longer possible. - -## [0.3.0] - August 13, 2019 - -### New Features -- In `AbstractEventProcessor`, validate `maxQueueSize` and `flushInterval`; ignore & use default values when invalid -- `AbstractEventProcessor` can be constructed with a `notificationCenter`. When `notificationCenter` is provided, it triggers a log event notification after the event is sent to the event dispatcher - -### Changed -- Removed transformers, interceptors, and callbacks from `AbstractEventProcessor` -- Removed grouping events by context and dispatching one event per group at flush time. Instead, only maintain one group and flush immediately when an incompatible event is processed. - -## [0.2.1] - June 6, 2019 - -- Wrap the `callback` in `try/catch` when implementing a custom `eventDispatcher`. This ensures invoking the `callback` will always cleanup any pending retry tasks. - -## [0.2.0] - March 27, 2019 - -- Add `PendingEventsDispatcher` to wrap another EventDispatcher with retry support for -events that did not send successfully due to page navigation - -## [0.1.0] - March 1, 2019 - -Initial release diff --git a/packages/event-processor/LICENSE b/packages/event-processor/LICENSE deleted file mode 100644 index b9f80c5bd..000000000 --- a/packages/event-processor/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2016-2017, Optimizely, Inc. and contributors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/event-processor/__mocks__/@react-native-async-storage/async-storage.ts b/packages/event-processor/__mocks__/@react-native-async-storage/async-storage.ts deleted file mode 100644 index 8f9a3a2f3..000000000 --- a/packages/event-processor/__mocks__/@react-native-async-storage/async-storage.ts +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -let items: {[key: string]: string} = {} - -export default class AsyncStorage { - - static getItem(key: string, callback?: (error?: Error, result?: string) => void): Promise<string | null> { - return new Promise(resolve => { - setTimeout(() => resolve(items[key] || null), 1) - }) - } - - static setItem(key: string, value: string, callback?: (error?: Error) => void): Promise<void> { - return new Promise((resolve) => { - setTimeout(() => { - items[key] = value - resolve() - }, 1) - }) - } - - static removeItem(key: string, callback?: (error?: Error, result?: string) => void): Promise<string | null> { - return new Promise(resolve => { - setTimeout(() => { - items[key] && delete items[key] - resolve(null) - }, 1) - }) - } - - static dumpItems(): {[key: string]: string} { - return items - } - - static clearStore(): void { - items = {} - } -} diff --git a/packages/event-processor/__tests__/buildEventV1.spec.ts b/packages/event-processor/__tests__/buildEventV1.spec.ts deleted file mode 100644 index 79d77447f..000000000 --- a/packages/event-processor/__tests__/buildEventV1.spec.ts +++ /dev/null @@ -1,812 +0,0 @@ -/** - * Copyright 2019, 2021, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/// <reference types="jest" /> - -import { - buildConversionEventV1, - buildImpressionEventV1, - makeBatchedEventV1, -} from '../src/v1/buildEventV1' -import { ImpressionEvent, ConversionEvent } from '../src/events' - -describe('buildEventV1', () => { - describe('buildImpressionEventV1', () => { - it('should build an ImpressionEventV1 when experiment and variation are defined', () => { - const impressionEvent: ImpressionEvent = { - type: 'impression', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - layer: { - id: 'layerId', - }, - - experiment: { - id: 'expId', - key: 'expKey', - }, - - variation: { - id: 'varId', - key: 'varKey', - }, - - ruleKey: 'expKey', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: true, - } - - const result = buildImpressionEventV1(impressionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - decisions: [ - { - campaign_id: 'layerId', - experiment_id: 'expId', - variation_id: 'varId', - metadata: { - flag_key: 'flagKey1', - rule_key: 'expKey', - rule_type: 'experiment', - variation_key: 'varKey', - enabled: true, - }, - }, - ], - events: [ - { - entity_id: 'layerId', - timestamp: 69, - key: 'campaign_activated', - uuid: 'uuid', - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - - it('should build an ImpressionEventV1 when experiment and variation are not defined', () => { - const impressionEvent: ImpressionEvent = { - type: 'impression', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - layer: { - id: null, - }, - - experiment: { - id: null, - key: '', - }, - - variation: { - id: null, - key: '', - }, - - ruleKey: '', - flagKey: 'flagKey1', - ruleType: 'rollout', - enabled: true, - } - - const result = buildImpressionEventV1(impressionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - decisions: [ - { - campaign_id: null, - experiment_id: "", - variation_id: "", - metadata: { - flag_key: 'flagKey1', - rule_key: '', - rule_type: 'rollout', - variation_key: '', - enabled: true, - }, - }, - ], - events: [ - { - entity_id: null, - timestamp: 69, - key: 'campaign_activated', - uuid: 'uuid', - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - }) - - describe('buildConversionEventV1', () => { - it('should build a ConversionEventV1 when tags object is defined', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - - revenue: 1000, - value: 123, - } - - const result = buildConversionEventV1(conversionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - events: [ - { - entity_id: 'event-id', - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - revenue: 1000, - value: 123, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - - it('should build a ConversionEventV1 when tags object is undefined', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: undefined, - - revenue: 1000, - value: 123, - } - - const result = buildConversionEventV1(conversionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - events: [ - { - entity_id: 'event-id', - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: undefined, - revenue: 1000, - value: 123, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - - it('should build a ConversionEventV1 when event id is null', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: null, - key: 'event-key', - }, - - tags: undefined, - - revenue: 1000, - value: 123, - } - - const result = buildConversionEventV1(conversionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - events: [ - { - entity_id: null, - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: undefined, - revenue: 1000, - value: 123, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - - it('should include revenue and value if they are 0', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: 0, - revenue: 0, - }, - - revenue: 0, - value: 0, - } - - const result = buildConversionEventV1(conversionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - events: [ - { - entity_id: 'event-id', - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: { - foo: 'bar', - value: 0, - revenue: 0, - }, - revenue: 0, - value: 0, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - - it('should not include $opt_bot_filtering attribute if context.botFiltering is undefined', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - - revenue: 1000, - value: 123, - } - - const result = buildConversionEventV1(conversionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - events: [ - { - entity_id: 'event-id', - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - revenue: 1000, - value: 123, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - ], - }, - ], - }) - }) - }) - - describe('makeBatchedEventV1', () => { - it('should batch Conversion and Impression events together', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - - revenue: 1000, - value: 123, - } - - const impressionEvent: ImpressionEvent = { - type: 'impression', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - layer: { - id: 'layerId', - }, - - experiment: { - id: 'expId', - key: 'expKey', - }, - - variation: { - id: 'varId', - key: 'varKey', - }, - - ruleKey: 'expKey', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: true, - } - - const result = makeBatchedEventV1([impressionEvent, conversionEvent]) - - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - decisions: [ - { - campaign_id: 'layerId', - experiment_id: 'expId', - variation_id: 'varId', - metadata: { - flag_key: 'flagKey1', - rule_key: 'expKey', - rule_type: 'experiment', - variation_key: 'varKey', - enabled: true, - }, - }, - ], - events: [ - { - entity_id: 'layerId', - timestamp: 69, - key: 'campaign_activated', - uuid: 'uuid', - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - { - snapshots: [ - { - events: [ - { - entity_id: 'event-id', - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - revenue: 1000, - value: 123, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - }) -}) diff --git a/packages/event-processor/__tests__/eventQueue.spec.ts b/packages/event-processor/__tests__/eventQueue.spec.ts deleted file mode 100644 index 71aaa415f..000000000 --- a/packages/event-processor/__tests__/eventQueue.spec.ts +++ /dev/null @@ -1,290 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/// <reference types="jest" /> - -import { DefaultEventQueue, SingleEventQueue } from '../src/eventQueue' - -describe('eventQueue', () => { - beforeEach(() => { - jest.useFakeTimers() - }) - - afterEach(() => { - jest.useRealTimers() - jest.resetAllMocks() - }) - - describe('SingleEventQueue', () => { - it('should immediately invoke the sink function when items are enqueued', () => { - const sinkFn = jest.fn() - const queue = new SingleEventQueue<number>({ - sink: sinkFn, - }) - - queue.start() - - queue.enqueue(1) - - expect(sinkFn).toBeCalledTimes(1) - expect(sinkFn).toHaveBeenLastCalledWith([1]) - - queue.enqueue(2) - expect(sinkFn).toBeCalledTimes(2) - expect(sinkFn).toHaveBeenLastCalledWith([2]) - - queue.stop() - }) - }) - - describe('DefaultEventQueue', () => { - it('should treat maxQueueSize = -1 as 1', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: -1, - sink: sinkFn, - batchComparator: () => true - }) - - queue.start() - - queue.enqueue(1) - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - queue.enqueue(2) - expect(sinkFn).toHaveBeenCalledTimes(2) - expect(sinkFn).toHaveBeenCalledWith([2]) - - queue.stop() - }) - - it('should treat maxQueueSize = 0 as 1', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 0, - sink: sinkFn, - batchComparator: () => true - }) - - queue.start() - - queue.enqueue(1) - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - queue.enqueue(2) - expect(sinkFn).toHaveBeenCalledTimes(2) - expect(sinkFn).toHaveBeenCalledWith([2]) - - queue.stop() - }) - - it('should invoke the sink function when maxQueueSize is reached', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 3, - sink: sinkFn, - batchComparator: () => true - }) - - queue.start() - - queue.enqueue(1) - queue.enqueue(2) - expect(sinkFn).not.toHaveBeenCalled() - - queue.enqueue(3) - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1, 2, 3]) - - queue.enqueue(4) - queue.enqueue(5) - queue.enqueue(6) - expect(sinkFn).toHaveBeenCalledTimes(2) - expect(sinkFn).toHaveBeenCalledWith([4, 5, 6]) - - queue.stop() - }) - - it('should invoke the sink function when the interval has expired', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - batchComparator: () => true - }) - - queue.start() - - queue.enqueue(1) - queue.enqueue(2) - expect(sinkFn).not.toHaveBeenCalled() - - jest.advanceTimersByTime(100) - - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1, 2]) - - queue.enqueue(3) - jest.advanceTimersByTime(100) - - expect(sinkFn).toHaveBeenCalledTimes(2) - expect(sinkFn).toHaveBeenCalledWith([3]) - - queue.stop() - }) - - it('should invoke the sink function when an item incompatable with the current batch (according to batchComparator) is received', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<string>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - // This batchComparator returns true when the argument strings start with the same letter - batchComparator: (s1, s2) => s1[0] === s2[0] - }) - - queue.start() - - queue.enqueue('a1') - queue.enqueue('a2') - // After enqueuing these strings, both starting with 'a', the sinkFn should not yet be called. Thus far all the items enqueued are - // compatible according to the batchComparator. - expect(sinkFn).not.toHaveBeenCalled() - - // Enqueuing a string starting with 'b' should cause the sinkFn to be called - queue.enqueue('b1') - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith(['a1', 'a2']) - }) - - it('stop() should flush the existing queue and call timer.stop()', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - batchComparator: () => true - }) - - jest.spyOn(queue.timer, 'stop') - - queue.start() - queue.enqueue(1) - - // stop + start is called when the first item is enqueued - expect(queue.timer.stop).toHaveBeenCalledTimes(1) - - queue.stop() - - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - expect(queue.timer.stop).toHaveBeenCalledTimes(2) - }) - - it('flush() should clear the current batch', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - batchComparator: () => true - }) - - jest.spyOn(queue.timer, 'refresh') - - queue.start() - queue.enqueue(1) - queue.flush() - - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - expect(queue.timer.refresh).toBeCalledTimes(1) - - queue.stop() - }) - - it('stop() should return a promise', () => { - const promise = Promise.resolve() - const sinkFn = jest.fn().mockReturnValue(promise) - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - batchComparator: () => true - }) - - expect(queue.stop()).toBe(promise) - }) - - it('should start the timer when the first event is put into the queue', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - batchComparator: () => true - }) - - queue.start() - jest.advanceTimersByTime(99) - queue.enqueue(1) - - jest.advanceTimersByTime(2) - expect(sinkFn).toHaveBeenCalledTimes(0) - jest.advanceTimersByTime(98) - - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - - jest.advanceTimersByTime(500) - // ensure sink function wasnt called again since no events have - // been added - expect(sinkFn).toHaveBeenCalledTimes(1) - - queue.enqueue(2) - - jest.advanceTimersByTime(100) - expect(sinkFn).toHaveBeenCalledTimes(2) - expect(sinkFn).toHaveBeenLastCalledWith([2]) - - queue.stop() - - }) - - it('should not enqueue additional events after stop() is called', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 30000, - maxQueueSize: 3, - sink: sinkFn, - batchComparator: () => true - }) - queue.start() - queue.enqueue(1) - queue.stop() - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - sinkFn.mockClear() - queue.enqueue(2) - queue.enqueue(3) - queue.enqueue(4) - expect(sinkFn).toBeCalledTimes(0) - }) - }) -}) diff --git a/packages/event-processor/__tests__/pendingEventsDispatcher.spec.ts b/packages/event-processor/__tests__/pendingEventsDispatcher.spec.ts deleted file mode 100644 index 415afdf11..000000000 --- a/packages/event-processor/__tests__/pendingEventsDispatcher.spec.ts +++ /dev/null @@ -1,261 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/// <reference types="jest" /> - -jest.mock('@optimizely/js-sdk-utils', () => ({ - __esModule: true, - generateUUID: jest.fn(), - getTimestamp: jest.fn(), - objectValues: jest.requireActual('@optimizely/js-sdk-utils').objectValues, -})) - -import { - LocalStoragePendingEventsDispatcher, - PendingEventsDispatcher, - DispatcherEntry, -} from '../src/pendingEventsDispatcher' -import { EventDispatcher, EventV1Request } from '../src/eventDispatcher' -import { EventV1 } from '../src/v1/buildEventV1' -import { PendingEventsStore, LocalStorageStore } from '../src/pendingEventsStore' -import { generateUUID, getTimestamp } from '@optimizely/js-sdk-utils' - -describe('LocalStoragePendingEventsDispatcher', () => { - let originalEventDispatcher: EventDispatcher - let pendingEventsDispatcher: PendingEventsDispatcher - - beforeEach(() => { - originalEventDispatcher = { - dispatchEvent: jest.fn(), - } - pendingEventsDispatcher = new LocalStoragePendingEventsDispatcher({ - eventDispatcher: originalEventDispatcher, - }) - ;((getTimestamp as unknown) as jest.Mock).mockReturnValue(1) - ;((generateUUID as unknown) as jest.Mock).mockReturnValue('uuid') - }) - - afterEach(() => { - localStorage.clear() - }) - - it('should properly send the events to the passed in eventDispatcher, when callback statusCode=200', () => { - const callback = jest.fn() - const eventV1Request: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event' } as unknown) as EventV1, - } - - pendingEventsDispatcher.dispatchEvent(eventV1Request, callback) - - expect(callback).not.toHaveBeenCalled() - // manually invoke original eventDispatcher callback - const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) - .mock.calls[0] - internalDispatchCall[1]({ statusCode: 200 }) - - // assert that the original dispatch function was called with the request - expect((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock).toBeCalledTimes(1) - expect(internalDispatchCall[0]).toEqual(eventV1Request) - - // assert that the passed in callback to pendingEventsDispatcher was called - expect(callback).toHaveBeenCalledTimes(1) - expect(callback).toHaveBeenCalledWith({ statusCode: 200 }) - }) - - it('should properly send the events to the passed in eventDispatcher, when callback statusCode=400', () => { - const callback = jest.fn() - const eventV1Request: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event' } as unknown) as EventV1, - } - - pendingEventsDispatcher.dispatchEvent(eventV1Request, callback) - - expect(callback).not.toHaveBeenCalled() - // manually invoke original eventDispatcher callback - const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) - .mock.calls[0] - internalDispatchCall[1]({ statusCode: 400 }) - - // assert that the original dispatch function was called with the request - expect((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock).toBeCalledTimes(1) - expect(internalDispatchCall[0]).toEqual(eventV1Request) - - // assert that the passed in callback to pendingEventsDispatcher was called - expect(callback).toHaveBeenCalledTimes(1) - expect(callback).toHaveBeenCalledWith({ statusCode: 400}) - }) -}) - -describe('PendingEventsDispatcher', () => { - let originalEventDispatcher: EventDispatcher - let pendingEventsDispatcher: PendingEventsDispatcher - let store: PendingEventsStore<DispatcherEntry> - - beforeEach(() => { - originalEventDispatcher = { - dispatchEvent: jest.fn(), - } - store = new LocalStorageStore({ - key: 'test', - maxValues: 3, - }) - pendingEventsDispatcher = new PendingEventsDispatcher({ - store, - eventDispatcher: originalEventDispatcher, - }) - ;((getTimestamp as unknown) as jest.Mock).mockReturnValue(1) - ;((generateUUID as unknown) as jest.Mock).mockReturnValue('uuid') - }) - - afterEach(() => { - localStorage.clear() - }) - - describe('dispatch', () => { - describe('when the dispatch is successful', () => { - it('should save the pendingEvent to the store and remove it once dispatch is completed', () => { - const callback = jest.fn() - const eventV1Request: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event' } as unknown) as EventV1, - } - - pendingEventsDispatcher.dispatchEvent(eventV1Request, callback) - - expect(store.values()).toHaveLength(1) - expect(store.get('uuid')).toEqual({ - uuid: 'uuid', - timestamp: 1, - request: eventV1Request, - }) - expect(callback).not.toHaveBeenCalled() - - // manually invoke original eventDispatcher callback - const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) - .mock.calls[0] - const internalCallback = internalDispatchCall[1]({ statusCode: 200 }) - - // assert that the original dispatch function was called with the request - expect( - (originalEventDispatcher.dispatchEvent as unknown) as jest.Mock, - ).toBeCalledTimes(1) - expect(internalDispatchCall[0]).toEqual(eventV1Request) - - // assert that the passed in callback to pendingEventsDispatcher was called - expect(callback).toHaveBeenCalledTimes(1) - expect(callback).toHaveBeenCalledWith({ statusCode: 200 }) - - expect(store.values()).toHaveLength(0) - }) - }) - - describe('when the dispatch is unsuccessful', () => { - it('should save the pendingEvent to the store and remove it once dispatch is completed', () => { - const callback = jest.fn() - const eventV1Request: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event' } as unknown) as EventV1, - } - - pendingEventsDispatcher.dispatchEvent(eventV1Request, callback) - - expect(store.values()).toHaveLength(1) - expect(store.get('uuid')).toEqual({ - uuid: 'uuid', - timestamp: 1, - request: eventV1Request, - }) - expect(callback).not.toHaveBeenCalled() - - // manually invoke original eventDispatcher callback - const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) - .mock.calls[0] - internalDispatchCall[1]({ statusCode: 400 }) - - // assert that the original dispatch function was called with the request - expect( - (originalEventDispatcher.dispatchEvent as unknown) as jest.Mock, - ).toBeCalledTimes(1) - expect(internalDispatchCall[0]).toEqual(eventV1Request) - - // assert that the passed in callback to pendingEventsDispatcher was called - expect(callback).toHaveBeenCalledTimes(1) - expect(callback).toHaveBeenCalledWith({ statusCode: 400 }) - - expect(store.values()).toHaveLength(0) - }) - }) - }) - - describe('sendPendingEvents', () => { - describe('when no pending events are in the store', () => { - it('should not invoked dispatch', () => { - expect(store.values()).toHaveLength(0) - - pendingEventsDispatcher.sendPendingEvents() - expect(originalEventDispatcher.dispatchEvent).not.toHaveBeenCalled() - }) - }) - - describe('when there are multiple pending events in the store', () => { - it('should dispatch all of the pending events, and remove them from store', () => { - expect(store.values()).toHaveLength(0) - - const callback = jest.fn() - const eventV1Request1: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event1' } as unknown) as EventV1, - } - - const eventV1Request2: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event2' } as unknown) as EventV1, - } - - store.set('uuid1', { - uuid: 'uuid1', - timestamp: 1, - request: eventV1Request1, - }) - store.set('uuid2', { - uuid: 'uuid2', - timestamp: 2, - request: eventV1Request2, - }) - - expect(store.values()).toHaveLength(2) - - pendingEventsDispatcher.sendPendingEvents() - expect(originalEventDispatcher.dispatchEvent).toHaveBeenCalledTimes(2) - - // manually invoke original eventDispatcher callback - const internalDispatchCalls = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) - .mock.calls - internalDispatchCalls[0][1]({ statusCode: 200 }) - internalDispatchCalls[1][1]({ statusCode: 200 }) - - expect(store.values()).toHaveLength(0) - }) - }) - }) -}) diff --git a/packages/event-processor/__tests__/pendingEventsStore.spec.ts b/packages/event-processor/__tests__/pendingEventsStore.spec.ts deleted file mode 100644 index 780b5ae13..000000000 --- a/packages/event-processor/__tests__/pendingEventsStore.spec.ts +++ /dev/null @@ -1,143 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/// <reference types="jest" /> - -import { LocalStorageStore } from '../src/pendingEventsStore' - -type TestEntry = { - uuid: string - timestamp: number - value: string -} - -describe('LocalStorageStore', () => { - let store: LocalStorageStore<TestEntry> - beforeEach(() => { - store = new LocalStorageStore({ - key: 'test_key', - maxValues: 3, - }) - }) - - afterEach(() => { - localStorage.clear() - }) - - it('should get, set and remove items', () => { - store.set('1', { - uuid: '1', - timestamp: 1, - value: 'first', - }) - - expect(store.get('1')).toEqual({ - uuid: '1', - timestamp: 1, - value: 'first', - }) - - store.set('1', { - uuid: '1', - timestamp: 2, - value: 'second', - }) - - expect(store.get('1')).toEqual({ - uuid: '1', - timestamp: 2, - value: 'second', - }) - - expect(store.values()).toHaveLength(1) - - store.remove('1') - - expect(store.values()).toHaveLength(0) - }) - - it('should allow replacement of the entire map', () => { - store.set('1', { - uuid: '1', - timestamp: 1, - value: 'first', - }) - - store.set('2', { - uuid: '2', - timestamp: 2, - value: 'second', - }) - - store.set('3', { - uuid: '3', - timestamp: 3, - value: 'third', - }) - - expect(store.values()).toEqual([ - { uuid: '1', timestamp: 1, value: 'first' }, - { uuid: '2', timestamp: 2, value: 'second' }, - { uuid: '3', timestamp: 3, value: 'third' }, - ]) - - const newMap: { [key: string]: TestEntry } = {} - store.values().forEach(item => { - newMap[item.uuid] = { - ...item, - value: 'new', - } - }) - store.replace(newMap) - - expect(store.values()).toEqual([ - { uuid: '1', timestamp: 1, value: 'new' }, - { uuid: '2', timestamp: 2, value: 'new' }, - { uuid: '3', timestamp: 3, value: 'new' }, - ]) - }) - - it(`shouldn't allow more than the configured maxValues, using timestamp to remove the oldest entries`, () => { - store.set('2', { - uuid: '2', - timestamp: 2, - value: 'second', - }) - - store.set('3', { - uuid: '3', - timestamp: 3, - value: 'third', - }) - - store.set('1', { - uuid: '1', - timestamp: 1, - value: 'first', - }) - - store.set('4', { - uuid: '4', - timestamp: 4, - value: 'fourth', - }) - - expect(store.values()).toEqual([ - { uuid: '2', timestamp: 2, value: 'second' }, - { uuid: '3', timestamp: 3, value: 'third' }, - { uuid: '4', timestamp: 4, value: 'fourth' }, - ]) - }) -}) diff --git a/packages/event-processor/__tests__/reactNativeEventsStore.spec.ts b/packages/event-processor/__tests__/reactNativeEventsStore.spec.ts deleted file mode 100644 index d2aff8500..000000000 --- a/packages/event-processor/__tests__/reactNativeEventsStore.spec.ts +++ /dev/null @@ -1,219 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/// <reference types="jest" /> - -import { ReactNativeEventsStore } from '../src/reactNativeEventsStore' -import AsyncStorage from '../__mocks__/@react-native-async-storage/async-storage' - -const STORE_KEY = 'test-store' - -describe('ReactNativeEventsStore', () => { - let store: ReactNativeEventsStore<any> - - beforeEach(() => { - store = new ReactNativeEventsStore(5, STORE_KEY) - }) - - describe('set', () => { - it('should store all the events correctly in the store', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - const storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - }) - - it('should store all the events when set asynchronously', async (done) => { - const promises = [] - promises.push(store.set('event1', {'name': 'event1'})) - promises.push(store.set('event2', {'name': 'event2'})) - promises.push(store.set('event3', {'name': 'event3'})) - promises.push(store.set('event4', {'name': 'event4'})) - Promise.all(promises).then(() => { - const storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - done() - }) - }) - }) - - describe('get', () => { - it('should correctly get items', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - expect(await store.get('event1')).toEqual({'name': 'event1'}) - expect(await store.get('event2')).toEqual({'name': 'event2'}) - expect(await store.get('event3')).toEqual({'name': 'event3'}) - expect(await store.get('event4')).toEqual({'name': 'event4'}) - }) - }) - - describe('getEventsMap', () => { - it('should get the whole map correctly', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - const mapResult = await store.getEventsMap() - expect(mapResult).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - }) - }) - - describe('getEventsList', () => { - it('should get all the events as a list', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - const listResult = await store.getEventsList() - expect(listResult).toEqual([ - { "name": "event1" }, - { "name": "event2" }, - { "name": "event3" }, - { "name": "event4" }, - ]) - }) - }) - - describe('remove', () => { - it('should correctly remove items from the store', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - - await store.remove('event1') - storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - - await store.remove('event2') - storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - }) - - it('should correctly remove items from the store when removed asynchronously', async (done) => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - - const promises = [] - promises.push(store.remove('event1')) - promises.push(store.remove('event2')) - promises.push(store.remove('event3')) - Promise.all(promises).then(() => { - let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ "event4": { "name": "event4" }}) - done() - }) - }) - }) - - describe('clear', () => { - it('should clear the whole store',async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - await store.clear() - storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY] || '{}') - expect(storedPendingEvents).toEqual({}) - }) - }) - - describe('maxSize', () => { - it('should not add anymore events if the store if full', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - - let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - await store.set('event5', {'name': 'event5'}) - - storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - "event5": { "name": "event5" }, - }) - - await store.set('event6', {'name': 'event6'}) - storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - "event5": { "name": "event5" }, - }) - }) - }) -}) diff --git a/packages/event-processor/__tests__/requestTracker.spec.ts b/packages/event-processor/__tests__/requestTracker.spec.ts deleted file mode 100644 index f6318de50..000000000 --- a/packages/event-processor/__tests__/requestTracker.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import RequestTracker from '../src/requestTracker' - -describe('requestTracker', () => { - describe('onRequestsComplete', () => { - it('returns an immediately-fulfilled promise when no requests are in flight', async () => { - const tracker = new RequestTracker() - await tracker.onRequestsComplete() - }) - - it('returns a promise that fulfills after in-flight requests are complete', async () => { - let resolveReq1: () => void - const req1 = new Promise<void>(resolve => { - resolveReq1 = resolve - }) - let resolveReq2: () => void - const req2 = new Promise<void>(resolve => { - resolveReq2 = resolve - }) - let resolveReq3: () => void - const req3 = new Promise<void>(resolve => { - resolveReq3 = resolve - }) - - const tracker = new RequestTracker() - tracker.trackRequest(req1) - tracker.trackRequest(req2) - tracker.trackRequest(req3) - - let reqsComplete = false - const reqsCompletePromise = tracker.onRequestsComplete().then(() => { - reqsComplete = true - }) - - resolveReq1!() - await req1 - expect(reqsComplete).toBe(false) - - resolveReq2!() - await req2 - expect(reqsComplete).toBe(false) - - resolveReq3!() - await req3 - await reqsCompletePromise - expect(reqsComplete).toBe(true) - }) - }) -}) diff --git a/packages/event-processor/__tests__/v1EventProcessor.react_native.spec.ts b/packages/event-processor/__tests__/v1EventProcessor.react_native.spec.ts deleted file mode 100644 index abc1370cd..000000000 --- a/packages/event-processor/__tests__/v1EventProcessor.react_native.spec.ts +++ /dev/null @@ -1,886 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/// <reference types="jest" /> -import { NotificationCenter, NOTIFICATION_TYPES } from '@optimizely/js-sdk-utils' - -import { LogTierV1EventProcessor } from '../src/v1/v1EventProcessor.react_native' -import { - EventDispatcher, - EventV1Request, - EventDispatcherCallback, -} from '../src/eventDispatcher' -import { EventProcessor, ProcessableEvent } from '../src/eventProcessor' -import { buildImpressionEventV1, makeBatchedEventV1 } from '../src/v1/buildEventV1' -import AsyncStorage from '../__mocks__/@react-native-async-storage/async-storage' -import { triggerInternetState } from '../__mocks__/@react-native-community/netinfo' -import { DefaultEventQueue } from '../src/eventQueue' - -function createImpressionEvent() { - return { - type: 'impression' as 'impression', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: '1', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - layer: { - id: 'layerId', - }, - - experiment: { - id: 'expId', - key: 'expKey', - }, - - variation: { - id: 'varId', - key: 'varKey', - }, - - ruleKey: 'expKey', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: false, - } -} - -function createConversionEvent() { - return { - type: 'conversion' as 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: '1', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - - revenue: 1000, - value: 123, - } -} - -describe('LogTierV1EventProcessorReactNative', () => { - describe('New Events', () => { - let stubDispatcher: EventDispatcher - let dispatchStub: jest.Mock - - beforeEach(() => { - dispatchStub = jest.fn() - - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - callback({ statusCode: 200 }) - }, - } - }) - - afterEach(() => { - jest.resetAllMocks() - AsyncStorage.clearStore() - }) - - describe('stop()', () => { - let localCallback: EventDispatcherCallback - beforeEach(async () => { - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - localCallback = callback - }, - } - }) - - it('should return a resolved promise when there is nothing in queue', async () => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - - await processor.start() - - await processor.stop() - }) - - it('should return a promise that is resolved when the dispatcher callback returns a 200 response', async (done) => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - await processor.start() - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - await new Promise(resolve => setTimeout(resolve, 150)) - processor.stop().then(() => { - done() - }) - - localCallback({ statusCode: 200 }) - }) - - it('should return a promise that is resolved when the dispatcher callback returns a 400 response', async (done) => { - // This test is saying that even if the request fails to send but - // the `dispatcher` yielded control back, then the `.stop()` promise should be resolved - let localCallback: any - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - localCallback = callback - }, - } - - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - await processor.start() - - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - await new Promise(resolve => setTimeout(resolve, 150)) - processor.stop().then(() => { - done() - }) - - localCallback({ statusCode: 400 }) - }) - - it('should return a promise when multiple event batches are sent', async () => { - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - callback({ statusCode: 200 }) - }, - } - - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - - await processor.start() - - const impressionEvent1 = createImpressionEvent() - const impressionEvent2 = createImpressionEvent() - impressionEvent2.context.revision = '2' - processor.process(impressionEvent1) - processor.process(impressionEvent2) - - await new Promise(resolve => setTimeout(resolve, 150)) - await processor.stop() - expect(dispatchStub).toBeCalledTimes(2) - }) - - it('should stop accepting events after stop is called', async () => { - const dispatcher = { - dispatchEvent: jest.fn((event: EventV1Request, callback: EventDispatcherCallback) => { - setTimeout(() => callback({ statusCode: 204 }), 0) - }) - } - const processor = new LogTierV1EventProcessor({ - dispatcher, - flushInterval: 100, - batchSize: 3, - }) - await processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - await new Promise(resolve => setTimeout(resolve, 150)) - - await processor.stop() - // calling stop should haver flushed the current batch of size 1 - expect(dispatcher.dispatchEvent).toBeCalledTimes(1) - - dispatcher.dispatchEvent.mockClear(); - - // From now on, subsequent events should be ignored. - // Process 3 more, which ordinarily would have triggered - // a flush due to the batch size. - const impressionEvent2 = createImpressionEvent() - processor.process(impressionEvent2) - const impressionEvent3 = createImpressionEvent() - processor.process(impressionEvent3) - const impressionEvent4 = createImpressionEvent() - processor.process(impressionEvent4) - // Since we already stopped the processor, the dispatcher should - // not have been called again. - await new Promise(resolve => setTimeout(resolve, 150)) - expect(dispatcher.dispatchEvent).toBeCalledTimes(0) - }) - }) - - describe('when batchSize = 1', () => { - let processor: EventProcessor - beforeEach(async () => { - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - await processor.start() - }) - - afterEach(async () => { - await processor.stop() - }) - - it('should immediately flush events as they are processed', async () => { - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - await new Promise(resolve => setTimeout(resolve, 50)) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: buildImpressionEventV1(impressionEvent), - }) - }) - }) - - describe('when batchSize = 3, flushInterval = 300', () => { - let processor: EventProcessor - beforeEach(async () => { - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 300, - batchSize: 3, - }) - await processor.start() - }) - - afterEach(async () => { - await processor.stop() - }) - - it('should wait until 3 events to be in the queue before it flushes', async () => { - const impressionEvent1 = createImpressionEvent() - const impressionEvent2 = createImpressionEvent() - const impressionEvent3 = createImpressionEvent() - - processor.process(impressionEvent1) - processor.process(impressionEvent2) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent3) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([ - impressionEvent1, - impressionEvent2, - impressionEvent3, - ]), - }) - }) - - it('should flush the current batch when it receives an event with a different context revision than the current batch', async () => { - const impressionEvent1 = createImpressionEvent() - const conversionEvent = createConversionEvent() - const impressionEvent2 = createImpressionEvent() - - // createImpressionEvent and createConversionEvent create events with revision '1' - // We modify this one's revision to '2' in order to test that the queue is flushed - // when an event with a different revision is processed. - impressionEvent2.context.revision = '2' - - processor.process(impressionEvent1) - processor.process(conversionEvent) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent2) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1, conversionEvent]), - }) - }) - - it('should flush the current batch when it receives an event with a different context projectId than the current batch', async () => { - const impressionEvent1 = createImpressionEvent() - const conversionEvent = createConversionEvent() - const impressionEvent2 = createImpressionEvent() - - impressionEvent2.context.projectId = 'projectId2' - - processor.process(impressionEvent1) - processor.process(conversionEvent) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent2) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1, conversionEvent]), - }) - }) - - it('should flush the queue when the flush interval happens', async () => { - const impressionEvent1 = createImpressionEvent() - - processor.process(impressionEvent1) - - expect(dispatchStub).toHaveBeenCalledTimes(0) - - await new Promise(resolve => setTimeout(resolve, 350)) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1]), - }) - - processor.process(createImpressionEvent()) - processor.process(createImpressionEvent()) - // flushing should reset queue, at this point only has two events - expect(dispatchStub).toHaveBeenCalledTimes(1) - }) - }) - - describe('when a notification center is provided', () => { - it('should trigger a notification when the event dispatcher dispatches an event', async () => { - const dispatcher: EventDispatcher = { - dispatchEvent: jest.fn() - } - - const notificationCenter: NotificationCenter = { - sendNotifications: jest.fn() - } - - const processor = new LogTierV1EventProcessor({ - dispatcher, - notificationCenter, - batchSize: 1, - }) - await processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - - await new Promise(resolve => setTimeout(resolve, 150)) - expect(notificationCenter.sendNotifications).toBeCalledTimes(1) - const event = (dispatcher.dispatchEvent as jest.Mock).mock.calls[0][0] - expect(notificationCenter.sendNotifications).toBeCalledWith(NOTIFICATION_TYPES.LOG_EVENT, event) - }) - }) - - describe('invalid batchSize', () => { - it('should ignore a batchSize of 0 and use the default', async () => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 30000, - batchSize: 0, - }) - await processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - - await new Promise(resolve => setTimeout(resolve, 150)) - expect(dispatchStub).toHaveBeenCalledTimes(0) - const impressionEvents = [impressionEvent1] - for (let i = 0; i < 9; i++) { - const evt = createImpressionEvent() - processor.process(evt) - impressionEvents.push(evt) - } - - await new Promise(resolve => setTimeout(resolve, 150)) - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1(impressionEvents), - }) - }) - }) - }) - - describe('Pending Events', () => { - let stubDispatcher: EventDispatcher - let dispatchStub: jest.Mock - - beforeEach(() => { - dispatchStub = jest.fn() - }) - - afterEach(() => { - jest.clearAllMocks() - AsyncStorage.clearStore() - }) - - describe('Retry Pending Events', () => { - describe('App start', () => { - it('should dispatch all the pending events in correct order', async () => { - let receivedEvents: EventV1Request[] = [] - - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - callback({ statusCode: 400 }) - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - let event1 = createConversionEvent() - event1.user.id = 'user1' - let event2 = createConversionEvent() - event2.user.id = 'user2' - let event3 = createConversionEvent() - event3.user.id = 'user3' - let event4 = createConversionEvent() - event4.user.id = 'user4' - - processor.process(event1) - processor.process(event2) - processor.process(event3) - processor.process(event4) - - await new Promise(resolve => setTimeout(resolve, 100)) - - expect(dispatchStub).toBeCalledTimes(4) - - await processor.stop() - - jest.clearAllMocks() - - receivedEvents = [] - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - receivedEvents.push(event) - dispatchStub(event) - callback({ statusCode: 200 }) - }, - } - - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - - receivedEvents.forEach((e, i) => { - expect(e.params.visitors[0].visitor_id).toEqual(`user${i+1}`) - }) - - expect(dispatchStub).toBeCalledTimes(4) - - await processor.stop() - }) - - it('should process all the events left in buffer when the app closed last time', async () => { - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - callback({ statusCode: 200 }) - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 1000, - batchSize: 4, - }) - - await processor.start() - let event1 = createConversionEvent() - event1.user.id = 'user1' - event1.uuid = 'user1' - let event2 = createConversionEvent() - event2.user.id = 'user2' - event2.uuid = 'user2' - - processor.process(event1) - processor.process(event2) - - await new Promise(resolve => setTimeout(resolve, 100)) - - // Explicitly stopping the timer to simulate app close - ;(processor.queue as DefaultEventQueue<ProcessableEvent>).timer.stop() - - let receivedEvents: EventV1Request[] = [] - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - receivedEvents.push(event) - dispatchStub(event) - callback({ statusCode: 200 }) - }, - } - - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 4, - }) - - await processor.start() - - await new Promise(resolve => setTimeout(resolve, 150)) - expect(dispatchStub).toBeCalledTimes(1) - expect(receivedEvents.length).toEqual(1) - const receivedEvent = receivedEvents[0] - - receivedEvent.params.visitors.forEach((v, i) => { - expect(v.visitor_id).toEqual(`user${i+1}`) - }) - - await processor.stop() - }) - - it('should dispatch pending events first and then process events in buffer store', async () => { - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - callback({ statusCode: 400 }) - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 300, - batchSize: 3, - }) - - await processor.start() - - for (let i = 0; i < 8; i++) { - let event = createConversionEvent() - event.user.id = `user${i}` - event.uuid = `user${i}` - processor.process(event) - } - - await new Promise(resolve => setTimeout(resolve, 50)) - - expect(dispatchStub).toBeCalledTimes(2) - - ;(processor.queue as DefaultEventQueue<ProcessableEvent>).timer.stop() - - jest.clearAllMocks() - - const visitorIds: string[] = [] - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - event.params.visitors.forEach(visitor => visitorIds.push(visitor.visitor_id)) - callback({ statusCode: 200 }) - }, - } - - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 200, - batchSize: 3, - }) - - await processor.start() - - expect(dispatchStub).toBeCalledTimes(2) - - await new Promise(resolve => setTimeout(resolve, 250)) - expect(visitorIds.length).toEqual(8) - expect(visitorIds).toEqual(['user0', 'user1', 'user2', 'user3', 'user4', 'user5', 'user6', 'user7']) - }) - }) - - describe('When a new event is dispatched', () => { - it('should dispatch all the pending events first and then new event in correct order', async () => { - let receivedVisitorIds: string[] = [] - let dispatchCount = 0 - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - dispatchCount++ - if (dispatchCount > 4) { - event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id)) - callback({ statusCode: 200 }) - } else { - callback({ statusCode: 400 }) - } - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - let event1 = createConversionEvent() - event1.user.id = event1.uuid = 'user1' - let event2 = createConversionEvent() - event2.user.id = event2.uuid = 'user2' - let event3 = createConversionEvent() - event3.user.id = event3.uuid = 'user3' - let event4 = createConversionEvent() - event4.user.id = event4.uuid = 'user4' - - processor.process(event1) - processor.process(event2) - processor.process(event3) - processor.process(event4) - - await new Promise(resolve => setTimeout(resolve, 100)) - - // Four events will return response code 400 which means only the first pending event will be tried each time and rest will be skipped - expect(dispatchStub).toBeCalledTimes(4) - - jest.resetAllMocks() - - let event5 = createConversionEvent() - event5.user.id = event5.uuid = 'user5' - - processor.process(event5) - - await new Promise(resolve => setTimeout(resolve, 100)) - expect(dispatchStub).toBeCalledTimes(5) - expect(receivedVisitorIds).toEqual(['user1', 'user2', 'user3', 'user4', 'user5']) - await processor.stop() - }) - - it('should skip dispatching subsequent events if an event fails to dispatch', async () => { - let receivedVisitorIds: string[] = [] - let dispatchCount = 0 - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - dispatchCount++ - event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id)) - callback({ statusCode: 400 }) - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - let event1 = createConversionEvent() - event1.user.id = event1.uuid = 'user1' - let event2 = createConversionEvent() - event2.user.id = event2.uuid = 'user2' - let event3 = createConversionEvent() - event3.user.id = event3.uuid = 'user3' - let event4 = createConversionEvent() - event4.user.id = event4.uuid = 'user4' - - processor.process(event1) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toBeCalledTimes(1) - - processor.process(event2) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toBeCalledTimes(2) - - processor.process(event3) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toBeCalledTimes(3) - - processor.process(event4) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toBeCalledTimes(4) - - expect(dispatchCount).toEqual(4) - - // subsequent events were skipped with each attempt because of request failure - expect(receivedVisitorIds).toEqual(['user1', 'user1', 'user1', 'user1']) - await processor.stop() - }) - }) - - describe('When internet connection is restored', () => { - it('should dispatch all the pending events in correct order when internet connection is restored', async () => { - let receivedVisitorIds: string[] = [] - let dispatchCount = 0 - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - dispatchCount++ - if (dispatchCount > 4) { - event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id)) - callback({ statusCode: 200 }) - } else { - callback({ statusCode: 400 }) - } - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - triggerInternetState(false) - let event1 = createConversionEvent() - event1.user.id = event1.uuid = 'user1' - let event2 = createConversionEvent() - event2.user.id = event2.uuid = 'user2' - let event3 = createConversionEvent() - event3.user.id = event3.uuid = 'user3' - let event4 = createConversionEvent() - event4.user.id = event4.uuid = 'user4' - - processor.process(event1) - processor.process(event2) - processor.process(event3) - processor.process(event4) - - await new Promise(resolve => setTimeout(resolve, 50)) - - // Four events will return response code 400 which means only the first pending event will be tried each time and rest will be skipped - expect(dispatchStub).toBeCalledTimes(4) - - jest.resetAllMocks() - - triggerInternetState(true) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toBeCalledTimes(4) - expect(receivedVisitorIds).toEqual(['user1', 'user2', 'user3', 'user4']) - await processor.stop() - }) - - it('should not dispatch duplicate events if internet is lost and restored twice in a short interval', async () => { - let receivedVisitorIds: string[] = [] - let dispatchCount = 0 - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - dispatchCount++ - if (dispatchCount > 4) { - event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id)) - callback({ statusCode: 200 }) - } else { - callback({ statusCode: 400 }) - } - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - triggerInternetState(false) - let event1 = createConversionEvent() - event1.user.id = event1.uuid = 'user1' - let event2 = createConversionEvent() - event2.user.id = event2.uuid = 'user2' - let event3 = createConversionEvent() - event3.user.id = event3.uuid = 'user3' - let event4 = createConversionEvent() - event4.user.id = event4.uuid = 'user4' - - processor.process(event1) - processor.process(event2) - processor.process(event3) - processor.process(event4) - - await new Promise(resolve => setTimeout(resolve, 100)) - - // Four events will return response code 400 which means only the first pending event will be tried each time and rest will be skipped - expect(dispatchStub).toBeCalledTimes(4) - - jest.resetAllMocks() - - triggerInternetState(true) - triggerInternetState(false) - triggerInternetState(true) - triggerInternetState(false) - triggerInternetState(true) - - await new Promise(resolve => setTimeout(resolve, 100)) - expect(dispatchStub).toBeCalledTimes(4) - expect(receivedVisitorIds).toEqual(['user1', 'user2', 'user3', 'user4']) - await processor.stop() - }) - }) - }) - }) -}) diff --git a/packages/event-processor/__tests__/v1EventProcessor.spec.ts b/packages/event-processor/__tests__/v1EventProcessor.spec.ts deleted file mode 100644 index e93291e94..000000000 --- a/packages/event-processor/__tests__/v1EventProcessor.spec.ts +++ /dev/null @@ -1,529 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/// <reference types="jest" /> - -import { LogTierV1EventProcessor } from '../src/v1/v1EventProcessor' -import { - EventDispatcher, - EventV1Request, - EventDispatcherCallback, -} from '../src/eventDispatcher' -import { EventProcessor } from '../src/eventProcessor' -import { buildImpressionEventV1, makeBatchedEventV1 } from '../src/v1/buildEventV1' -import { NotificationCenter, NOTIFICATION_TYPES } from '@optimizely/js-sdk-utils'; - -function createImpressionEvent() { - return { - type: 'impression' as 'impression', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: '1', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - layer: { - id: 'layerId', - }, - - experiment: { - id: 'expId', - key: 'expKey', - }, - - variation: { - id: 'varId', - key: 'varKey', - }, - - ruleKey: 'expKey', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: true, - } -} - -function createConversionEvent() { - return { - type: 'conversion' as 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: '1', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - - revenue: 1000, - value: 123, - } -} - -describe('LogTierV1EventProcessor', () => { - let stubDispatcher: EventDispatcher - let dispatchStub: jest.Mock - // TODO change this to ProjectConfig when js-sdk-models is available - let testProjectConfig: any - - beforeEach(() => { - jest.useFakeTimers() - - testProjectConfig = {} - dispatchStub = jest.fn() - - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - callback({ statusCode: 200 }) - }, - } - }) - - afterEach(() => { - jest.resetAllMocks() - }) - - describe('stop()', () => { - let localCallback: EventDispatcherCallback - beforeEach(() => { - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - localCallback = callback - }, - } - }) - - it('should return a resolved promise when there is nothing in queue', done => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - - processor.stop().then(() => { - done() - }) - }) - - it('should return a promise that is resolved when the dispatcher callback returns a 200 response', done => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - processor.start() - - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - processor.stop().then(() => { - done() - }) - - localCallback({ statusCode: 200 }) - }) - - it('should return a promise that is resolved when the dispatcher callback returns a 400 response', done => { - // This test is saying that even if the request fails to send but - // the `dispatcher` yielded control back, then the `.stop()` promise should be resolved - let localCallback: any - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - localCallback = callback - }, - } - - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - processor.start() - - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - processor.stop().then(() => { - done() - }) - - localCallback({ - statusCode: 400, - }) - }) - - it('should return a promise when multiple event batches are sent', done => { - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - callback({ statusCode: 200 }) - }, - } - - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - processor.start() - - const impressionEvent1 = createImpressionEvent() - const impressionEvent2 = createImpressionEvent() - impressionEvent2.context.revision = '2' - processor.process(impressionEvent1) - processor.process(impressionEvent2) - - processor.stop().then(() => { - expect(dispatchStub).toBeCalledTimes(2) - done() - }) - }) - - it('should stop accepting events after stop is called', () => { - const dispatcher = { - dispatchEvent: jest.fn((event: EventV1Request, callback: EventDispatcherCallback) => { - setTimeout(() => callback({ statusCode: 204 }), 0) - }) - } - const processor = new LogTierV1EventProcessor({ - dispatcher, - flushInterval: 100, - batchSize: 3, - }) - processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - processor.stop() - // calling stop should haver flushed the current batch of size 1 - expect(dispatcher.dispatchEvent).toBeCalledTimes(1) - - dispatcher.dispatchEvent.mockClear(); - - // From now on, subsequent events should be ignored. - // Process 3 more, which ordinarily would have triggered - // a flush due to the batch size. - const impressionEvent2 = createImpressionEvent() - processor.process(impressionEvent2) - const impressionEvent3 = createImpressionEvent() - processor.process(impressionEvent3) - const impressionEvent4 = createImpressionEvent() - processor.process(impressionEvent4) - // Since we already stopped the processor, the dispatcher should - // not have been called again. - expect(dispatcher.dispatchEvent).toBeCalledTimes(0) - }) - - it('should resolve the stop promise after all dispatcher requests are done', async () => { - const dispatchCbs: Array<EventDispatcherCallback> = [] - const dispatcher = { - dispatchEvent: jest.fn((event: EventV1Request, callback: EventDispatcherCallback) => { - dispatchCbs.push(callback) - }) - } - - const processor = new LogTierV1EventProcessor({ - dispatcher, - flushInterval: 100, - batchSize: 2, - }) - processor.start() - - for (let i = 0; i < 4; i++) { - processor.process(createImpressionEvent()) - } - expect(dispatchCbs.length).toBe(2) - - let stopPromiseResolved = false - const stopPromise = processor.stop().then(() => { - stopPromiseResolved = true - }) - expect(stopPromiseResolved).toBe(false) - - dispatchCbs[0]({ statusCode: 204 }) - jest.advanceTimersByTime(100) - expect(stopPromiseResolved).toBe(false) - dispatchCbs[1]({ statusCode: 204 }) - await stopPromise - expect(stopPromiseResolved).toBe(true) - }) - }) - - describe('when batchSize = 1', () => { - let processor: EventProcessor - beforeEach(() => { - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - processor.start() - }) - - afterEach(() => { - processor.stop() - }) - - it('should immediately flush events as they are processed', () => { - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: buildImpressionEventV1(impressionEvent), - }) - }) - }) - - describe('when batchSize = 3, flushInterval = 100', () => { - let processor: EventProcessor - beforeEach(() => { - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 3, - }) - processor.start() - }) - - afterEach(() => { - processor.stop() - }) - - it('should wait until 3 events to be in the queue before it flushes', () => { - const impressionEvent1 = createImpressionEvent() - const impressionEvent2 = createImpressionEvent() - const impressionEvent3 = createImpressionEvent() - - processor.process(impressionEvent1) - processor.process(impressionEvent2) - - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent3) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([ - impressionEvent1, - impressionEvent2, - impressionEvent3, - ]), - }) - }) - - it('should flush the current batch when it receives an event with a different context revision than the current batch', async () => { - const impressionEvent1 = createImpressionEvent() - const conversionEvent = createConversionEvent() - const impressionEvent2 = createImpressionEvent() - - // createImpressionEvent and createConversionEvent create events with revision '1' - // We modify this one's revision to '2' in order to test that the queue is flushed - // when an event with a different revision is processed. - impressionEvent2.context.revision = '2' - - processor.process(impressionEvent1) - processor.process(conversionEvent) - - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent2) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1, conversionEvent]), - }) - - await processor.stop() - - expect(dispatchStub).toHaveBeenCalledTimes(2) - - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent2]), - }) - }) - - it('should flush the current batch when it receives an event with a different context projectId than the current batch', async () => { - const impressionEvent1 = createImpressionEvent() - const conversionEvent = createConversionEvent() - const impressionEvent2 = createImpressionEvent() - - impressionEvent2.context.projectId = 'projectId2' - - processor.process(impressionEvent1) - processor.process(conversionEvent) - - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent2) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1, conversionEvent]), - }) - - await processor.stop() - - expect(dispatchStub).toHaveBeenCalledTimes(2) - - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent2]), - }) - }) - - it('should flush the queue when the flush interval happens', () => { - const impressionEvent1 = createImpressionEvent() - - processor.process(impressionEvent1) - - expect(dispatchStub).toHaveBeenCalledTimes(0) - - jest.advanceTimersByTime(100) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1]), - }) - - processor.process(createImpressionEvent()) - processor.process(createImpressionEvent()) - // flushing should reset queue, at this point only has two events - expect(dispatchStub).toHaveBeenCalledTimes(1) - }) - - }) - - describe('when a notification center is provided', () => { - it('should trigger a notification when the event dispatcher dispatches an event', () => { - const dispatcher: EventDispatcher = { - dispatchEvent: jest.fn() - } - - const notificationCenter: NotificationCenter = { - sendNotifications: jest.fn() - } - - const processor = new LogTierV1EventProcessor({ - dispatcher, - notificationCenter, - batchSize: 1, - }) - processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - - expect(notificationCenter.sendNotifications).toBeCalledTimes(1) - const event = (dispatcher.dispatchEvent as jest.Mock).mock.calls[0][0] - expect(notificationCenter.sendNotifications).toBeCalledWith(NOTIFICATION_TYPES.LOG_EVENT, event) - }) - }) - - describe('invalid flushInterval or batchSize', () => { - it('should ignore a flushInterval of 0 and use the default', () => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 0, - batchSize: 10, - }) - processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - expect(dispatchStub).toHaveBeenCalledTimes(0) - jest.advanceTimersByTime(30000) - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1]), - }) - }) - - it('should ignore a batchSize of 0 and use the default', () => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 30000, - batchSize: 0, - }) - processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - expect(dispatchStub).toHaveBeenCalledTimes(0) - const impressionEvents = [impressionEvent1] - for (let i = 0; i < 9; i++) { - const evt = createImpressionEvent() - processor.process(evt) - impressionEvents.push(evt) - } - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1(impressionEvents), - }) - }) - }) -}) diff --git a/packages/event-processor/jest.config.js b/packages/event-processor/jest.config.js deleted file mode 100644 index b862d4238..000000000 --- a/packages/event-processor/jest.config.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = { - "transform": { - "^.+\\.tsx?$": "ts-jest" - }, - "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$", - "moduleFileExtensions": [ - "ts", - "tsx", - "js", - "jsx", - "json", - "node" - ], - "setupFiles": ["jest-localstorage-mock"] -} \ No newline at end of file diff --git a/packages/event-processor/package-lock.json b/packages/event-processor/package-lock.json deleted file mode 100644 index 5d7c4fe62..000000000 --- a/packages/event-processor/package-lock.json +++ /dev/null @@ -1,5402 +0,0 @@ -{ - "name": "@optimizely/js-sdk-event-processor", - "version": "0.9.5", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", - "dev": true - }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - } - } - }, - "@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - } - }, - "@optimizely/js-sdk-logging": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-logging/-/js-sdk-logging-0.3.1.tgz", - "integrity": "sha512-K71Jf283FP0E4oXehcXTTM3gvgHZHr7FUrIsw//0mdJlotHJT4Nss4hE0CWPbBxO7LJAtwNnO+VIA/YOcO4vHg==", - "requires": { - "@optimizely/js-sdk-utils": "^0.4.0" - } - }, - "@optimizely/js-sdk-utils": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-utils/-/js-sdk-utils-0.4.0.tgz", - "integrity": "sha512-QG2oytnITW+VKTJK+l0RxjaS5VrA6W+AZMzpeg4LCB4Rn4BEKtF+EcW/5S1fBDLAviGq/0TLpkjM3DlFkJ9/Gw==", - "requires": { - "uuid": "^3.3.2" - } - }, - "@react-native-async-storage/async-storage": { - "version": "1.15.4", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.15.4.tgz", - "integrity": "sha512-pC0MS6UBuv/YiVAxtzi7CgUed8oCQNYMtGt0yb/I9fI/BWTiJK5cj4YtW2XtL95K5IuvPX/6uGWaouZ8KqXwdg==", - "dev": true, - "requires": { - "deep-assign": "^3.0.0" - } - }, - "@react-native-community/netinfo": { - "version": "5.9.5", - "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-5.9.5.tgz", - "integrity": "sha512-PbSsRmhRwYIMdeVJTf9gJtvW0TVq/hmgz1xyjsrTIsQ7QS7wbMEiv1Eb/M/y6AEEsdUped5Axm5xykq9TGISHg==", - "dev": true - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", - "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*", - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "24.9.1", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-24.9.1.tgz", - "integrity": "sha512-Fb38HkXSVA4L8fGKEZ6le5bB8r6MRWlOCZbVuWZcmOMSCd2wCYOwN1ibj8daIoV9naq7aaOZjrLCoCMptKU/4Q==", - "dev": true, - "requires": { - "jest-diff": "^24.3.0" - } - }, - "@types/yargs": { - "version": "13.0.9", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.9.tgz", - "integrity": "sha512-xrvhZ4DZewMDhoH1utLtOAwYQy60eYFoXeje30TzM3VOvQlBwQaEpKFq5m34k1wOw2AKIi2pwtiAjdmhvlBUzg==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "15.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-15.0.0.tgz", - "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", - "dev": true - }, - "abab": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", - "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==", - "dev": true - }, - "acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", - "dev": true - }, - "acorn-globals": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", - "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", - "dev": true, - "requires": { - "acorn": "^6.0.1", - "acorn-walk": "^6.0.1" - }, - "dependencies": { - "acorn": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", - "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", - "dev": true - } - } - }, - "acorn-walk": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", - "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", - "dev": true - }, - "ajv": { - "version": "6.12.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", - "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "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 - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - } - } - }, - "append-transform": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", - "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", - "dev": true, - "requires": { - "default-require-extensions": "^1.0.0" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, - "array-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", - "dev": true - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", - "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==", - "dev": true - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" - } - }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "dev": true, - "requires": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" - } - }, - "babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-jest": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-23.6.0.tgz", - "integrity": "sha512-lqKGG6LYXYu+DQh/slrQ8nxXQkEkhugdXsU6St7GmhVS7Ilc/22ArwqXNJrf0QaOBjZB0360qZMwXqDYQHXaew==", - "dev": true, - "requires": { - "babel-plugin-istanbul": "^4.1.6", - "babel-preset-jest": "^23.2.0" - } - }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-istanbul": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", - "integrity": "sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==", - "dev": true, - "requires": { - "babel-plugin-syntax-object-rest-spread": "^6.13.0", - "find-up": "^2.1.0", - "istanbul-lib-instrument": "^1.10.1", - "test-exclude": "^4.2.1" - } - }, - "babel-plugin-jest-hoist": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz", - "integrity": "sha1-5h+uBaHKiAGq3uV6bWa4zvr0QWc=", - "dev": true - }, - "babel-plugin-syntax-object-rest-spread": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", - "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", - "dev": true - }, - "babel-preset-jest": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz", - "integrity": "sha1-jsegOhOPABoaj7HoETZSvxpV2kY=", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^23.2.0", - "babel-plugin-syntax-object-rest-spread": "^6.13.0" - } - }, - "babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", - "dev": true, - "requires": { - "babel-core": "^6.26.0", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "home-or-tmp": "^2.0.0", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "source-map-support": "^0.4.15" - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "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 - } - } - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } - }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "browser-resolve": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", - "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", - "dev": true, - "requires": { - "resolve": "1.1.7" - }, - "dependencies": { - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - } - } - }, - "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, - "requires": { - "fast-json-stable-stringify": "2.x" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", - "dev": true - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "capture-exit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-1.2.0.tgz", - "integrity": "sha1-HF/MSJ/QqwDU8ax64QcuMXP7q28=", - "dev": true, - "requires": { - "rsvp": "^3.3.3" - } - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", - "dev": true - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "dev": true, - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "core-js": { - "version": "2.6.11", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", - "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - }, - "cssstyle": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", - "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", - "dev": true, - "requires": { - "cssom": "0.3.x" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "data-urls": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", - "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", - "dev": true, - "requires": { - "abab": "^2.0.0", - "whatwg-mimetype": "^2.2.0", - "whatwg-url": "^7.0.0" - }, - "dependencies": { - "whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - } - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, - "deep-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/deep-assign/-/deep-assign-3.0.0.tgz", - "integrity": "sha512-YX2i9XjJ7h5q/aQ/IM9PEwEnDqETAIYbggmdDB3HLTlSgo1CxPsj6pvhPG68rq6SVE0+p+6Ywsm5fTYNrYtBWw==", - "dev": true, - "requires": { - "is-obj": "^1.0.0" - } - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "default-require-extensions": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", - "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", - "dev": true, - "requires": { - "strip-bom": "^2.0.0" - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "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 - } - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, - "detect-newline": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", - "dev": true - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "diff-sequences": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-24.9.0.tgz", - "integrity": "sha512-Dj6Wk3tWyTE+Fo1rW8v0Xhwk80um6yFYKbuAxc9c3EZxIHFDYwbi34Uk42u1CdnIiVorvt4RmlSDjIPyzGC2ew==", - "dev": true - }, - "domexception": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", - "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", - "dev": true, - "requires": { - "webidl-conversions": "^4.0.2" - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.17.6", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.6.tgz", - "integrity": "sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.0", - "is-regex": "^1.1.0", - "object-inspect": "^1.7.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.0", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" - } - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } - } - }, - "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 - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "exec-sh": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.2.tgz", - "integrity": "sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw==", - "dev": true, - "requires": { - "merge": "^1.2.0" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "^0.1.0" - } - }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "dev": true, - "requires": { - "fill-range": "^2.1.0" - } - }, - "expect": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-23.6.0.tgz", - "integrity": "sha512-dgSoOHgmtn/aDGRVFWclQyPDKl2CQRq0hmIEoUAuQs/2rn2NcvCWcSCovm6BLeuB/7EZuLGu2QfnR+qRt5OM4w==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "jest-diff": "^23.6.0", - "jest-get-type": "^22.1.0", - "jest-matcher-utils": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-regex-util": "^23.3.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "jest-diff": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-23.6.0.tgz", - "integrity": "sha512-Gz9l5Ov+X3aL5L37IT+8hoCUsof1CVYBb2QEkOupK64XyRR3h+uRpYIm97K7sY8diFxowR8pIGEdyfMKTixo3g==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "diff": "^3.2.0", - "jest-get-type": "^22.1.0", - "pretty-format": "^23.6.0" - } - }, - "jest-get-type": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", - "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", - "dev": true - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - } - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true - }, - "fileset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", - "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", - "dev": true, - "requires": { - "glob": "^7.0.3", - "minimatch": "^3.0.3" - } - }, - "fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", - "dev": true, - "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "dev": true, - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true, - "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - } - }, - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, - "requires": { - "is-glob": "^2.0.0" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", - "dev": true - }, - "handlebars": { - "version": "4.7.6", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.6.tgz", - "integrity": "sha512-1f2BACcBfiwAfStCKZNrUCgqNZkGsAT7UM3kkYtXuLo0KnaVfjKOyf7PRzB6++aK9STyT1Pd2ZCPe3EGOXleXA==", - "dev": true, - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "dev": true, - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - } - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", - "dev": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.1" - } - }, - "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", - "dev": true - }, - "html-encoding-sniffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", - "dev": true, - "requires": { - "whatwg-encoding": "^1.0.1" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "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, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "import-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", - "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", - "dev": true, - "requires": { - "pkg-dir": "^2.0.0", - "resolve-cwd": "^2.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-callable": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.0.tgz", - "integrity": "sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw==", - "dev": true - }, - "is-ci": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", - "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", - "dev": true, - "requires": { - "ci-info": "^1.5.0" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-date-object": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", - "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", - "dev": true - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "dev": true, - "requires": { - "is-primitive": "^2.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-finite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "is-generator-fn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-1.0.0.tgz", - "integrity": "sha1-lp1J4bszKfa7fwkIm+JleLLd1Go=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true - }, - "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, - "requires": { - "isobject": "^3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true - }, - "is-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", - "integrity": "sha512-iI97M8KTWID2la5uYXlkbSDQIg4F6o1sYboZKKTDpnDQMLtUL86zxhgDet3Q2SriaYsyGqZ6Mn2SjbRKeLHdqw==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-symbol": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", - "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", - "dev": true, - "requires": { - "has-symbols": "^1.0.1" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "istanbul-api": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.7.tgz", - "integrity": "sha512-4/ApBnMVeEPG3EkSzcw25wDe4N66wxwn+KKn6b47vyek8Xb3NBAcg4xfuQbS7BqcZuTX4wxfD5lVagdggR3gyA==", - "dev": true, - "requires": { - "async": "^2.1.4", - "fileset": "^2.0.2", - "istanbul-lib-coverage": "^1.2.1", - "istanbul-lib-hook": "^1.2.2", - "istanbul-lib-instrument": "^1.10.2", - "istanbul-lib-report": "^1.1.5", - "istanbul-lib-source-maps": "^1.2.6", - "istanbul-reports": "^1.5.1", - "js-yaml": "^3.7.0", - "mkdirp": "^0.5.1", - "once": "^1.4.0" - } - }, - "istanbul-lib-coverage": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", - "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", - "dev": true - }, - "istanbul-lib-hook": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz", - "integrity": "sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==", - "dev": true, - "requires": { - "append-transform": "^0.4.0" - } - }, - "istanbul-lib-instrument": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", - "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", - "dev": true, - "requires": { - "babel-generator": "^6.18.0", - "babel-template": "^6.16.0", - "babel-traverse": "^6.18.0", - "babel-types": "^6.18.0", - "babylon": "^6.18.0", - "istanbul-lib-coverage": "^1.2.1", - "semver": "^5.3.0" - } - }, - "istanbul-lib-report": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz", - "integrity": "sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^1.2.1", - "mkdirp": "^0.5.1", - "path-parse": "^1.0.5", - "supports-color": "^3.1.2" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz", - "integrity": "sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg==", - "dev": true, - "requires": { - "debug": "^3.1.0", - "istanbul-lib-coverage": "^1.2.1", - "mkdirp": "^0.5.1", - "rimraf": "^2.6.1", - "source-map": "^0.5.3" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "istanbul-reports": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.5.1.tgz", - "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==", - "dev": true, - "requires": { - "handlebars": "^4.0.3" - } - }, - "jest": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-23.6.0.tgz", - "integrity": "sha512-lWzcd+HSiqeuxyhG+EnZds6iO3Y3ZEnMrfZq/OTGvF/C+Z4fPMCdhWTGSAiO2Oym9rbEXfwddHhh6jqrTF3+Lw==", - "dev": true, - "requires": { - "import-local": "^1.0.0", - "jest-cli": "^23.6.0" - }, - "dependencies": { - "jest-cli": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-23.6.0.tgz", - "integrity": "sha512-hgeD1zRUp1E1zsiyOXjEn4LzRLWdJBV//ukAHGlx6s5mfCNJTbhbHjgxnDUXA8fsKWN/HqFFF6X5XcCwC/IvYQ==", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.1", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "import-local": "^1.0.0", - "is-ci": "^1.0.10", - "istanbul-api": "^1.3.1", - "istanbul-lib-coverage": "^1.2.0", - "istanbul-lib-instrument": "^1.10.1", - "istanbul-lib-source-maps": "^1.2.4", - "jest-changed-files": "^23.4.2", - "jest-config": "^23.6.0", - "jest-environment-jsdom": "^23.4.0", - "jest-get-type": "^22.1.0", - "jest-haste-map": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-regex-util": "^23.3.0", - "jest-resolve-dependencies": "^23.6.0", - "jest-runner": "^23.6.0", - "jest-runtime": "^23.6.0", - "jest-snapshot": "^23.6.0", - "jest-util": "^23.4.0", - "jest-validate": "^23.6.0", - "jest-watcher": "^23.4.0", - "jest-worker": "^23.2.0", - "micromatch": "^2.3.11", - "node-notifier": "^5.2.1", - "prompts": "^0.1.9", - "realpath-native": "^1.0.0", - "rimraf": "^2.5.4", - "slash": "^1.0.0", - "string-length": "^2.0.0", - "strip-ansi": "^4.0.0", - "which": "^1.2.12", - "yargs": "^11.0.0" - } - }, - "jest-get-type": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", - "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", - "dev": true - } - } - }, - "jest-changed-files": { - "version": "23.4.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-23.4.2.tgz", - "integrity": "sha512-EyNhTAUWEfwnK0Is/09LxoqNDOn7mU7S3EHskG52djOFS/z+IT0jT3h3Ql61+dklcG7bJJitIWEMB4Sp1piHmA==", - "dev": true, - "requires": { - "throat": "^4.0.0" - } - }, - "jest-config": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-23.6.0.tgz", - "integrity": "sha512-i8V7z9BeDXab1+VNo78WM0AtWpBRXJLnkT+lyT+Slx/cbP5sZJ0+NDuLcmBE5hXAoK0aUp7vI+MOxR+R4d8SRQ==", - "dev": true, - "requires": { - "babel-core": "^6.0.0", - "babel-jest": "^23.6.0", - "chalk": "^2.0.1", - "glob": "^7.1.1", - "jest-environment-jsdom": "^23.4.0", - "jest-environment-node": "^23.4.0", - "jest-get-type": "^22.1.0", - "jest-jasmine2": "^23.6.0", - "jest-regex-util": "^23.3.0", - "jest-resolve": "^23.6.0", - "jest-util": "^23.4.0", - "jest-validate": "^23.6.0", - "micromatch": "^2.3.11", - "pretty-format": "^23.6.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "jest-get-type": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", - "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", - "dev": true - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - } - } - }, - "jest-diff": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-24.9.0.tgz", - "integrity": "sha512-qMfrTs8AdJE2iqrTp0hzh7kTd2PQWrsFyj9tORoKmu32xjPjeE4NyjVRDz8ybYwqS2ik8N4hsIpiVTyFeo2lBQ==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "diff-sequences": "^24.9.0", - "jest-get-type": "^24.9.0", - "pretty-format": "^24.9.0" - } - }, - "jest-docblock": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-23.2.0.tgz", - "integrity": "sha1-8IXh8YVI2Z/dabICB+b9VdkTg6c=", - "dev": true, - "requires": { - "detect-newline": "^2.1.0" - } - }, - "jest-each": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-23.6.0.tgz", - "integrity": "sha512-x7V6M/WGJo6/kLoissORuvLIeAoyo2YqLOoCDkohgJ4XOXSqOtyvr8FbInlAWS77ojBsZrafbozWoKVRdtxFCg==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "pretty-format": "^23.6.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - } - } - }, - "jest-environment-jsdom": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-23.4.0.tgz", - "integrity": "sha1-BWp5UrP+pROsYqFAosNox52eYCM=", - "dev": true, - "requires": { - "jest-mock": "^23.2.0", - "jest-util": "^23.4.0", - "jsdom": "^11.5.1" - } - }, - "jest-environment-node": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-23.4.0.tgz", - "integrity": "sha1-V+gO0IQd6jAxZ8zozXlSHeuv3hA=", - "dev": true, - "requires": { - "jest-mock": "^23.2.0", - "jest-util": "^23.4.0" - } - }, - "jest-get-type": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-24.9.0.tgz", - "integrity": "sha512-lUseMzAley4LhIcpSP9Jf+fTrQ4a1yHQwLNeeVa2cEmbCGeoZAtYPOIv8JaxLD/sUpKxetKGP+gsHl8f8TSj8Q==", - "dev": true - }, - "jest-haste-map": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-23.6.0.tgz", - "integrity": "sha512-uyNhMyl6dr6HaXGHp8VF7cK6KpC6G9z9LiMNsst+rJIZ8l7wY0tk8qwjPmEghczojZ2/ZhtEdIabZ0OQRJSGGg==", - "dev": true, - "requires": { - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.1.11", - "invariant": "^2.2.4", - "jest-docblock": "^23.2.0", - "jest-serializer": "^23.0.1", - "jest-worker": "^23.2.0", - "micromatch": "^2.3.11", - "sane": "^2.0.0" - } - }, - "jest-jasmine2": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-23.6.0.tgz", - "integrity": "sha512-pe2Ytgs1nyCs8IvsEJRiRTPC0eVYd8L/dXJGU08GFuBwZ4sYH/lmFDdOL3ZmvJR8QKqV9MFuwlsAi/EWkFUbsQ==", - "dev": true, - "requires": { - "babel-traverse": "^6.0.0", - "chalk": "^2.0.1", - "co": "^4.6.0", - "expect": "^23.6.0", - "is-generator-fn": "^1.0.0", - "jest-diff": "^23.6.0", - "jest-each": "^23.6.0", - "jest-matcher-utils": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-snapshot": "^23.6.0", - "jest-util": "^23.4.0", - "pretty-format": "^23.6.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "jest-diff": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-23.6.0.tgz", - "integrity": "sha512-Gz9l5Ov+X3aL5L37IT+8hoCUsof1CVYBb2QEkOupK64XyRR3h+uRpYIm97K7sY8diFxowR8pIGEdyfMKTixo3g==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "diff": "^3.2.0", - "jest-get-type": "^22.1.0", - "pretty-format": "^23.6.0" - } - }, - "jest-get-type": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", - "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", - "dev": true - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - } - } - }, - "jest-leak-detector": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-23.6.0.tgz", - "integrity": "sha512-f/8zA04rsl1Nzj10HIyEsXvYlMpMPcy0QkQilVZDFOaPbv2ur71X5u2+C4ZQJGyV/xvVXtCCZ3wQ99IgQxftCg==", - "dev": true, - "requires": { - "pretty-format": "^23.6.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - } - } - }, - "jest-localstorage-mock": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/jest-localstorage-mock/-/jest-localstorage-mock-2.4.2.tgz", - "integrity": "sha512-bywlhvs7RM2vpZ0EN12XOU5C2WAXRUbxl1VOxel4cqRUHD6zaUmh99UzwOTx0fWuqjfd0y/NDvEbewNaJaz+UQ==", - "dev": true - }, - "jest-matcher-utils": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-23.6.0.tgz", - "integrity": "sha512-rosyCHQfBcol4NsckTn01cdelzWLU9Cq7aaigDf8VwwpIRvWE/9zLgX2bON+FkEW69/0UuYslUe22SOdEf2nog==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-get-type": "^22.1.0", - "pretty-format": "^23.6.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "jest-get-type": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", - "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", - "dev": true - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - } - } - }, - "jest-message-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-23.4.0.tgz", - "integrity": "sha1-F2EMUJQjSVCNAaPR4L2iwHkIap8=", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0-beta.35", - "chalk": "^2.0.1", - "micromatch": "^2.3.11", - "slash": "^1.0.0", - "stack-utils": "^1.0.1" - } - }, - "jest-mock": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-23.2.0.tgz", - "integrity": "sha1-rRxg8p6HGdR8JuETgJi20YsmETQ=", - "dev": true - }, - "jest-regex-util": { - "version": "23.3.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-23.3.0.tgz", - "integrity": "sha1-X4ZylUfCeFxAAs6qj4Sf6MpHG8U=", - "dev": true - }, - "jest-resolve": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-23.6.0.tgz", - "integrity": "sha512-XyoRxNtO7YGpQDmtQCmZjum1MljDqUCob7XlZ6jy9gsMugHdN2hY4+Acz9Qvjz2mSsOnPSH7skBmDYCHXVZqkA==", - "dev": true, - "requires": { - "browser-resolve": "^1.11.3", - "chalk": "^2.0.1", - "realpath-native": "^1.0.0" - } - }, - "jest-resolve-dependencies": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-23.6.0.tgz", - "integrity": "sha512-EkQWkFWjGKwRtRyIwRwI6rtPAEyPWlUC2MpzHissYnzJeHcyCn1Hc8j7Nn1xUVrS5C6W5+ZL37XTem4D4pLZdA==", - "dev": true, - "requires": { - "jest-regex-util": "^23.3.0", - "jest-snapshot": "^23.6.0" - } - }, - "jest-runner": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-23.6.0.tgz", - "integrity": "sha512-kw0+uj710dzSJKU6ygri851CObtCD9cN8aNkg8jWJf4ewFyEa6kwmiH/r/M1Ec5IL/6VFa0wnAk6w+gzUtjJzA==", - "dev": true, - "requires": { - "exit": "^0.1.2", - "graceful-fs": "^4.1.11", - "jest-config": "^23.6.0", - "jest-docblock": "^23.2.0", - "jest-haste-map": "^23.6.0", - "jest-jasmine2": "^23.6.0", - "jest-leak-detector": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-runtime": "^23.6.0", - "jest-util": "^23.4.0", - "jest-worker": "^23.2.0", - "source-map-support": "^0.5.6", - "throat": "^4.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - } - } - }, - "jest-runtime": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-23.6.0.tgz", - "integrity": "sha512-ycnLTNPT2Gv+TRhnAYAQ0B3SryEXhhRj1kA6hBPSeZaNQkJ7GbZsxOLUkwg6YmvWGdX3BB3PYKFLDQCAE1zNOw==", - "dev": true, - "requires": { - "babel-core": "^6.0.0", - "babel-plugin-istanbul": "^4.1.6", - "chalk": "^2.0.1", - "convert-source-map": "^1.4.0", - "exit": "^0.1.2", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.1.11", - "jest-config": "^23.6.0", - "jest-haste-map": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-regex-util": "^23.3.0", - "jest-resolve": "^23.6.0", - "jest-snapshot": "^23.6.0", - "jest-util": "^23.4.0", - "jest-validate": "^23.6.0", - "micromatch": "^2.3.11", - "realpath-native": "^1.0.0", - "slash": "^1.0.0", - "strip-bom": "3.0.0", - "write-file-atomic": "^2.1.0", - "yargs": "^11.0.0" - }, - "dependencies": { - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - } - } - }, - "jest-serializer": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-23.0.1.tgz", - "integrity": "sha1-o3dq6zEekP6D+rnlM+hRAr0WQWU=", - "dev": true - }, - "jest-snapshot": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-23.6.0.tgz", - "integrity": "sha512-tM7/Bprftun6Cvj2Awh/ikS7zV3pVwjRYU2qNYS51VZHgaAMBs5l4o/69AiDHhQrj5+LA2Lq4VIvK7zYk/bswg==", - "dev": true, - "requires": { - "babel-types": "^6.0.0", - "chalk": "^2.0.1", - "jest-diff": "^23.6.0", - "jest-matcher-utils": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-resolve": "^23.6.0", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "pretty-format": "^23.6.0", - "semver": "^5.5.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "jest-diff": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-23.6.0.tgz", - "integrity": "sha512-Gz9l5Ov+X3aL5L37IT+8hoCUsof1CVYBb2QEkOupK64XyRR3h+uRpYIm97K7sY8diFxowR8pIGEdyfMKTixo3g==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "diff": "^3.2.0", - "jest-get-type": "^22.1.0", - "pretty-format": "^23.6.0" - } - }, - "jest-get-type": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", - "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", - "dev": true - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - } - } - }, - "jest-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-23.4.0.tgz", - "integrity": "sha1-TQY8uSe68KI4Mf9hvsLLv0l5NWE=", - "dev": true, - "requires": { - "callsites": "^2.0.0", - "chalk": "^2.0.1", - "graceful-fs": "^4.1.11", - "is-ci": "^1.0.10", - "jest-message-util": "^23.4.0", - "mkdirp": "^0.5.1", - "slash": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "jest-validate": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-23.6.0.tgz", - "integrity": "sha512-OFKapYxe72yz7agrDAWi8v2WL8GIfVqcbKRCLbRG9PAxtzF9b1SEDdTpytNDN12z2fJynoBwpMpvj2R39plI2A==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-get-type": "^22.1.0", - "leven": "^2.1.0", - "pretty-format": "^23.6.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "jest-get-type": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", - "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", - "dev": true - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - } - } - }, - "jest-watcher": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-23.4.0.tgz", - "integrity": "sha1-0uKM50+NrWxq/JIrksq+9u0FyRw=", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.1", - "string-length": "^2.0.0" - } - }, - "jest-worker": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-23.2.0.tgz", - "integrity": "sha1-+vcGqNo2+uYOsmlXJX+ntdjqArk=", - "dev": true, - "requires": { - "merge-stream": "^1.0.1" - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "jsdom": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", - "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", - "dev": true, - "requires": { - "abab": "^2.0.0", - "acorn": "^5.5.3", - "acorn-globals": "^4.1.0", - "array-equal": "^1.0.0", - "cssom": ">= 0.3.2 < 0.4.0", - "cssstyle": "^1.0.0", - "data-urls": "^1.0.0", - "domexception": "^1.0.1", - "escodegen": "^1.9.1", - "html-encoding-sniffer": "^1.0.2", - "left-pad": "^1.3.0", - "nwsapi": "^2.0.7", - "parse5": "4.0.0", - "pn": "^1.1.0", - "request": "^2.87.0", - "request-promise-native": "^1.0.5", - "sax": "^1.2.4", - "symbol-tree": "^3.2.2", - "tough-cookie": "^2.3.4", - "w3c-hr-time": "^1.0.1", - "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.3", - "whatwg-mimetype": "^2.1.0", - "whatwg-url": "^6.4.1", - "ws": "^5.2.0", - "xml-name-validator": "^3.0.0" - } - }, - "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", - "dev": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - }, - "kleur": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-2.0.2.tgz", - "integrity": "sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ==", - "dev": true - }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, - "left-pad": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", - "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", - "dev": true - }, - "leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", - "dev": true - }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "dev": true, - "requires": { - "tmpl": "1.0.x" - } - }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, - "requires": { - "p-defer": "^1.0.0" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "math-random": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", - "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", - "dev": true - }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - } - }, - "merge": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz", - "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", - "dev": true - }, - "merge-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", - "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", - "dev": true, - "requires": { - "readable-stream": "^2.0.1" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } - }, - "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", - "dev": true - }, - "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "dev": true, - "requires": { - "mime-db": "1.44.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "nan": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", - "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", - "dev": true, - "optional": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "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 - } - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node-notifier": { - "version": "5.4.3", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.3.tgz", - "integrity": "sha512-M4UBGcs4jeOK9CjTsYwkvH6/MzuUmGCyTW+kCY7uO+1ZVr0+FHGdPdIf5CCLqAaxnRrWidyoQlNkMIIVwbKB8Q==", - "dev": true, - "requires": { - "growly": "^1.3.0", - "is-wsl": "^1.1.0", - "semver": "^5.5.0", - "shellwords": "^0.1.1", - "which": "^1.3.0" - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", - "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true, - "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "^3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true, - "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "parse5": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", - "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - }, - "pn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", - "dev": true - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true - }, - "pretty-format": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", - "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", - "dev": true, - "requires": { - "@jest/types": "^24.9.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" - } - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "prompts": { - "version": "0.1.14", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-0.1.14.tgz", - "integrity": "sha512-rxkyiE9YH6zAz/rZpywySLKkpaj0NMVyNw1qhsubdbjjSgcayjTShDreZGlFMcGSu5sab3bAKPfFk78PB90+8w==", - "dev": true, - "requires": { - "kleur": "^2.0.1", - "sisteransi": "^0.1.1" - } - }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "randomatic": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", - "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", - "dev": true, - "requires": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - }, - "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 - } - } - }, - "react-is": { - "version": "16.13.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", - "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", - "dev": true - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - } - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "realpath-native": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", - "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", - "dev": true, - "requires": { - "util.promisify": "^1.0.0" - } - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "dev": true, - "requires": { - "is-equal-shallow": "^0.1.3" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "request-promise-core": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", - "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", - "dev": true, - "requires": { - "lodash": "^4.17.15" - } - }, - "request-promise-native": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", - "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", - "dev": true, - "requires": { - "request-promise-core": "1.1.3", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", - "dev": true, - "requires": { - "resolve-from": "^3.0.0" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rsvp": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz", - "integrity": "sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "sane": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/sane/-/sane-2.5.2.tgz", - "integrity": "sha1-tNwYYcIbQn6SlQej51HiosuKs/o=", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "capture-exit": "^1.2.0", - "exec-sh": "^0.2.0", - "fb-watchman": "^2.0.0", - "fsevents": "^1.2.3", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5", - "watch": "~0.18.0" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "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 - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - } - } - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "sisteransi": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-0.1.1.tgz", - "integrity": "sha512-PmGOd02bM9YO5ifxpw36nrNMBTptEtfRl4qUYl9SndkolplkrZZOW7PGHjrZL53QvMVj9nQ+TKqUnRsw4tJa4g==", - "dev": true - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "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 - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "^0.5.6" - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==", - "dev": true - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "stack-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", - "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", - "dev": true - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", - "dev": true - }, - "string-length": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", - "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=", - "dev": true, - "requires": { - "astral-regex": "^1.0.0", - "strip-ansi": "^4.0.0" - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "string.prototype.trimend": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz", - "integrity": "sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string.prototype.trimstart": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz", - "integrity": "sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - } - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "test-exclude": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.3.tgz", - "integrity": "sha512-SYbXgY64PT+4GAL2ocI3HwPa4Q4TBKm0cwAVeKOt/Aoc0gSpNRjJX8w0pA1LMKZ3LBmd8pYBqApFNQLII9kavA==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "micromatch": "^2.3.11", - "object-assign": "^4.1.0", - "read-pkg-up": "^1.0.1", - "require-main-filename": "^1.0.1" - } - }, - "throat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", - "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=", - "dev": true - }, - "tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", - "dev": true - }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - } - } - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "dev": true - }, - "ts-jest": { - "version": "23.10.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-23.10.5.tgz", - "integrity": "sha512-MRCs9qnGoyKgFc8adDEntAOP64fWK1vZKnOYU1o2HxaqjdJvGqmkLCPCnVq1/If4zkUmEjKPnCiUisTrlX2p2A==", - "dev": true, - "requires": { - "bs-logger": "0.x", - "buffer-from": "1.x", - "fast-json-stable-stringify": "2.x", - "json5": "2.x", - "make-error": "1.x", - "mkdirp": "0.x", - "resolve": "1.x", - "semver": "^5.5", - "yargs-parser": "10.x" - }, - "dependencies": { - "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - } - } - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "typescript": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.3.tgz", - "integrity": "sha512-tEu6DGxGgRJPb/mVPIZ48e69xCn2yRmCgYmDugAVwmJ6o+0u1RI18eO7E7WBTLYLaEVVOhwQmcdhQHweux/WPg==", - "dev": true - }, - "uglify-js": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.10.0.tgz", - "integrity": "sha512-Esj5HG5WAyrLIdYU74Z3JdG2PxdIusvj6IWHMtlyESxc7kcDz7zYlYjpnSokn1UbpV0d/QX9fan7gkCNd/9BQA==", - "dev": true, - "optional": true - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "util.promisify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.1.tgz", - "integrity": "sha512-g9JpC/3He3bm38zsLupWryXHoEcS22YHthuPQSJdMy6KNrzIRzWqcsHzD/WUnqe45whVou4VIsPew37DoXWNrA==", - "dev": true, - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.2", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.0" - } - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "dev": true, - "requires": { - "makeerror": "1.0.x" - } - }, - "watch": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/watch/-/watch-0.18.0.tgz", - "integrity": "sha1-KAlUdsbffJDJYxOJkMClQj60uYY=", - "dev": true, - "requires": { - "exec-sh": "^0.2.0", - "minimist": "^1.2.0" - } - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "whatwg-url": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", - "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "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 - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "ws": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", - "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, - "yargs": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.1.tgz", - "integrity": "sha512-PRU7gJrJaXv3q3yQZ/+/X6KBswZiaQ+zOmdprZcouPYtQgvNU35i+68M4b1ZHLZtYFT5QObFLV+ZkmJYcwKdiw==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.1.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^9.0.2" - } - }, - "yargs-parser": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", - "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - } - } - } -} diff --git a/packages/event-processor/package.json b/packages/event-processor/package.json deleted file mode 100644 index 151f06960..000000000 --- a/packages/event-processor/package.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "name": "@optimizely/js-sdk-event-processor", - "version": "0.9.5", - "description": "Optimizely Full Stack Event Processor", - "author": "jordangarcia <jordan@optimizely.com>", - "homepage": "https://github.com/optimizely/javascript-sdk/tree/master/packages/event-processor", - "license": "Apache-2.0", - "main": "lib/index.js", - "react-native": "lib/index.react_native.js", - "types": "lib/index.d.ts", - "directories": { - "lib": "lib", - "test": "test" - }, - "files": [ - "lib" - ], - "scripts": { - "clean": "rm -rf lib", - "prebuild": "npm run clean", - "build": "tsc", - "test": "jest", - "prepare": "npm run build", - "prepublishOnly": "npm test" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/optimizely/javascript-sdk.git", - "directory": "packages/event-processor" - }, - "keywords": [ - "optimizely" - ], - "bugs": { - "url": "https://github.com/optimizely/javascript-sdk/issues" - }, - "publishConfig": { - "access": "public" - }, - "dependencies": { - "@optimizely/js-sdk-logging": "^0.3.1", - "@optimizely/js-sdk-utils": "^0.4.0" - }, - "devDependencies": { - "@react-native-community/netinfo": "^5.9.4", - "@react-native-async-storage/async-storage": "^1.2.0", - "@types/jest": "^24.0.9", - "jest": "^23.6.0", - "jest-localstorage-mock": "^2.4.0", - "ts-jest": "^23.10.5", - "typescript": "^4.7.4" - - }, - "peerDependencies": { - "@react-native-community/netinfo": "5.9.4", - "@react-native-async-storage/async-storage": "^1.2.0" - }, - "peerDependenciesMeta": { - "@react-native-async-storage/async-storage": { - "optional": true - }, - "@react-native-community/netinfo": { - "optional": true - } - } -} diff --git a/packages/event-processor/src/eventDispatcher.ts b/packages/event-processor/src/eventDispatcher.ts deleted file mode 100644 index 28f9ae07c..000000000 --- a/packages/event-processor/src/eventDispatcher.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { EventV1 } from "./v1/buildEventV1"; - -export type EventDispatcherResponse = { - statusCode: number -} - -export type EventDispatcherCallback = (response: EventDispatcherResponse) => void - -export interface EventDispatcher { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void -} - -export interface EventV1Request { - url: string - httpVerb: 'POST' | 'PUT' | 'GET' | 'PATCH' - params: EventV1, -} \ No newline at end of file diff --git a/packages/event-processor/src/eventProcessor.ts b/packages/event-processor/src/eventProcessor.ts deleted file mode 100644 index 57826e41e..000000000 --- a/packages/event-processor/src/eventProcessor.ts +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// TODO change this to use Managed from js-sdk-models when available -import { Managed } from './managed' -import { ConversionEvent, ImpressionEvent } from './events' -import { EventV1Request } from './eventDispatcher' -import { EventQueue, DefaultEventQueue, SingleEventQueue } from './eventQueue' -import { getLogger } from '@optimizely/js-sdk-logging' -import { NOTIFICATION_TYPES, NotificationCenter } from '@optimizely/js-sdk-utils' - -export const DEFAULT_FLUSH_INTERVAL = 30000 // Unit is ms - default flush interval is 30s -export const DEFAULT_BATCH_SIZE = 10 - -const logger = getLogger('EventProcessor') - -export type ProcessableEvent = ConversionEvent | ImpressionEvent - -export type EventDispatchResult = { result: boolean; event: ProcessableEvent } - -export interface EventProcessor extends Managed { - process(event: ProcessableEvent): void -} - -export function validateAndGetFlushInterval(flushInterval: number): number { - if (flushInterval <= 0) { - logger.warn( - `Invalid flushInterval ${flushInterval}, defaulting to ${DEFAULT_FLUSH_INTERVAL}`, - ) - flushInterval = DEFAULT_FLUSH_INTERVAL - } - return flushInterval -} - -export function validateAndGetBatchSize(batchSize: number): number { - batchSize = Math.floor(batchSize) - if (batchSize < 1) { - logger.warn( - `Invalid batchSize ${batchSize}, defaulting to ${DEFAULT_BATCH_SIZE}`, - ) - batchSize = DEFAULT_BATCH_SIZE - } - batchSize = Math.max(1, batchSize) - return batchSize -} - -export function getQueue(batchSize: number, flushInterval: number, sink: any, batchComparator: any): EventQueue<ProcessableEvent> { - let queue: EventQueue<ProcessableEvent> - if (batchSize > 1) { - queue = new DefaultEventQueue<ProcessableEvent>({ - flushInterval, - maxQueueSize: batchSize, - sink, - batchComparator, - }) - } else { - queue = new SingleEventQueue({ sink }) - } - return queue -} - -export function sendEventNotification(notificationCenter: NotificationCenter | undefined, event: EventV1Request): void { - if (notificationCenter) { - notificationCenter.sendNotifications( - NOTIFICATION_TYPES.LOG_EVENT, - event, - ) - } -} diff --git a/packages/event-processor/src/eventQueue.ts b/packages/event-processor/src/eventQueue.ts deleted file mode 100644 index 8a9438a83..000000000 --- a/packages/event-processor/src/eventQueue.ts +++ /dev/null @@ -1,159 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getLogger } from '@optimizely/js-sdk-logging' -// TODO change this to use Managed from js-sdk-models when available -import { Managed } from './managed' - -const logger = getLogger('EventProcessor') - -export type EventQueueSink<K> = (buffer: K[]) => Promise<any> - -export interface EventQueue<K> extends Managed { - enqueue(event: K): void -} - -export interface EventQueueFactory<K> { - createEventQueue(config: { - sink: EventQueueSink<K> - flushInterval: number - maxQueueSize: number - }): EventQueue<K> -} - -class Timer { - private timeout: number - private callback: () => void - private timeoutId?: number - - constructor({ timeout, callback }: { timeout: number; callback: () => void }) { - this.timeout = Math.max(timeout, 0) - this.callback = callback - } - - start(): void { - this.timeoutId = setTimeout(this.callback, this.timeout) as any - } - - refresh(): void { - this.stop() - this.start() - } - - stop(): void { - if (this.timeoutId) { - clearTimeout(this.timeoutId as any) - } - } -} - -export class SingleEventQueue<K> implements EventQueue<K> { - private sink: EventQueueSink<K> - - constructor({ sink }: { sink: EventQueueSink<K> }) { - this.sink = sink - } - - start(): void { - // no-op - } - - stop(): Promise<any> { - // no-op - return Promise.resolve() - } - - enqueue(event: K): void { - this.sink([event]) - } -} - -export class DefaultEventQueue<K> implements EventQueue<K> { - // expose for testing - public timer: Timer - private buffer: K[] - private maxQueueSize: number - private sink: EventQueueSink<K> - // batchComparator is called to determine whether two events can be included - // together in the same batch - private batchComparator: (eventA: K, eventB: K) => boolean - private started: boolean - - constructor({ - flushInterval, - maxQueueSize, - sink, - batchComparator, - }: { - flushInterval: number - maxQueueSize: number - sink: EventQueueSink<K> - batchComparator: (eventA: K, eventB: K) => boolean - }) { - this.buffer = [] - this.maxQueueSize = Math.max(maxQueueSize, 1) - this.sink = sink - this.batchComparator = batchComparator - this.timer = new Timer({ - callback: this.flush.bind(this), - timeout: flushInterval, - }) - this.started = false - } - - start(): void { - this.started = true - // dont start the timer until the first event is enqueued - } - - stop(): Promise<any> { - this.started = false - const result = this.sink(this.buffer) - this.buffer = [] - this.timer.stop() - return result - } - - enqueue(event: K): void { - if (!this.started) { - logger.warn('Queue is stopped, not accepting event') - return - } - - // If new event cannot be included into the current batch, flush so it can - // be in its own new batch. - const bufferedEvent: K | undefined = this.buffer[0] - if (bufferedEvent && !this.batchComparator(bufferedEvent, event)) { - this.flush() - } - - // start the timer when the first event is put in - if (this.buffer.length === 0) { - this.timer.refresh() - } - this.buffer.push(event) - - if (this.buffer.length >= this.maxQueueSize) { - this.flush() - } - } - - flush() { - this.sink(this.buffer) - this.buffer = [] - this.timer.stop() - } -} diff --git a/packages/event-processor/src/events.ts b/packages/event-processor/src/events.ts deleted file mode 100644 index a28d86773..000000000 --- a/packages/event-processor/src/events.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export type VisitorAttribute = { - entityId: string - key: string - value: string | number | boolean -} - -export interface BaseEvent { - type: 'impression' | 'conversion' - timestamp: number - uuid: string - - // projectConfig stuff - context: { - accountId: string - projectId: string - clientName: string - clientVersion: string - revision: string - anonymizeIP: boolean - botFiltering?: boolean - } -} - -export interface ImpressionEvent extends BaseEvent { - type: 'impression' - - user: { - id: string - attributes: VisitorAttribute[] - } - - layer: { - id: string | null - } | null - - experiment: { - id: string | null - key: string - } | null - - variation: { - id: string | null - key: string - } | null - - ruleKey: string - flagKey: string - ruleType: string - enabled: boolean -} - -export interface ConversionEvent extends BaseEvent { - type: 'conversion' - - user: { - id: string - attributes: VisitorAttribute[] - } - - event: { - id: string | null - key: string - } - - revenue: number | null - value: number | null - tags: EventTags | undefined -} - -export type EventTags = { - [key: string]: string | number | null -} - -export function areEventContextsEqual(eventA: BaseEvent, eventB: BaseEvent): boolean { - const contextA = eventA.context - const contextB = eventB.context - return ( - contextA.accountId === contextB.accountId && - contextA.projectId === contextB.projectId && - contextA.clientName === contextB.clientName && - contextA.clientVersion === contextB.clientVersion && - contextA.revision === contextB.revision && - contextA.anonymizeIP === contextB.anonymizeIP && - contextA.botFiltering === contextB.botFiltering - ) -} diff --git a/packages/event-processor/src/index.react_native.ts b/packages/event-processor/src/index.react_native.ts deleted file mode 100644 index 2e3ac34e4..000000000 --- a/packages/event-processor/src/index.react_native.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export * from './events' -export * from './eventProcessor' -export * from './eventDispatcher' -export * from './managed' -export * from './pendingEventsDispatcher' -export * from './v1/buildEventV1' -export * from './v1/v1EventProcessor.react_native' diff --git a/packages/event-processor/src/index.ts b/packages/event-processor/src/index.ts deleted file mode 100644 index d2f916615..000000000 --- a/packages/event-processor/src/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export * from './events' -export * from './eventProcessor' -export * from './eventDispatcher' -export * from './managed' -export * from './pendingEventsDispatcher' -export * from './v1/buildEventV1' -export * from './v1/v1EventProcessor' diff --git a/packages/event-processor/src/pendingEventsDispatcher.ts b/packages/event-processor/src/pendingEventsDispatcher.ts deleted file mode 100644 index f94f9a253..000000000 --- a/packages/event-processor/src/pendingEventsDispatcher.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { getLogger } from '@optimizely/js-sdk-logging' -import { EventDispatcher, EventV1Request, EventDispatcherCallback } from './eventDispatcher' -import { PendingEventsStore, LocalStorageStore } from './pendingEventsStore' -import { generateUUID, getTimestamp } from '@optimizely/js-sdk-utils' - -const logger = getLogger('EventProcessor') - -export type DispatcherEntry = { - uuid: string - timestamp: number - request: EventV1Request -} - -export class PendingEventsDispatcher implements EventDispatcher { - protected dispatcher: EventDispatcher - protected store: PendingEventsStore<DispatcherEntry> - - constructor({ - eventDispatcher, - store, - }: { - eventDispatcher: EventDispatcher - store: PendingEventsStore<DispatcherEntry> - }) { - this.dispatcher = eventDispatcher - this.store = store - } - - dispatchEvent(request: EventV1Request, callback: EventDispatcherCallback): void { - this.send( - { - uuid: generateUUID(), - timestamp: getTimestamp(), - request, - }, - callback, - ) - } - - sendPendingEvents(): void { - const pendingEvents = this.store.values() - - logger.debug('Sending %s pending events from previous page', pendingEvents.length) - - pendingEvents.forEach(item => { - try { - this.send(item, () => {}) - } catch (e) {} - }) - } - - protected send(entry: DispatcherEntry, callback: EventDispatcherCallback): void { - this.store.set(entry.uuid, entry) - - this.dispatcher.dispatchEvent(entry.request, response => { - this.store.remove(entry.uuid) - callback(response) - }) - } -} - -export class LocalStoragePendingEventsDispatcher extends PendingEventsDispatcher { - constructor({ eventDispatcher }: { eventDispatcher: EventDispatcher }) { - super({ - eventDispatcher, - store: new LocalStorageStore({ - // TODO make this configurable - maxValues: 100, - key: 'fs_optly_pending_events', - }), - }) - } -} diff --git a/packages/event-processor/src/pendingEventsStore.ts b/packages/event-processor/src/pendingEventsStore.ts deleted file mode 100644 index 60f424c71..000000000 --- a/packages/event-processor/src/pendingEventsStore.ts +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { objectValues } from '@optimizely/js-sdk-utils' -import { getLogger } from '@optimizely/js-sdk-logging'; - -const logger = getLogger('EventProcessor') - -export interface PendingEventsStore<K> { - get(key: string): K | null - - set(key: string, value: K): void - - remove(key: string): void - - values(): K[] - - clear(): void - - replace(newMap: { [key: string]: K }): void -} - -interface StoreEntry { - uuid: string - timestamp: number -} - -export class LocalStorageStore<K extends StoreEntry> implements PendingEventsStore<K> { - protected LS_KEY: string - protected maxValues: number - - constructor({ key, maxValues = 1000 }: { key: string; maxValues?: number }) { - this.LS_KEY = key - this.maxValues = maxValues - } - - get(key: string): K | null { - return this.getMap()[key] || null - } - - set(key: string, value: K): void { - const map = this.getMap() - map[key] = value - this.replace(map) - } - - remove(key: string): void { - const map = this.getMap() - delete map[key] - this.replace(map) - } - - values(): K[] { - return objectValues(this.getMap()) - } - - clear(): void { - this.replace({}) - } - - replace(map: { [key: string]: K }): void { - try { - // This is a temporary fix to support React Native which does not have localStorage. - window.localStorage && localStorage.setItem(this.LS_KEY, JSON.stringify(map)) - this.clean() - } catch (e: any) { - logger.error(e) - } - } - - private clean() { - const map = this.getMap() - const keys = Object.keys(map) - const toRemove = keys.length - this.maxValues - if (toRemove < 1) { - return - } - - const entries = keys.map(key => ({ - key, - value: map[key] - })) - - entries.sort((a, b) => a.value.timestamp - b.value.timestamp) - - for (var i = 0; i < toRemove; i++) { - delete map[entries[i].key] - } - - this.replace(map) - } - - private getMap(): { [key: string]: K } { - try { - // This is a temporary fix to support React Native which does not have localStorage. - const data = window.localStorage && localStorage.getItem(this.LS_KEY); - if (data) { - return (JSON.parse(data) as { [key: string]: K }) || {} - } - } catch (e: any) { - logger.error(e) - } - return {} - } -} diff --git a/packages/event-processor/src/persistentKeyValueCache.ts b/packages/event-processor/src/persistentKeyValueCache.ts deleted file mode 100644 index 9b3ef9fac..000000000 --- a/packages/event-processor/src/persistentKeyValueCache.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * An Interface to implement a persistent key value cache which supports strings as keys - * and JSON Object as value. - */ -export default interface PersistentKeyValueCache { - /** - * Returns value stored against a key or null if not found. - * @param key - * @returns - * Resolves promise with - * 1. Object if value found was stored as a JSON Object. - * 2. null if the key does not exist in the cache. - * Rejects the promise in case of an error - */ - get(key: string): Promise<any | null>; - - /** - * Stores Object in the persistent cache against a key - * @param key - * @param val - * @returns - * Resolves promise without a value if successful - * Rejects the promise in case of an error - */ - set(key: string, val: any): Promise<void>; - - /** - * Checks if a key exists in the cache - * @param key - * Resolves promise with - * 1. true if the key exists - * 2. false if the key does not exist - * Rejects the promise in case of an error - */ - contains(key: string): Promise<boolean>; - - /** - * Removes the key value pair from cache. - * @param key - * Resolves promise without a value if successful - * Rejects the promise in case of an error - */ - remove(key: string): Promise<void>; -} diff --git a/packages/event-processor/src/reactNativeAsyncStorageCache.ts b/packages/event-processor/src/reactNativeAsyncStorageCache.ts deleted file mode 100644 index 54d057cb1..000000000 --- a/packages/event-processor/src/reactNativeAsyncStorageCache.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import AsyncStorage from '@react-native-async-storage/async-storage'; - -import PersistentKeyValueCache from './persistentKeyValueCache'; - -export default class ReactNativeAsyncStorageCache implements PersistentKeyValueCache { - get(key: string): Promise<any | null> { - return AsyncStorage.getItem(key).then((val: string | null) => { - if (!val) { - return null; - } - try { - return JSON.parse(val); - } catch (ex) { - throw ex; - } - }); - } - - set(key: string, val: any): Promise<void> { - try { - return AsyncStorage.setItem(key, JSON.stringify(val)); - } catch (ex) { - return Promise.reject(ex); - } - } - - contains(key: string): Promise<boolean> { - return AsyncStorage.getItem(key).then((val: string | null) => val !== null); - } - - remove(key: string): Promise<void> { - return AsyncStorage.removeItem(key); - } -} diff --git a/packages/event-processor/src/reactNativeEventsStore.ts b/packages/event-processor/src/reactNativeEventsStore.ts deleted file mode 100644 index ff303f01c..000000000 --- a/packages/event-processor/src/reactNativeEventsStore.ts +++ /dev/null @@ -1,81 +0,0 @@ - -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { getLogger } from '@optimizely/js-sdk-logging' -import { objectValues } from "@optimizely/js-sdk-utils" - -import { Synchronizer } from './synchronizer' -import ReactNativeAsyncStorageCache from './reactNativeAsyncStorageCache' - -const logger = getLogger('ReactNativeEventsStore') - -/** - * A key value store which stores objects of type T with string keys - */ -export class ReactNativeEventsStore<T> { - private maxSize: number - private storeKey: string - private synchronizer: Synchronizer = new Synchronizer() - private cache: ReactNativeAsyncStorageCache = new ReactNativeAsyncStorageCache() - - constructor(maxSize: number, storeKey: string) { - this.maxSize = maxSize - this.storeKey = storeKey - } - - public async set(key: string, event: T): Promise<string> { - await this.synchronizer.getLock() - const eventsMap: {[key: string]: T} = await this.cache.get(this.storeKey) || {} - if (Object.keys(eventsMap).length < this.maxSize) { - eventsMap[key] = event - await this.cache.set(this.storeKey, eventsMap) - } else { - logger.warn('React native events store is full. Store key: %s', this.storeKey) - } - this.synchronizer.releaseLock() - return key - } - - public async get(key: string): Promise<T> { - await this.synchronizer.getLock() - const eventsMap: {[key: string]: T} = await this.cache.get(this.storeKey) || {} - this.synchronizer.releaseLock() - return eventsMap[key] - } - - public async getEventsMap(): Promise<{[key: string]: T}> { - return await this.cache.get(this.storeKey) || {} - } - - public async getEventsList(): Promise<T[]> { - await this.synchronizer.getLock() - const eventsMap: {[key: string]: T} = await this.cache.get(this.storeKey) || {} - this.synchronizer.releaseLock() - return objectValues(eventsMap) - } - - public async remove(key: string): Promise<void> { - await this.synchronizer.getLock() - const eventsMap: {[key: string]: T} = await this.cache.get(this.storeKey) || {} - eventsMap[key] && delete eventsMap[key] - await this.cache.set(this.storeKey, eventsMap) - this.synchronizer.releaseLock() - } - - public async clear(): Promise<void> { - await this.cache.remove(this.storeKey) - } -} diff --git a/packages/event-processor/src/requestTracker.ts b/packages/event-processor/src/requestTracker.ts deleted file mode 100644 index e3f774690..000000000 --- a/packages/event-processor/src/requestTracker.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * RequestTracker keeps track of in-flight requests for EventProcessor using - * an internal counter. It exposes methods for adding a new request to be - * tracked, and getting a Promise representing the completion of currently - * tracked requests. - */ -class RequestTracker { - private reqsInFlightCount: number = 0 - private reqsCompleteResolvers: Array<() => void> = [] - - /** - * Track the argument request (represented by a Promise). reqPromise will feed - * into the state of Promises returned by onRequestsComplete. - * @param {Promise<void>} reqPromise - */ - public trackRequest(reqPromise: Promise<void>): void { - this.reqsInFlightCount++ - const onReqComplete = () => { - this.reqsInFlightCount-- - if (this.reqsInFlightCount === 0) { - this.reqsCompleteResolvers.forEach(resolver => resolver()) - this.reqsCompleteResolvers = [] - } - } - reqPromise.then(onReqComplete, onReqComplete) - } - - /** - * Return a Promise that fulfills after all currently-tracked request promises - * are resolved. - * @return {Promise<void>} - */ - public onRequestsComplete(): Promise<void> { - return new Promise(resolve => { - if (this.reqsInFlightCount === 0) { - resolve() - } else { - this.reqsCompleteResolvers.push(resolve) - } - }) - } -} - -export default RequestTracker diff --git a/packages/event-processor/src/synchronizer.ts b/packages/event-processor/src/synchronizer.ts deleted file mode 100644 index 2d5d861f2..000000000 --- a/packages/event-processor/src/synchronizer.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This synchronizer makes sure the operations are atomic using promises. - */ -export class Synchronizer { - private lockPromises: Promise<void>[] = [] - private resolvers: any[] = [] - - // Adds a promise to the existing list and returns the promise so that the code block can wait for its turn - public async getLock(): Promise<void> { - this.lockPromises.push(new Promise(resolve => this.resolvers.push(resolve))) - if (this.lockPromises.length === 1) { - return - } - await this.lockPromises[this.lockPromises.length - 2] - } - - // Resolves first promise in the array so that the code block waiting on the first promise can continue execution - public releaseLock(): void { - if (this.lockPromises.length > 0) { - this.lockPromises.shift() - const resolver = this.resolvers.shift() - resolver() - return - } - } -} diff --git a/packages/event-processor/src/v1/buildEventV1.ts b/packages/event-processor/src/v1/buildEventV1.ts deleted file mode 100644 index 59ab01017..000000000 --- a/packages/event-processor/src/v1/buildEventV1.ts +++ /dev/null @@ -1,255 +0,0 @@ -import { EventTags, ConversionEvent, ImpressionEvent, VisitorAttribute } from '../events' -import { ProcessableEvent } from '../eventProcessor' -import { EventV1Request } from '../eventDispatcher' - -const ACTIVATE_EVENT_KEY = 'campaign_activated' -const CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom' -const BOT_FILTERING_KEY = '$opt_bot_filtering' - -export type EventV1 = { - account_id: string - project_id: string - revision: string - client_name: string - client_version: string - anonymize_ip: boolean - enrich_decisions: boolean - visitors: Visitor[] -} - -type Visitor = { - snapshots: Visitor.Snapshot[] - visitor_id: string - attributes: Visitor.Attribute[] -} - -namespace Visitor { - type AttributeType = 'custom' - - export type Attribute = { - // attribute id - entity_id: string - // attribute key - key: string - type: AttributeType - value: string | number | boolean - } - - export type Snapshot = { - decisions?: Decision[] - events: SnapshotEvent[] - } - - type Decision = { - campaign_id: string | null - experiment_id: string | null - variation_id: string | null - metadata: Metadata - } - - type Metadata = { - flag_key: string; - rule_key: string; - rule_type: string; - variation_key: string; - enabled: boolean; - } - - export type SnapshotEvent = { - entity_id: string | null - timestamp: number - uuid: string - key: string - revenue?: number - value?: number - tags?: EventTags - } -} - - - -type Attributes = { - [key: string]: string | number | boolean -} - -/** - * Given an array of batchable Decision or ConversionEvent events it returns - * a single EventV1 with proper batching - * - * @param {ProcessableEvent[]} events - * @returns {EventV1} - */ -export function makeBatchedEventV1(events: ProcessableEvent[]): EventV1 { - const visitors: Visitor[] = [] - const data = events[0] - - events.forEach(event => { - if (event.type === 'conversion' || event.type === 'impression') { - let visitor = makeVisitor(event) - - if (event.type === 'impression') { - visitor.snapshots.push(makeDecisionSnapshot(event)) - } else if (event.type === 'conversion') { - visitor.snapshots.push(makeConversionSnapshot(event)) - } - - visitors.push(visitor) - } - }) - - return { - client_name: data.context.clientName, - client_version: data.context.clientVersion, - - account_id: data.context.accountId, - project_id: data.context.projectId, - revision: data.context.revision, - anonymize_ip: data.context.anonymizeIP, - enrich_decisions: true, - - visitors, - } -} - -function makeConversionSnapshot(conversion: ConversionEvent): Visitor.Snapshot { - let tags: EventTags = { - ...conversion.tags, - } - - delete tags['revenue'] - delete tags['value'] - - const event: Visitor.SnapshotEvent = { - entity_id: conversion.event.id, - key: conversion.event.key, - timestamp: conversion.timestamp, - uuid: conversion.uuid, - } - - if (conversion.tags) { - event.tags = conversion.tags - } - - if (conversion.value != null) { - event.value = conversion.value - } - - if (conversion.revenue != null) { - event.revenue = conversion.revenue - } - - return { - events: [event], - } -} - -function makeDecisionSnapshot(event: ImpressionEvent): Visitor.Snapshot { - const { layer, experiment, variation, ruleKey, flagKey, ruleType, enabled } = event - let layerId = layer ? layer.id : null - let experimentId = experiment?.id ?? '' - let variationId = variation?.id ?? '' - let variationKey = variation ? variation.key : '' - - return { - decisions: [ - { - campaign_id: layerId, - experiment_id: experimentId, - variation_id: variationId, - metadata: { - flag_key: flagKey, - rule_key: ruleKey, - rule_type: ruleType, - variation_key: variationKey, - enabled: enabled, - }, - }, - ], - events: [ - { - entity_id: layerId, - timestamp: event.timestamp, - key: ACTIVATE_EVENT_KEY, - uuid: event.uuid, - }, - ], - } -} - -function makeVisitor(data: ImpressionEvent | ConversionEvent): Visitor { - const visitor: Visitor = { - snapshots: [], - visitor_id: data.user.id, - attributes: [], - } - - data.user.attributes.forEach(attr => { - visitor.attributes.push({ - entity_id: attr.entityId, - key: attr.key, - type: 'custom' as 'custom', // tell the compiler this is always string "custom" - value: attr.value, - }) - }) - - if (typeof data.context.botFiltering === 'boolean') { - visitor.attributes.push({ - entity_id: BOT_FILTERING_KEY, - key: BOT_FILTERING_KEY, - type: CUSTOM_ATTRIBUTE_FEATURE_TYPE, - value: data.context.botFiltering, - }) - } - return visitor -} - -/** - * Event for usage with v1 logtier - * - * @export - * @interface EventBuilderV1 - */ - -export function buildImpressionEventV1(data: ImpressionEvent): EventV1 { - const visitor = makeVisitor(data) - visitor.snapshots.push(makeDecisionSnapshot(data)) - - return { - client_name: data.context.clientName, - client_version: data.context.clientVersion, - - account_id: data.context.accountId, - project_id: data.context.projectId, - revision: data.context.revision, - anonymize_ip: data.context.anonymizeIP, - enrich_decisions: true, - - visitors: [visitor], - } -} - -export function buildConversionEventV1(data: ConversionEvent): EventV1 { - const visitor = makeVisitor(data) - visitor.snapshots.push(makeConversionSnapshot(data)) - - return { - client_name: data.context.clientName, - client_version: data.context.clientVersion, - - account_id: data.context.accountId, - project_id: data.context.projectId, - revision: data.context.revision, - anonymize_ip: data.context.anonymizeIP, - enrich_decisions: true, - - visitors: [visitor], - } -} - -export function formatEvents(events: ProcessableEvent[]): EventV1Request { - return { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1(events), - } -} diff --git a/packages/event-processor/src/v1/v1EventProcessor.react_native.ts b/packages/event-processor/src/v1/v1EventProcessor.react_native.ts deleted file mode 100644 index cf2274674..000000000 --- a/packages/event-processor/src/v1/v1EventProcessor.react_native.ts +++ /dev/null @@ -1,235 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { - generateUUID, - NotificationCenter, - objectEntries, -} from '@optimizely/js-sdk-utils' -import { - NetInfoState, - addEventListener as addConnectionListener, -} from "@react-native-community/netinfo" -import { getLogger } from '@optimizely/js-sdk-logging' - -import { - getQueue, - EventProcessor, - ProcessableEvent, - sendEventNotification, - validateAndGetBatchSize, - validateAndGetFlushInterval, - DEFAULT_BATCH_SIZE, - DEFAULT_FLUSH_INTERVAL, -} from "../eventProcessor" -import { ReactNativeEventsStore } from '../reactNativeEventsStore' -import { Synchronizer } from '../synchronizer' -import { EventQueue } from '../eventQueue' -import RequestTracker from '../requestTracker' -import { areEventContextsEqual } from '../events' -import { formatEvents } from './buildEventV1' -import { - EventV1Request, - EventDispatcher, - EventDispatcherResponse, -} from '../eventDispatcher' - -const logger = getLogger('ReactNativeEventProcessor') - -const DEFAULT_MAX_QUEUE_SIZE = 10000 -const PENDING_EVENTS_STORE_KEY = 'fs_optly_pending_events' -const EVENT_BUFFER_STORE_KEY = 'fs_optly_event_buffer' - -/** - * React Native Events Processor with Caching support for events when app is offline. - */ -export class LogTierV1EventProcessor implements EventProcessor { - private dispatcher: EventDispatcher - // expose for testing - public queue: EventQueue<ProcessableEvent> - private notificationCenter?: NotificationCenter - private requestTracker: RequestTracker - - private unsubscribeNetInfo: Function | null = null - private isInternetReachable: boolean = true - private pendingEventsPromise: Promise<void> | null = null - private synchronizer: Synchronizer = new Synchronizer() - - // If a pending event fails to dispatch, this indicates skipping further events to preserve sequence in the next retry. - private shouldSkipDispatchToPreserveSequence: boolean = false - - /** - * This Stores Formatted events before dispatching. The events are removed after they are successfully dispatched. - * Stored events are retried on every new event dispatch, when connection becomes available again or when SDK initializes the next time. - */ - private pendingEventsStore: ReactNativeEventsStore<EventV1Request> - - /** - * This stores individual events generated from the SDK till they are part of the pending buffer. - * The store is cleared right before the event is formatted to be dispatched. - * This is to make sure that individual events are not lost when app closes before the buffer was flushed. - */ - private eventBufferStore: ReactNativeEventsStore<ProcessableEvent> - - constructor({ - dispatcher, - flushInterval = DEFAULT_FLUSH_INTERVAL, - batchSize = DEFAULT_BATCH_SIZE, - maxQueueSize = DEFAULT_MAX_QUEUE_SIZE, - notificationCenter, - }: { - dispatcher: EventDispatcher - flushInterval?: number - batchSize?: number - maxQueueSize?: number - notificationCenter?: NotificationCenter - }) { - this.dispatcher = dispatcher - this.notificationCenter = notificationCenter - this.requestTracker = new RequestTracker() - - flushInterval = validateAndGetFlushInterval(flushInterval) - batchSize = validateAndGetBatchSize(batchSize) - this.queue = getQueue(batchSize, flushInterval, this.drainQueue.bind(this), areEventContextsEqual) - this.pendingEventsStore = new ReactNativeEventsStore(maxQueueSize, PENDING_EVENTS_STORE_KEY) - this.eventBufferStore = new ReactNativeEventsStore(maxQueueSize, EVENT_BUFFER_STORE_KEY) - } - - private async connectionListener(state: NetInfoState) { - if (this.isInternetReachable && !state.isInternetReachable) { - this.isInternetReachable = false - logger.debug('Internet connection lost') - return - } - if (!this.isInternetReachable && state.isInternetReachable) { - this.isInternetReachable = true - logger.debug('Internet connection is restored, attempting to dispatch pending events') - await this.processPendingEvents() - this.shouldSkipDispatchToPreserveSequence = false - } - } - - private isSuccessResponse(status: number): boolean { - return status >= 200 && status < 400 - } - - private async drainQueue(buffer: ProcessableEvent[]): Promise<void> { - if (buffer.length === 0) { - return - } - - await this.synchronizer.getLock() - - // Retry pending failed events while draining queue - await this.processPendingEvents() - - logger.debug('draining queue with %s events', buffer.length) - - const eventCacheKey = generateUUID() - const formattedEvent = formatEvents(buffer) - - // Store formatted event before dispatching to be retried later in case of failure. - await this.pendingEventsStore.set(eventCacheKey, formattedEvent) - - // Clear buffer because the buffer has become a formatted event and is already stored in pending cache. - for (const {uuid} of buffer) { - await this.eventBufferStore.remove(uuid) - } - - if (!this.shouldSkipDispatchToPreserveSequence) { - await this.dispatchEvent(eventCacheKey, formattedEvent) - } - - // Resetting skip flag because current sequence of events have all been processed - this.shouldSkipDispatchToPreserveSequence = false - - this.synchronizer.releaseLock() - } - - private async processPendingEvents(): Promise<void> { - logger.debug('Processing pending events from offline storage') - if (!this.pendingEventsPromise) { - // Only process events if existing promise is not in progress - this.pendingEventsPromise = this.getPendingEventsPromise() - } else { - logger.debug('Already processing pending events, returning the existing promise') - } - await this.pendingEventsPromise - this.pendingEventsPromise = null - } - - private async getPendingEventsPromise(): Promise<void> { - const formattedEvents: {[key: string]: any} = await this.pendingEventsStore.getEventsMap() - const eventEntries = objectEntries(formattedEvents) - logger.debug('Processing %s pending events', eventEntries.length) - // Using for loop to be able to wait for previous dispatch to finish before moving on to the new one - for (const [eventKey, event] of eventEntries) { - // If one event dispatch failed, skip subsequent events to preserve sequence - if (this.shouldSkipDispatchToPreserveSequence) { - return - } - await this.dispatchEvent(eventKey, event) - } - } - - private async dispatchEvent(eventCacheKey: string, event: EventV1Request): Promise<void> { - const requestPromise = new Promise<void>((resolve) => { - this.dispatcher.dispatchEvent(event, async ({ statusCode }: EventDispatcherResponse) => { - if (this.isSuccessResponse(statusCode)) { - await this.pendingEventsStore.remove(eventCacheKey) - } else { - this.shouldSkipDispatchToPreserveSequence = true - logger.warn('Failed to dispatch event, Response status Code: %s', statusCode) - } - resolve() - }) - sendEventNotification(this.notificationCenter, event) - }) - // Tracking all the requests to dispatch to make sure request is completed before fulfilling the `stop` promise - this.requestTracker.trackRequest(requestPromise) - return requestPromise - } - - public async start(): Promise<void> { - this.queue.start() - this.unsubscribeNetInfo = addConnectionListener(this.connectionListener.bind(this)) - - await this.processPendingEvents() - this.shouldSkipDispatchToPreserveSequence = false - - // Process individual events pending from the buffer. - const events: ProcessableEvent[] = await this.eventBufferStore.getEventsList() - await this.eventBufferStore.clear() - events.forEach(this.process.bind(this)) - } - - public process(event: ProcessableEvent): void { - // Adding events to buffer store. If app closes before dispatch, we can reprocess next time the app initializes - this.eventBufferStore.set(event.uuid, event).then(() => { - this.queue.enqueue(event) - }) - } - - public async stop(): Promise<void> { - // swallow - an error stopping this queue shouldn't prevent this from stopping - try { - this.unsubscribeNetInfo && this.unsubscribeNetInfo() - await this.queue.stop() - return this.requestTracker.onRequestsComplete() - } catch (e: any) { - logger.error('Error stopping EventProcessor: "%s"', e?.message, e) - } - } -} diff --git a/packages/event-processor/src/v1/v1EventProcessor.ts b/packages/event-processor/src/v1/v1EventProcessor.ts deleted file mode 100644 index 1dcbf0768..000000000 --- a/packages/event-processor/src/v1/v1EventProcessor.ts +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { getLogger } from '@optimizely/js-sdk-logging' -import { NotificationCenter } from '@optimizely/js-sdk-utils' - -import { EventDispatcher } from '../eventDispatcher' -import { - getQueue, - EventProcessor, - ProcessableEvent, - sendEventNotification, - validateAndGetBatchSize, - validateAndGetFlushInterval, - DEFAULT_BATCH_SIZE, - DEFAULT_FLUSH_INTERVAL, -} from '../eventProcessor' -import { EventQueue } from '../eventQueue' -import RequestTracker from '../requestTracker' -import { areEventContextsEqual } from '../events' -import { formatEvents } from './buildEventV1' - -const logger = getLogger('LogTierV1EventProcessor') - -export class LogTierV1EventProcessor implements EventProcessor { - private dispatcher: EventDispatcher - private queue: EventQueue<ProcessableEvent> - private notificationCenter?: NotificationCenter - private requestTracker: RequestTracker - - constructor({ - dispatcher, - flushInterval = DEFAULT_FLUSH_INTERVAL, - batchSize = DEFAULT_BATCH_SIZE, - notificationCenter, - }: { - dispatcher: EventDispatcher - flushInterval?: number - batchSize?: number - notificationCenter?: NotificationCenter - }) { - this.dispatcher = dispatcher - this.notificationCenter = notificationCenter - this.requestTracker = new RequestTracker() - - flushInterval = validateAndGetFlushInterval(flushInterval) - batchSize = validateAndGetBatchSize(batchSize) - this.queue = getQueue(batchSize, flushInterval, this.drainQueue.bind(this), areEventContextsEqual) - } - - drainQueue(buffer: ProcessableEvent[]): Promise<void> { - const reqPromise = new Promise<void>(resolve => { - logger.debug('draining queue with %s events', buffer.length) - - if (buffer.length === 0) { - resolve() - return - } - - const formattedEvent = formatEvents(buffer) - this.dispatcher.dispatchEvent(formattedEvent, () => { - resolve() - }) - sendEventNotification(this.notificationCenter, formattedEvent) - }) - this.requestTracker.trackRequest(reqPromise) - return reqPromise - } - - process(event: ProcessableEvent): void { - this.queue.enqueue(event) - } - - stop(): Promise<any> { - // swallow - an error stopping this queue shouldn't prevent this from stopping - try { - this.queue.stop() - return this.requestTracker.onRequestsComplete() - } catch (e: any) { - logger.error('Error stopping EventProcessor: "%s"', e.message, e) - } - return Promise.resolve() - } - - async start(): Promise<void> { - this.queue.start() - } -} diff --git a/packages/event-processor/tsconfig.json b/packages/event-processor/tsconfig.json deleted file mode 100644 index 8ca97f079..000000000 --- a/packages/event-processor/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./lib", - "noImplicitAny": true, - "strictNullChecks": true, - }, - "include": [ - "./src" - ], - "exclude": [ - "./lib", - "./src/**/*.spec.ts" - ] -} \ No newline at end of file diff --git a/packages/logging/.gitignore b/packages/logging/.gitignore deleted file mode 100644 index d4a125901..000000000 --- a/packages/logging/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -lib/ -doc/ diff --git a/packages/logging/CHANGELOG.md b/packages/logging/CHANGELOG.md deleted file mode 100644 index 180abfe5f..000000000 --- a/packages/logging/CHANGELOG.md +++ /dev/null @@ -1,27 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). - -## [Unreleased] -Changes that have landed but are not yet released. - -## [0.3.1] - October 13, 2021 - -### Bug Fixes -- Downgrade typescript version to `3.8.x` due to stubbing issues. - -## [0.3.0] - October 8, 2021 - -### New Features -- Upgraded `@optimizely/js-sdk-utils` package to version `0.4.0` - -## [0.2.0] - October 6, 2021 - -### New Features -- Added optional args to `logger.log` to format string only when log level applies ([#706](https://github.com/optimizely/javascript-sdk/pull/706)). - -## [0.1.0] - March 1, 2019 - -Initial release diff --git a/packages/logging/LICENSE b/packages/logging/LICENSE deleted file mode 100644 index b9f80c5bd..000000000 --- a/packages/logging/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2016-2017, Optimizely, Inc. and contributors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/logging/README.md b/packages/logging/README.md deleted file mode 100644 index efa69a82d..000000000 --- a/packages/logging/README.md +++ /dev/null @@ -1,156 +0,0 @@ -# Javascript SDK Logging - -Provides a centralized LogManager and errorHandler for Javascript SDK packages. - -## Installation - -```sh -npm install @optimizely/js-sdk-logging -``` - -## Architecture - -![Logging Architecture](./logging_architecture.png) - - - - **LogHandler** - the component that determines where to write logs. Common log handlers - are `ConsoleLogHandler` or `NoopLogHandler` - - **LogManager** - returns Logger facade instances via LogManager.getLogger(name) - - **LoggerFacade** the internal logging interface available to other packages via `LogManager.getLogger(name)` - - -## Usage - - -#### Using the logger - -```typescript -import { getLogger } from '@optimizely/js-sdk-logging' - -const logger = getLogger('myModule') -logger.log('warn', 'this is a warning') - -logger.debug('string interpolation is easy and %s', 'lazily evaluated') - -logger.info('info logging') -logger.warn('this is a warning') -logger.error('this is an error') - -// `info` `warn` `debug` and `error` all support passing an Error as the last argument -// this will call the registered errorHandler -logger.error('an error occurred: %s', ex.message) - -// also Error passes to errorHandler.handleError(ex) -logger.error('an error occurred: %s', ex.message, ex) - -// if no message is passed will log `ex.message` -logger.error(ex) -``` - -#### Setting the log level - -```typescript -import { LogLevel, setLogLevel } from '@optimizely/js-sdk-logging' - -// can use enum -setLogLevel(LogLevel.ERROR) - -// can also use a string (lowercase or uppercase) -setLogLevel('debug') -setLogLevel('info') -setLogLevel('warn') -setLogLevel('error') -``` - - -#### Setting a LogHandler - -```typescript -import { setLogHandler, ConsoleLogHandler } from '@optimizely/js-sdk-logging' - -const handler = new ConsoleLogHandler({ - logLevel: 'error', - prefix: '[My custom prefix]', // defaults to "[OPTIMIZELY]" -}) - -setLogHandler(handler) -``` - -#### Implementing a custom LogHandler - -Perhaps you want to integrate Optimizely with your own logging system or use an existing library. - -A valid `LogHandler` is anything that implements this interface - -```typescript -interface LogHandler { - log(level: LogLevel, message: string): void -} -``` - -**Example: integrating with Winston** - -```js -import winston from 'winston' -import { setLogHandler, LogLevel } from '@optimizely/js-sdk-logging' - -const winstonLogger = winston.createLogger({ - level: 'info', - format: winston.format.json(), - defaultMeta: { service: 'optimizely' }, - transports: [ - new winston.transports.File({ filename: 'combined.log' }), - ], -}) - -/** - * Convert from optimizely log levels to winston - */ -function convertLogLevels(level) { - switch(level) { - case LogLevel.DEBUG: - return 'debug' - case LogLevel.INFO: - return 'info' - case LogLevel.WARNING: - return 'warning' - case LogLevel.ERROR: - return 'error' - default: - return 'silly' - } -} - -setLogHandler({ - log(level, message) { - winstoLogger.log({ - level: convertLogLevels(level), - message, - }) - } -}) -``` - -### API Interfaces - -```typescript -interface LoggerFacade { - log(level: LogLevel | string, message: string): void - - info(message: string | Error, ...splat: any[]): void - - debug(message: string | Error, ...splat: any[]): void - - warn(message: string | Error, ...splat: any[]): void - - error(message: string | Error, ...splat: any[]): void -} - -interface LogManager { - getLogger(name?: string): LoggerFacade -} - -interface LogHandler { - log(level: LogLevel, message: string): void -} -``` \ No newline at end of file diff --git a/packages/logging/__tests__/logger.spec.ts b/packages/logging/__tests__/logger.spec.ts deleted file mode 100644 index 10529d94a..000000000 --- a/packages/logging/__tests__/logger.spec.ts +++ /dev/null @@ -1,386 +0,0 @@ -/// <reference types="jest" /> -import { - LogLevel, - LogHandler, - LoggerFacade, -} from '../src/models' - -import { - setLogHandler, - setLogLevel, - getLogger, - ConsoleLogHandler, - resetLogger, - getLogLevel, -} from '../src/logger' - -import { resetErrorHandler } from '../src/errorHandler' -import { ErrorHandler, setErrorHandler } from '../src/errorHandler' - -describe('logger', () => { - afterEach(() => { - resetLogger() - resetErrorHandler() - }) - - describe('OptimizelyLogger', () => { - let stubLogger: LogHandler - let logger: LoggerFacade - let stubErrorHandler: ErrorHandler - - beforeEach(() => { - stubLogger = { - log: jest.fn(), - } - stubErrorHandler = { - handleError: jest.fn(), - } - setLogLevel(LogLevel.DEBUG) - setLogHandler(stubLogger) - setErrorHandler(stubErrorHandler) - logger = getLogger() - }) - - describe('setLogLevel', () => { - it('should coerce "debug"', () => { - setLogLevel('debug') - expect(getLogLevel()).toBe(LogLevel.DEBUG) - }) - - it('should coerce "deBug"', () => { - setLogLevel('deBug') - expect(getLogLevel()).toBe(LogLevel.DEBUG) - }) - - it('should coerce "INFO"', () => { - setLogLevel('INFO') - expect(getLogLevel()).toBe(LogLevel.INFO) - }) - - it('should coerce "WARN"', () => { - setLogLevel('WARN') - expect(getLogLevel()).toBe(LogLevel.WARNING) - }) - - it('should coerce "warning"', () => { - setLogLevel('warning') - expect(getLogLevel()).toBe(LogLevel.WARNING) - }) - - it('should coerce "ERROR"', () => { - setLogLevel('WARN') - expect(getLogLevel()).toBe(LogLevel.WARNING) - }) - - it('should default to error if invalid', () => { - setLogLevel('invalid') - expect(getLogLevel()).toBe(LogLevel.ERROR) - }) - }) - - describe('getLogger(name)', () => { - it('should prepend the name in the log messages', () => { - const myLogger = getLogger('doit') - myLogger.info('test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'doit: test') - }) - }) - - describe('logger.log(level, msg)', () => { - it('should work with a string logLevel', () => { - setLogLevel(LogLevel.INFO) - logger.log('info', 'test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test') - }) - - it('should call the loggerBackend when the message logLevel is equal to the configured logLevel threshold', () => { - setLogLevel(LogLevel.INFO) - logger.log(LogLevel.INFO, 'test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test') - }) - - it('should call the loggerBackend when the message logLevel is above to the configured logLevel threshold', () => { - setLogLevel(LogLevel.INFO) - logger.log(LogLevel.WARNING, 'test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test') - }) - - it('should not call the loggerBackend when the message logLevel is above to the configured logLevel threshold', () => { - setLogLevel(LogLevel.INFO) - logger.log(LogLevel.DEBUG, 'test') - - expect(stubLogger.log).toHaveBeenCalledTimes(0) - }) - - it('should not throw if loggerBackend is not supplied', () => { - setLogLevel(LogLevel.INFO) - logger.log(LogLevel.ERROR, 'test') - }) - }) - - describe('logger.info', () => { - it('should handle info(message)', () => { - logger.info('test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test') - }) - it('should handle info(message, ...splat)', () => { - logger.info('test: %s %s', 'hey', 'jude') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test: hey jude') - }) - - it('should handle info(message, ...splat, error)', () => { - const error = new Error('hey') - logger.info('test: %s', 'hey', error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test: hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - - it('should handle info(error)', () => { - const error = new Error('hey') - logger.info(error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - }) - - describe('logger.debug', () => { - it('should handle debug(message)', () => { - logger.debug('test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'test') - }) - - it('should handle debug(message, ...splat)', () => { - logger.debug('test: %s', 'hey') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'test: hey') - }) - - it('should handle debug(message, ...splat, error)', () => { - const error = new Error('hey') - logger.debug('test: %s', 'hey', error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'test: hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - - it('should handle debug(error)', () => { - const error = new Error('hey') - logger.debug(error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - }) - - describe('logger.warn', () => { - it('should handle warn(message)', () => { - logger.warn('test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test') - }) - - it('should handle warn(message, ...splat)', () => { - logger.warn('test: %s', 'hey') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test: hey') - }) - - it('should handle warn(message, ...splat, error)', () => { - const error = new Error('hey') - logger.warn('test: %s', 'hey', error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test: hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - - it('should handle info(error)', () => { - const error = new Error('hey') - logger.warn(error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - }) - - describe('logger.error', () => { - it('should handle error(message)', () => { - logger.error('test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'test') - }) - - it('should handle error(message, ...splat)', () => { - logger.error('test: %s', 'hey') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'test: hey') - }) - - it('should handle error(message, ...splat, error)', () => { - const error = new Error('hey') - logger.error('test: %s', 'hey', error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'test: hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - - it('should handle error(error)', () => { - const error = new Error('hey') - logger.error(error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - - it('should work with an insufficient amount of splat args error(msg, ...splat, message)', () => { - const error = new Error('hey') - logger.error('hey %s', error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'hey undefined') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - }) - - describe('using ConsoleLoggerHandler', () => { - beforeEach(() => { - jest.spyOn(console, 'info').mockImplementation(() => {}) - }) - - afterEach(() => { - jest.resetAllMocks() - }) - - it('should work with BasicLogger', () => { - const logger = new ConsoleLogHandler() - const TIME = '12:00' - setLogHandler(logger) - setLogLevel(LogLevel.INFO) - jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) - - logger.log(LogLevel.INFO, 'hey') - - expect(console.info).toBeCalledTimes(1) - expect(console.info).toBeCalledWith('[OPTIMIZELY] - INFO 12:00 hey') - }) - - it('should set logLevel to ERROR when setLogLevel is called with invalid value', () => { - const logger = new ConsoleLogHandler() - logger.setLogLevel('invalid' as any) - - expect(logger.logLevel).toEqual(LogLevel.ERROR) - }) - - it('should set logLevel to ERROR when setLogLevel is called with no value', () => { - const logger = new ConsoleLogHandler() - // @ts-ignore - logger.setLogLevel() - - expect(logger.logLevel).toEqual(LogLevel.ERROR) - }) - }) - }) - - describe('ConsoleLogger', function() { - beforeEach(() => { - jest.spyOn(console, 'info') - jest.spyOn(console, 'log') - jest.spyOn(console, 'warn') - jest.spyOn(console, 'error') - }) - - afterEach(() => { - jest.resetAllMocks() - }) - - it('should log to console.info for LogLevel.INFO', () => { - const logger = new ConsoleLogHandler({ - logLevel: LogLevel.DEBUG, - }) - const TIME = '12:00' - jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) - - logger.log(LogLevel.INFO, 'test') - - expect(console.info).toBeCalledTimes(1) - expect(console.info).toBeCalledWith('[OPTIMIZELY] - INFO 12:00 test') - }) - - it('should log to console.log for LogLevel.DEBUG', () => { - const logger = new ConsoleLogHandler({ - logLevel: LogLevel.DEBUG, - }) - const TIME = '12:00' - jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) - - logger.log(LogLevel.DEBUG, 'debug') - - expect(console.log).toBeCalledTimes(1) - expect(console.log).toBeCalledWith('[OPTIMIZELY] - DEBUG 12:00 debug') - }) - - it('should log to console.warn for LogLevel.WARNING', () => { - const logger = new ConsoleLogHandler({ - logLevel: LogLevel.DEBUG, - }) - const TIME = '12:00' - jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) - - logger.log(LogLevel.WARNING, 'warning') - - expect(console.warn).toBeCalledTimes(1) - expect(console.warn).toBeCalledWith('[OPTIMIZELY] - WARN 12:00 warning') - }) - - it('should log to console.error for LogLevel.ERROR', () => { - const logger = new ConsoleLogHandler({ - logLevel: LogLevel.DEBUG, - }) - const TIME = '12:00' - jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) - - logger.log(LogLevel.ERROR, 'error') - - expect(console.error).toBeCalledTimes(1) - expect(console.error).toBeCalledWith('[OPTIMIZELY] - ERROR 12:00 error') - }) - - it('should not log if the configured logLevel is higher', () => { - const logger = new ConsoleLogHandler({ - logLevel: LogLevel.INFO, - }) - - logger.log(LogLevel.DEBUG, 'debug') - - expect(console.log).toBeCalledTimes(0) - }) - }) -}) diff --git a/packages/logging/jest.config.js b/packages/logging/jest.config.js deleted file mode 100644 index 391afe8eb..000000000 --- a/packages/logging/jest.config.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - // "roots": [ - // "./src" - // ], - "transform": { - "^.+\\.tsx?$": "ts-jest" - }, - "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$", - "moduleFileExtensions": [ - "ts", - "tsx", - "js", - "jsx", - "json", - "node" - ], -} \ No newline at end of file diff --git a/packages/logging/logging_architecture.png b/packages/logging/logging_architecture.png deleted file mode 100644 index 7f01f74b5..000000000 Binary files a/packages/logging/logging_architecture.png and /dev/null differ diff --git a/packages/logging/package-lock.json b/packages/logging/package-lock.json deleted file mode 100644 index bc84bf13c..000000000 --- a/packages/logging/package-lock.json +++ /dev/null @@ -1,5597 +0,0 @@ -{ - "name": "@optimizely/js-sdk-logging", - "version": "0.3.1", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", - "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", - "dev": true, - "requires": { - "@babel/highlight": "^7.0.0" - } - }, - "@babel/highlight": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", - "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", - "dev": true, - "requires": { - "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - } - } - }, - "@optimizely/js-sdk-utils": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-utils/-/js-sdk-utils-0.4.0.tgz", - "integrity": "sha512-QG2oytnITW+VKTJK+l0RxjaS5VrA6W+AZMzpeg4LCB4Rn4BEKtF+EcW/5S1fBDLAviGq/0TLpkjM3DlFkJ9/Gw==", - "requires": { - "uuid": "^3.3.2" - } - }, - "@types/jest": { - "version": "23.3.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.3.14.tgz", - "integrity": "sha512-Q5hTcfdudEL2yOmluA1zaSyPbzWPmJ3XfSWeP3RyoYvS9hnje1ZyagrZOuQ6+1nQC1Gw+7gap3pLNL3xL6UBug==", - "dev": true - }, - "abab": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.0.tgz", - "integrity": "sha512-sY5AXXVZv4Y1VACTtR11UJCPHHudgY5i26Qj5TypE6DKlIApbwb5uqhXcJ5UUGbvZNRh7EeIoW+LrJumBsKp7w==", - "dev": true - }, - "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", - "dev": true - }, - "acorn-globals": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.0.tgz", - "integrity": "sha512-hMtHj3s5RnuhvHPowpBYvJVj3rAar82JiDQHvGs1zO0l10ocX/xEdBShNHTJaboucJUsScghp74pH3s7EnHHQw==", - "dev": true, - "requires": { - "acorn": "^6.0.1", - "acorn-walk": "^6.0.1" - }, - "dependencies": { - "acorn": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.0.tgz", - "integrity": "sha512-MW/FjM+IvU9CgBzjO3UIPCE2pyEwUsoFl+VGdczOPEdxfGFjuKny/gN54mOuX7Qxmb9Rg9MCn2oKiSUeW+pjrw==", - "dev": true - } - } - }, - "acorn-walk": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", - "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==", - "dev": true - }, - "ajv": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.1.tgz", - "integrity": "sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==", - "dev": true, - "requires": { - "fast-deep-equal": "^2.0.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - } - } - }, - "append-transform": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", - "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", - "dev": true, - "requires": { - "default-require-extensions": "^1.0.0" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - }, - "dependencies": { - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - } - } - }, - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, - "array-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", - "dev": true - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "dev": true - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, - "async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", - "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", - "dev": true, - "requires": { - "lodash": "^4.17.11" - } - }, - "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true - }, - "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", - "dev": true - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" - } - }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "dev": true, - "requires": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" - } - }, - "babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-jest": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-23.6.0.tgz", - "integrity": "sha512-lqKGG6LYXYu+DQh/slrQ8nxXQkEkhugdXsU6St7GmhVS7Ilc/22ArwqXNJrf0QaOBjZB0360qZMwXqDYQHXaew==", - "dev": true, - "requires": { - "babel-plugin-istanbul": "^4.1.6", - "babel-preset-jest": "^23.2.0" - } - }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-istanbul": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", - "integrity": "sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==", - "dev": true, - "requires": { - "babel-plugin-syntax-object-rest-spread": "^6.13.0", - "find-up": "^2.1.0", - "istanbul-lib-instrument": "^1.10.1", - "test-exclude": "^4.2.1" - } - }, - "babel-plugin-jest-hoist": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz", - "integrity": "sha1-5h+uBaHKiAGq3uV6bWa4zvr0QWc=", - "dev": true - }, - "babel-plugin-syntax-object-rest-spread": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", - "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", - "dev": true - }, - "babel-preset-jest": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz", - "integrity": "sha1-jsegOhOPABoaj7HoETZSvxpV2kY=", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^23.2.0", - "babel-plugin-syntax-object-rest-spread": "^6.13.0" - } - }, - "babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", - "dev": true, - "requires": { - "babel-core": "^6.26.0", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "home-or-tmp": "^2.0.0", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "source-map-support": "^0.4.15" - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } - }, - "browser-process-hrtime": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz", - "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw==", - "dev": true - }, - "browser-resolve": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", - "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", - "dev": true, - "requires": { - "resolve": "1.1.7" - }, - "dependencies": { - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - } - } - }, - "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, - "requires": { - "fast-json-stable-stringify": "2.x" - } - }, - "bser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz", - "integrity": "sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk=", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", - "dev": true - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "capture-exit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-1.2.0.tgz", - "integrity": "sha1-HF/MSJ/QqwDU8ax64QcuMXP7q28=", - "dev": true, - "requires": { - "rsvp": "^3.3.3" - } - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", - "dev": true - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "dev": true, - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "combined-stream": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", - "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", - "dev": true, - "optional": true - }, - "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "convert-source-map": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", - "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "core-js": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz", - "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, - "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "cssom": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", - "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==", - "dev": true - }, - "cssstyle": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.2.1.tgz", - "integrity": "sha512-7DYm8qe+gPx/h77QlCyFmX80+fGaE/6A/Ekl0zaszYOubvySO2saYFdQ78P29D0UsULxFKCetDGNaNRUdSF+2A==", - "dev": true, - "requires": { - "cssom": "0.3.x" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "data-urls": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", - "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", - "dev": true, - "requires": { - "abab": "^2.0.0", - "whatwg-mimetype": "^2.2.0", - "whatwg-url": "^7.0.0" - }, - "dependencies": { - "whatwg-url": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.0.0.tgz", - "integrity": "sha512-37GeVSIJ3kn1JgKyjiYNmSLP1yzbpb29jdmwBSgkD9h40/hyrR/OifpVUndji3tmwGgD8qpw7iQu3RSbCrBpsQ==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - } - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "default-require-extensions": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", - "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", - "dev": true, - "requires": { - "strip-bom": "^2.0.0" - } - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, - "detect-newline": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", - "dev": true - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "domexception": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", - "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", - "dev": true, - "requires": { - "webidl-conversions": "^4.0.2" - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz", - "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==", - "dev": true, - "requires": { - "es-to-primitive": "^1.2.0", - "function-bind": "^1.1.1", - "has": "^1.0.3", - "is-callable": "^1.1.4", - "is-regex": "^1.0.4", - "object-keys": "^1.0.12" - } - }, - "es-to-primitive": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz", - "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "escodegen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz", - "integrity": "sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==", - "dev": true, - "requires": { - "esprima": "^3.1.3", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } - } - }, - "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 - }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "exec-sh": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.2.tgz", - "integrity": "sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw==", - "dev": true, - "requires": { - "merge": "^1.2.0" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "^0.1.0" - } - }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "dev": true, - "requires": { - "fill-range": "^2.1.0" - } - }, - "expect": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-23.6.0.tgz", - "integrity": "sha512-dgSoOHgmtn/aDGRVFWclQyPDKl2CQRq0hmIEoUAuQs/2rn2NcvCWcSCovm6BLeuB/7EZuLGu2QfnR+qRt5OM4w==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "jest-diff": "^23.6.0", - "jest-get-type": "^22.1.0", - "jest-matcher-utils": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-regex-util": "^23.3.0" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fb-watchman": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", - "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", - "dev": true, - "requires": { - "bser": "^2.0.0" - } - }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true - }, - "fileset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", - "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", - "dev": true, - "requires": { - "glob": "^7.0.3", - "minimatch": "^3.0.3" - } - }, - "fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", - "dev": true, - "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.7.tgz", - "integrity": "sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==", - "dev": true, - "optional": true, - "requires": { - "nan": "^2.9.2", - "node-pre-gyp": "^0.10.0" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "debug": { - "version": "2.6.9", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true, - "optional": true - }, - "minipass": { - "version": "2.3.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "needle": { - "version": "2.2.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "^2.1.2", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.10.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.6.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "dev": true, - "optional": true - }, - "semver": { - "version": "5.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "4.4.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "yallist": { - "version": "3.0.3", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true, - "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - } - }, - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, - "requires": { - "is-glob": "^2.0.0" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "graceful-fs": { - "version": "4.1.15", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", - "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", - "dev": true - }, - "growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", - "dev": true - }, - "handlebars": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.1.2.tgz", - "integrity": "sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw==", - "dev": true, - "requires": { - "neo-async": "^2.6.0", - "optimist": "^0.6.1", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", - "dev": true, - "requires": { - "ajv": "^6.5.5", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", - "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=", - "dev": true - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", - "dev": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.1" - } - }, - "hosted-git-info": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.7.1.tgz", - "integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==", - "dev": true - }, - "html-encoding-sniffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", - "dev": true, - "requires": { - "whatwg-encoding": "^1.0.1" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "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, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "import-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", - "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", - "dev": true, - "requires": { - "pkg-dir": "^2.0.0", - "resolve-cwd": "^2.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-callable": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz", - "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==", - "dev": true - }, - "is-ci": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", - "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", - "dev": true, - "requires": { - "ci-info": "^1.5.0" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "dev": true, - "requires": { - "is-primitive": "^2.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "is-generator-fn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-1.0.0.tgz", - "integrity": "sha1-lp1J4bszKfa7fwkIm+JleLLd1Go=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "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, - "requires": { - "isobject": "^3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true - }, - "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, - "requires": { - "has": "^1.0.1" - } - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true - }, - "is-symbol": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz", - "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==", - "dev": true, - "requires": { - "has-symbols": "^1.0.0" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "istanbul-api": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.7.tgz", - "integrity": "sha512-4/ApBnMVeEPG3EkSzcw25wDe4N66wxwn+KKn6b47vyek8Xb3NBAcg4xfuQbS7BqcZuTX4wxfD5lVagdggR3gyA==", - "dev": true, - "requires": { - "async": "^2.1.4", - "fileset": "^2.0.2", - "istanbul-lib-coverage": "^1.2.1", - "istanbul-lib-hook": "^1.2.2", - "istanbul-lib-instrument": "^1.10.2", - "istanbul-lib-report": "^1.1.5", - "istanbul-lib-source-maps": "^1.2.6", - "istanbul-reports": "^1.5.1", - "js-yaml": "^3.7.0", - "mkdirp": "^0.5.1", - "once": "^1.4.0" - } - }, - "istanbul-lib-coverage": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", - "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", - "dev": true - }, - "istanbul-lib-hook": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz", - "integrity": "sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==", - "dev": true, - "requires": { - "append-transform": "^0.4.0" - } - }, - "istanbul-lib-instrument": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", - "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", - "dev": true, - "requires": { - "babel-generator": "^6.18.0", - "babel-template": "^6.16.0", - "babel-traverse": "^6.18.0", - "babel-types": "^6.18.0", - "babylon": "^6.18.0", - "istanbul-lib-coverage": "^1.2.1", - "semver": "^5.3.0" - } - }, - "istanbul-lib-report": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz", - "integrity": "sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^1.2.1", - "mkdirp": "^0.5.1", - "path-parse": "^1.0.5", - "supports-color": "^3.1.2" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz", - "integrity": "sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg==", - "dev": true, - "requires": { - "debug": "^3.1.0", - "istanbul-lib-coverage": "^1.2.1", - "mkdirp": "^0.5.1", - "rimraf": "^2.6.1", - "source-map": "^0.5.3" - }, - "dependencies": { - "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", - "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", - "dev": true - } - } - }, - "istanbul-reports": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.5.1.tgz", - "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==", - "dev": true, - "requires": { - "handlebars": "^4.0.3" - } - }, - "jest": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-23.6.0.tgz", - "integrity": "sha512-lWzcd+HSiqeuxyhG+EnZds6iO3Y3ZEnMrfZq/OTGvF/C+Z4fPMCdhWTGSAiO2Oym9rbEXfwddHhh6jqrTF3+Lw==", - "dev": true, - "requires": { - "import-local": "^1.0.0", - "jest-cli": "^23.6.0" - }, - "dependencies": { - "jest-cli": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-23.6.0.tgz", - "integrity": "sha512-hgeD1zRUp1E1zsiyOXjEn4LzRLWdJBV//ukAHGlx6s5mfCNJTbhbHjgxnDUXA8fsKWN/HqFFF6X5XcCwC/IvYQ==", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.1", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "import-local": "^1.0.0", - "is-ci": "^1.0.10", - "istanbul-api": "^1.3.1", - "istanbul-lib-coverage": "^1.2.0", - "istanbul-lib-instrument": "^1.10.1", - "istanbul-lib-source-maps": "^1.2.4", - "jest-changed-files": "^23.4.2", - "jest-config": "^23.6.0", - "jest-environment-jsdom": "^23.4.0", - "jest-get-type": "^22.1.0", - "jest-haste-map": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-regex-util": "^23.3.0", - "jest-resolve-dependencies": "^23.6.0", - "jest-runner": "^23.6.0", - "jest-runtime": "^23.6.0", - "jest-snapshot": "^23.6.0", - "jest-util": "^23.4.0", - "jest-validate": "^23.6.0", - "jest-watcher": "^23.4.0", - "jest-worker": "^23.2.0", - "micromatch": "^2.3.11", - "node-notifier": "^5.2.1", - "prompts": "^0.1.9", - "realpath-native": "^1.0.0", - "rimraf": "^2.5.4", - "slash": "^1.0.0", - "string-length": "^2.0.0", - "strip-ansi": "^4.0.0", - "which": "^1.2.12", - "yargs": "^11.0.0" - } - } - } - }, - "jest-changed-files": { - "version": "23.4.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-23.4.2.tgz", - "integrity": "sha512-EyNhTAUWEfwnK0Is/09LxoqNDOn7mU7S3EHskG52djOFS/z+IT0jT3h3Ql61+dklcG7bJJitIWEMB4Sp1piHmA==", - "dev": true, - "requires": { - "throat": "^4.0.0" - } - }, - "jest-config": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-23.6.0.tgz", - "integrity": "sha512-i8V7z9BeDXab1+VNo78WM0AtWpBRXJLnkT+lyT+Slx/cbP5sZJ0+NDuLcmBE5hXAoK0aUp7vI+MOxR+R4d8SRQ==", - "dev": true, - "requires": { - "babel-core": "^6.0.0", - "babel-jest": "^23.6.0", - "chalk": "^2.0.1", - "glob": "^7.1.1", - "jest-environment-jsdom": "^23.4.0", - "jest-environment-node": "^23.4.0", - "jest-get-type": "^22.1.0", - "jest-jasmine2": "^23.6.0", - "jest-regex-util": "^23.3.0", - "jest-resolve": "^23.6.0", - "jest-util": "^23.4.0", - "jest-validate": "^23.6.0", - "micromatch": "^2.3.11", - "pretty-format": "^23.6.0" - } - }, - "jest-diff": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-23.6.0.tgz", - "integrity": "sha512-Gz9l5Ov+X3aL5L37IT+8hoCUsof1CVYBb2QEkOupK64XyRR3h+uRpYIm97K7sY8diFxowR8pIGEdyfMKTixo3g==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "diff": "^3.2.0", - "jest-get-type": "^22.1.0", - "pretty-format": "^23.6.0" - } - }, - "jest-docblock": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-23.2.0.tgz", - "integrity": "sha1-8IXh8YVI2Z/dabICB+b9VdkTg6c=", - "dev": true, - "requires": { - "detect-newline": "^2.1.0" - } - }, - "jest-each": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-23.6.0.tgz", - "integrity": "sha512-x7V6M/WGJo6/kLoissORuvLIeAoyo2YqLOoCDkohgJ4XOXSqOtyvr8FbInlAWS77ojBsZrafbozWoKVRdtxFCg==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "pretty-format": "^23.6.0" - } - }, - "jest-environment-jsdom": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-23.4.0.tgz", - "integrity": "sha1-BWp5UrP+pROsYqFAosNox52eYCM=", - "dev": true, - "requires": { - "jest-mock": "^23.2.0", - "jest-util": "^23.4.0", - "jsdom": "^11.5.1" - } - }, - "jest-environment-node": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-23.4.0.tgz", - "integrity": "sha1-V+gO0IQd6jAxZ8zozXlSHeuv3hA=", - "dev": true, - "requires": { - "jest-mock": "^23.2.0", - "jest-util": "^23.4.0" - } - }, - "jest-get-type": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", - "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", - "dev": true - }, - "jest-haste-map": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-23.6.0.tgz", - "integrity": "sha512-uyNhMyl6dr6HaXGHp8VF7cK6KpC6G9z9LiMNsst+rJIZ8l7wY0tk8qwjPmEghczojZ2/ZhtEdIabZ0OQRJSGGg==", - "dev": true, - "requires": { - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.1.11", - "invariant": "^2.2.4", - "jest-docblock": "^23.2.0", - "jest-serializer": "^23.0.1", - "jest-worker": "^23.2.0", - "micromatch": "^2.3.11", - "sane": "^2.0.0" - } - }, - "jest-jasmine2": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-23.6.0.tgz", - "integrity": "sha512-pe2Ytgs1nyCs8IvsEJRiRTPC0eVYd8L/dXJGU08GFuBwZ4sYH/lmFDdOL3ZmvJR8QKqV9MFuwlsAi/EWkFUbsQ==", - "dev": true, - "requires": { - "babel-traverse": "^6.0.0", - "chalk": "^2.0.1", - "co": "^4.6.0", - "expect": "^23.6.0", - "is-generator-fn": "^1.0.0", - "jest-diff": "^23.6.0", - "jest-each": "^23.6.0", - "jest-matcher-utils": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-snapshot": "^23.6.0", - "jest-util": "^23.4.0", - "pretty-format": "^23.6.0" - } - }, - "jest-leak-detector": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-23.6.0.tgz", - "integrity": "sha512-f/8zA04rsl1Nzj10HIyEsXvYlMpMPcy0QkQilVZDFOaPbv2ur71X5u2+C4ZQJGyV/xvVXtCCZ3wQ99IgQxftCg==", - "dev": true, - "requires": { - "pretty-format": "^23.6.0" - } - }, - "jest-matcher-utils": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-23.6.0.tgz", - "integrity": "sha512-rosyCHQfBcol4NsckTn01cdelzWLU9Cq7aaigDf8VwwpIRvWE/9zLgX2bON+FkEW69/0UuYslUe22SOdEf2nog==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-get-type": "^22.1.0", - "pretty-format": "^23.6.0" - } - }, - "jest-message-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-23.4.0.tgz", - "integrity": "sha1-F2EMUJQjSVCNAaPR4L2iwHkIap8=", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0-beta.35", - "chalk": "^2.0.1", - "micromatch": "^2.3.11", - "slash": "^1.0.0", - "stack-utils": "^1.0.1" - } - }, - "jest-mock": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-23.2.0.tgz", - "integrity": "sha1-rRxg8p6HGdR8JuETgJi20YsmETQ=", - "dev": true - }, - "jest-regex-util": { - "version": "23.3.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-23.3.0.tgz", - "integrity": "sha1-X4ZylUfCeFxAAs6qj4Sf6MpHG8U=", - "dev": true - }, - "jest-resolve": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-23.6.0.tgz", - "integrity": "sha512-XyoRxNtO7YGpQDmtQCmZjum1MljDqUCob7XlZ6jy9gsMugHdN2hY4+Acz9Qvjz2mSsOnPSH7skBmDYCHXVZqkA==", - "dev": true, - "requires": { - "browser-resolve": "^1.11.3", - "chalk": "^2.0.1", - "realpath-native": "^1.0.0" - } - }, - "jest-resolve-dependencies": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-23.6.0.tgz", - "integrity": "sha512-EkQWkFWjGKwRtRyIwRwI6rtPAEyPWlUC2MpzHissYnzJeHcyCn1Hc8j7Nn1xUVrS5C6W5+ZL37XTem4D4pLZdA==", - "dev": true, - "requires": { - "jest-regex-util": "^23.3.0", - "jest-snapshot": "^23.6.0" - } - }, - "jest-runner": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-23.6.0.tgz", - "integrity": "sha512-kw0+uj710dzSJKU6ygri851CObtCD9cN8aNkg8jWJf4ewFyEa6kwmiH/r/M1Ec5IL/6VFa0wnAk6w+gzUtjJzA==", - "dev": true, - "requires": { - "exit": "^0.1.2", - "graceful-fs": "^4.1.11", - "jest-config": "^23.6.0", - "jest-docblock": "^23.2.0", - "jest-haste-map": "^23.6.0", - "jest-jasmine2": "^23.6.0", - "jest-leak-detector": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-runtime": "^23.6.0", - "jest-util": "^23.4.0", - "jest-worker": "^23.2.0", - "source-map-support": "^0.5.6", - "throat": "^4.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.10", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.10.tgz", - "integrity": "sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - } - } - }, - "jest-runtime": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-23.6.0.tgz", - "integrity": "sha512-ycnLTNPT2Gv+TRhnAYAQ0B3SryEXhhRj1kA6hBPSeZaNQkJ7GbZsxOLUkwg6YmvWGdX3BB3PYKFLDQCAE1zNOw==", - "dev": true, - "requires": { - "babel-core": "^6.0.0", - "babel-plugin-istanbul": "^4.1.6", - "chalk": "^2.0.1", - "convert-source-map": "^1.4.0", - "exit": "^0.1.2", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.1.11", - "jest-config": "^23.6.0", - "jest-haste-map": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-regex-util": "^23.3.0", - "jest-resolve": "^23.6.0", - "jest-snapshot": "^23.6.0", - "jest-util": "^23.4.0", - "jest-validate": "^23.6.0", - "micromatch": "^2.3.11", - "realpath-native": "^1.0.0", - "slash": "^1.0.0", - "strip-bom": "3.0.0", - "write-file-atomic": "^2.1.0", - "yargs": "^11.0.0" - }, - "dependencies": { - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true - } - } - }, - "jest-serializer": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-23.0.1.tgz", - "integrity": "sha1-o3dq6zEekP6D+rnlM+hRAr0WQWU=", - "dev": true - }, - "jest-snapshot": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-23.6.0.tgz", - "integrity": "sha512-tM7/Bprftun6Cvj2Awh/ikS7zV3pVwjRYU2qNYS51VZHgaAMBs5l4o/69AiDHhQrj5+LA2Lq4VIvK7zYk/bswg==", - "dev": true, - "requires": { - "babel-types": "^6.0.0", - "chalk": "^2.0.1", - "jest-diff": "^23.6.0", - "jest-matcher-utils": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-resolve": "^23.6.0", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "pretty-format": "^23.6.0", - "semver": "^5.5.0" - } - }, - "jest-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-23.4.0.tgz", - "integrity": "sha1-TQY8uSe68KI4Mf9hvsLLv0l5NWE=", - "dev": true, - "requires": { - "callsites": "^2.0.0", - "chalk": "^2.0.1", - "graceful-fs": "^4.1.11", - "is-ci": "^1.0.10", - "jest-message-util": "^23.4.0", - "mkdirp": "^0.5.1", - "slash": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "jest-validate": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-23.6.0.tgz", - "integrity": "sha512-OFKapYxe72yz7agrDAWi8v2WL8GIfVqcbKRCLbRG9PAxtzF9b1SEDdTpytNDN12z2fJynoBwpMpvj2R39plI2A==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-get-type": "^22.1.0", - "leven": "^2.1.0", - "pretty-format": "^23.6.0" - } - }, - "jest-watcher": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-23.4.0.tgz", - "integrity": "sha1-0uKM50+NrWxq/JIrksq+9u0FyRw=", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.1", - "string-length": "^2.0.0" - } - }, - "jest-worker": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-23.2.0.tgz", - "integrity": "sha1-+vcGqNo2+uYOsmlXJX+ntdjqArk=", - "dev": true, - "requires": { - "merge-stream": "^1.0.1" - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true - }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "jsdom": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", - "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", - "dev": true, - "requires": { - "abab": "^2.0.0", - "acorn": "^5.5.3", - "acorn-globals": "^4.1.0", - "array-equal": "^1.0.0", - "cssom": ">= 0.3.2 < 0.4.0", - "cssstyle": "^1.0.0", - "data-urls": "^1.0.0", - "domexception": "^1.0.1", - "escodegen": "^1.9.1", - "html-encoding-sniffer": "^1.0.2", - "left-pad": "^1.3.0", - "nwsapi": "^2.0.7", - "parse5": "4.0.0", - "pn": "^1.1.0", - "request": "^2.87.0", - "request-promise-native": "^1.0.5", - "sax": "^1.2.4", - "symbol-tree": "^3.2.2", - "tough-cookie": "^2.3.4", - "w3c-hr-time": "^1.0.1", - "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.3", - "whatwg-mimetype": "^2.1.0", - "whatwg-url": "^6.4.1", - "ws": "^5.2.0", - "xml-name-validator": "^3.0.0" - } - }, - "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", - "dev": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - }, - "kleur": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-2.0.2.tgz", - "integrity": "sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ==", - "dev": true - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "requires": { - "invert-kv": "^1.0.0" - } - }, - "left-pad": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", - "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", - "dev": true - }, - "leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", - "dev": true - }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "lru-cache": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", - "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, - "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" - } - }, - "make-error": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", - "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", - "dev": true - }, - "makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "dev": true, - "requires": { - "tmpl": "1.0.x" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "math-random": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", - "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", - "dev": true - }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "merge": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz", - "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", - "dev": true - }, - "merge-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", - "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", - "dev": true, - "requires": { - "readable-stream": "^2.0.1" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } - }, - "mime-db": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", - "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==", - "dev": true - }, - "mime-types": { - "version": "2.1.22", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", - "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", - "dev": true, - "requires": { - "mime-db": "~1.38.0" - } - }, - "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mixin-deep": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", - "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "nan": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.12.1.tgz", - "integrity": "sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw==", - "dev": true, - "optional": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "neo-async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", - "dev": true - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node-notifier": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.0.tgz", - "integrity": "sha512-SUDEb+o71XR5lXSTyivXd9J7fCloE3SyP4lSgt3lU2oSANiox+SxlNRGPjDKrwU1YN3ix2KN/VGGCg0t01rttQ==", - "dev": true, - "requires": { - "growly": "^1.3.0", - "is-wsl": "^1.1.0", - "semver": "^5.5.0", - "shellwords": "^0.1.1", - "which": "^1.3.0" - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "nwsapi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.0.tgz", - "integrity": "sha512-ZG3bLAvdHmhIjaQ/Db1qvBxsGvFMLIRpQszyqbg31VJ53UP++uZX1/gf3Ut96pdwN9AuDwlMqIYLm0UPCdUeHg==", - "dev": true - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "object-keys": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.0.tgz", - "integrity": "sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg==", - "dev": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "object.getownpropertydescriptors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", - "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" - } - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true, - "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "^3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" - }, - "dependencies": { - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - } - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", - "dev": true, - "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true - }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true, - "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "parse5": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", - "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - }, - "pn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", - "dev": true - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - } - } - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "dev": true - }, - "prompts": { - "version": "0.1.14", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-0.1.14.tgz", - "integrity": "sha512-rxkyiE9YH6zAz/rZpywySLKkpaj0NMVyNw1qhsubdbjjSgcayjTShDreZGlFMcGSu5sab3bAKPfFk78PB90+8w==", - "dev": true, - "requires": { - "kleur": "^2.0.1", - "sisteransi": "^0.1.1" - } - }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true - }, - "psl": { - "version": "1.1.31", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz", - "integrity": "sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw==", - "dev": true - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true - }, - "randomatic": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", - "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", - "dev": true, - "requires": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - } - } - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "realpath-native": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", - "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", - "dev": true, - "requires": { - "util.promisify": "^1.0.0" - } - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "dev": true, - "requires": { - "is-equal-shallow": "^0.1.3" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } - }, - "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.0", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", - "dev": true, - "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - } - } - } - }, - "request-promise-core": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", - "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", - "dev": true, - "requires": { - "lodash": "^4.17.11" - } - }, - "request-promise-native": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", - "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", - "dev": true, - "requires": { - "request-promise-core": "1.1.2", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "resolve": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", - "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", - "dev": true, - "requires": { - "resolve-from": "^3.0.0" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rsvp": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz", - "integrity": "sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "sane": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/sane/-/sane-2.5.2.tgz", - "integrity": "sha1-tNwYYcIbQn6SlQej51HiosuKs/o=", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "capture-exit": "^1.2.0", - "exec-sh": "^0.2.0", - "fb-watchman": "^2.0.0", - "fsevents": "^1.2.3", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5", - "watch": "~0.18.0" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, - "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", - "dev": true - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true - }, - "shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true - }, - "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true - }, - "sisteransi": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-0.1.1.tgz", - "integrity": "sha512-PmGOd02bM9YO5ifxpw36nrNMBTptEtfRl4qUYl9SndkolplkrZZOW7PGHjrZL53QvMVj9nQ+TKqUnRsw4tJa4g==", - "dev": true - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - }, - "source-map-resolve": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", - "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", - "dev": true, - "requires": { - "atob": "^2.1.1", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "^0.5.6" - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, - "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.3.tgz", - "integrity": "sha512-uBIcIl3Ih6Phe3XHK1NqboJLdGfwr1UN3k6wSD1dZpmPsIkb8AGNbZYJ1fOBk834+Gxy8rpfDxrS6XLEMZMY2g==", - "dev": true - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "stack-utils": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz", - "integrity": "sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==", - "dev": true - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", - "dev": true - }, - "string-length": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", - "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=", - "dev": true, - "requires": { - "astral-regex": "^1.0.0", - "strip-ansi": "^4.0.0" - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - } - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "symbol-tree": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", - "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", - "dev": true - }, - "test-exclude": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.3.tgz", - "integrity": "sha512-SYbXgY64PT+4GAL2ocI3HwPa4Q4TBKm0cwAVeKOt/Aoc0gSpNRjJX8w0pA1LMKZ3LBmd8pYBqApFNQLII9kavA==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "micromatch": "^2.3.11", - "object-assign": "^4.1.0", - "read-pkg-up": "^1.0.1", - "require-main-filename": "^1.0.1" - } - }, - "throat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", - "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=", - "dev": true - }, - "tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", - "dev": true - }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - } - } - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "dev": true - }, - "ts-jest": { - "version": "23.10.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-23.10.5.tgz", - "integrity": "sha512-MRCs9qnGoyKgFc8adDEntAOP64fWK1vZKnOYU1o2HxaqjdJvGqmkLCPCnVq1/If4zkUmEjKPnCiUisTrlX2p2A==", - "dev": true, - "requires": { - "bs-logger": "0.x", - "buffer-from": "1.x", - "fast-json-stable-stringify": "2.x", - "json5": "2.x", - "make-error": "1.x", - "mkdirp": "0.x", - "resolve": "1.x", - "semver": "^5.5", - "yargs-parser": "10.x" - }, - "dependencies": { - "json5": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", - "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - } - } - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", - "dev": true - }, - "uglify-js": { - "version": "3.4.9", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", - "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", - "dev": true, - "optional": true, - "requires": { - "commander": "~2.17.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } - } - }, - "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" - } - } - } - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "uri-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", - "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "util.promisify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", - "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "object.getownpropertydescriptors": "^2.0.3" - } - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "w3c-hr-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", - "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", - "dev": true, - "requires": { - "browser-process-hrtime": "^0.1.2" - } - }, - "walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "dev": true, - "requires": { - "makeerror": "1.0.x" - } - }, - "watch": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/watch/-/watch-0.18.0.tgz", - "integrity": "sha1-KAlUdsbffJDJYxOJkMClQj60uYY=", - "dev": true, - "requires": { - "exec-sh": "^0.2.0", - "minimist": "^1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - } - } - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "whatwg-url": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", - "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "write-file-atomic": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.2.tgz", - "integrity": "sha512-s0b6vB3xIVRLWywa6X9TOMA7k9zio0TMOsl9ZnDkliA/cfJlpHXAscj0gbHVJiTdIuAYpIyqS5GW91fqm6gG5g==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "ws": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", - "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, - "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true - }, - "yargs": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz", - "integrity": "sha512-NwW69J42EsCSanF8kyn5upxvjp5ds+t3+udGBeTbFnERA+lF541DDpMawzo4z6W/QrzNM18D+BPMiOBibnFV5A==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^9.0.2" - } - }, - "yargs-parser": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", - "integrity": "sha1-nM9qQ0YP5O1Aqbto9I1DuKaMwHc=", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - } - } - } -} diff --git a/packages/logging/package.json b/packages/logging/package.json deleted file mode 100644 index 2a2ec99a5..000000000 --- a/packages/logging/package.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "name": "@optimizely/js-sdk-logging", - "version": "0.3.1", - "description": "Optimizely Full Stack Core Logging", - "author": "jordangarcia <jordan@optimizely.com>", - "homepage": "https://github.com/optimizely/javascript-sdk/tree/master/packages/logging", - "license": "Apache-2.0", - "main": "lib/index.js", - "types": "lib/index.d.ts", - "directories": { - "lib": "lib", - "test": "test" - }, - "files": [ - "lib" - ], - "scripts": { - "clean": "rm -rf lib", - "prebuild": "npm run clean", - "build": "tsc", - "test": "jest", - "prepare": "npm run build", - "prepublishOnly": "npm test" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/optimizely/javascript-sdk.git", - "directory": "packages/logging" - }, - "keywords": [ - "optimizely" - ], - "bugs": { - "url": "https://github.com/optimizely/javascript-sdk/issues" - }, - "publishConfig": { - "access": "public" - }, - "dependencies": { - "@optimizely/js-sdk-utils": "^0.4.0" - }, - "devDependencies": { - "@types/jest": "^23.3.12", - "jest": "^23.6.0", - "ts-jest": "^23.10.5", - "typescript": "^4.7.4" - } -} diff --git a/packages/logging/src/errorHandler.ts b/packages/logging/src/errorHandler.ts deleted file mode 100644 index bb659aeae..000000000 --- a/packages/logging/src/errorHandler.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * @export - * @interface ErrorHandler - */ -export interface ErrorHandler { - /** - * @param {Error} exception - * @memberof ErrorHandler - */ - handleError(exception: Error): void -} - -/** - * @export - * @class NoopErrorHandler - * @implements {ErrorHandler} - */ -export class NoopErrorHandler implements ErrorHandler { - /** - * @param {Error} exception - * @memberof NoopErrorHandler - */ - handleError(exception: Error): void { - // no-op - return - } -} - -let globalErrorHandler: ErrorHandler = new NoopErrorHandler() - -/** - * @export - * @param {ErrorHandler} handler - */ -export function setErrorHandler(handler: ErrorHandler): void { - globalErrorHandler = handler -} - -/** - * @export - * @returns {ErrorHandler} - */ -export function getErrorHandler(): ErrorHandler { - return globalErrorHandler -} - -/** - * @export - */ -export function resetErrorHandler(): void { - globalErrorHandler = new NoopErrorHandler() -} diff --git a/packages/logging/src/index.ts b/packages/logging/src/index.ts deleted file mode 100644 index 47a1e99c8..000000000 --- a/packages/logging/src/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export * from './errorHandler' -export * from './models' -export * from './logger' diff --git a/packages/logging/src/logger.ts b/packages/logging/src/logger.ts deleted file mode 100644 index ff9abd920..000000000 --- a/packages/logging/src/logger.ts +++ /dev/null @@ -1,328 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { getErrorHandler } from './errorHandler' -import { isValidEnum, sprintf } from '@optimizely/js-sdk-utils' - -import { LogLevel, LoggerFacade, LogManager, LogHandler } from './models' - -type StringToLogLevel = { - NOTSET: number, - DEBUG: number, - INFO: number, - WARNING: number, - ERROR: number, -} - -const stringToLogLevel: StringToLogLevel = { - NOTSET: 0, - DEBUG: 1, - INFO: 2, - WARNING: 3, - ERROR: 4, -} - -function coerceLogLevel(level: any): LogLevel { - if (typeof level !== 'string') { - return level - } - - level = level.toUpperCase() - if (level === 'WARN') { - level = 'WARNING' - } - - if (!stringToLogLevel[level as keyof StringToLogLevel]) { - return level - } - - return stringToLogLevel[level as keyof StringToLogLevel] -} - -type LogData = { - message: string - splat: any[] - error?: Error -} - -class DefaultLogManager implements LogManager { - private loggers: { - [name: string]: LoggerFacade - } - private defaultLoggerFacade = new OptimizelyLogger() - - constructor() { - this.loggers = {} - } - - getLogger(name?: string): LoggerFacade { - if (!name) { - return this.defaultLoggerFacade - } - - if (!this.loggers[name]) { - this.loggers[name] = new OptimizelyLogger({ messagePrefix: name }) - } - - return this.loggers[name] - } -} - -type ConsoleLogHandlerConfig = { - logLevel?: LogLevel | string - logToConsole?: boolean - prefix?: string -} - -export class ConsoleLogHandler implements LogHandler { - public logLevel: LogLevel - private logToConsole: boolean - private prefix: string - - /** - * Creates an instance of ConsoleLogger. - * @param {ConsoleLogHandlerConfig} config - * @memberof ConsoleLogger - */ - constructor(config: ConsoleLogHandlerConfig = {}) { - this.logLevel = LogLevel.NOTSET - if (config.logLevel !== undefined && isValidEnum(LogLevel, config.logLevel)) { - this.setLogLevel(config.logLevel) - } - - this.logToConsole = config.logToConsole !== undefined ? !!config.logToConsole : true - this.prefix = config.prefix !== undefined ? config.prefix : '[OPTIMIZELY]' - } - - /** - * @param {LogLevel} level - * @param {string} message - * @memberof ConsoleLogger - */ - log(level: LogLevel, message: string) { - if (!this.shouldLog(level) || !this.logToConsole) { - return - } - - let logMessage: string = `${this.prefix} - ${this.getLogLevelName( - level, - )} ${this.getTime()} ${message}` - - this.consoleLog(level, [logMessage]) - } - - /** - * @param {LogLevel} level - * @memberof ConsoleLogger - */ - setLogLevel(level: LogLevel | string) { - level = coerceLogLevel(level) - if (!isValidEnum(LogLevel, level) || level === undefined) { - this.logLevel = LogLevel.ERROR - } else { - this.logLevel = level - } - } - - /** - * @returns {string} - * @memberof ConsoleLogger - */ - getTime(): string { - return new Date().toISOString() - } - - /** - * @private - * @param {LogLevel} targetLogLevel - * @returns {boolean} - * @memberof ConsoleLogger - */ - private shouldLog(targetLogLevel: LogLevel): boolean { - return targetLogLevel >= this.logLevel - } - - /** - * @private - * @param {LogLevel} logLevel - * @returns {string} - * @memberof ConsoleLogger - */ - private getLogLevelName(logLevel: LogLevel): string { - switch (logLevel) { - case LogLevel.DEBUG: - return 'DEBUG' - case LogLevel.INFO: - return 'INFO ' - case LogLevel.WARNING: - return 'WARN ' - case LogLevel.ERROR: - return 'ERROR' - default: - return 'NOTSET' - } - } - - /** - * @private - * @param {LogLevel} logLevel - * @param {string[]} logArguments - * @memberof ConsoleLogger - */ - private consoleLog(logLevel: LogLevel, logArguments: [string, ...string[]]) { - switch (logLevel) { - case LogLevel.DEBUG: - console.log.apply(console, logArguments) - break - case LogLevel.INFO: - console.info.apply(console, logArguments) - break - case LogLevel.WARNING: - console.warn.apply(console, logArguments) - break - case LogLevel.ERROR: - console.error.apply(console, logArguments) - break - default: - console.log.apply(console, logArguments) - } - } -} - -let globalLogLevel: LogLevel = LogLevel.NOTSET -let globalLogHandler: LogHandler | null = null - -class OptimizelyLogger implements LoggerFacade { - private messagePrefix: string = '' - - constructor(opts: { messagePrefix?: string } = {}) { - if (opts.messagePrefix) { - this.messagePrefix = opts.messagePrefix - } - } - - /** - * @param {(LogLevel | LogInputObject)} levelOrObj - * @param {string} [message] - * @memberof OptimizelyLogger - */ - log(level: LogLevel | string, message: string, ...splat: any[]): void { - this.internalLog(coerceLogLevel(level), { - message, - splat, - }) - } - - info(message: string | Error, ...splat: any[]): void { - this.namedLog(LogLevel.INFO, message, splat) - } - - debug(message: string | Error, ...splat: any[]): void { - this.namedLog(LogLevel.DEBUG, message, splat) - } - - warn(message: string | Error, ...splat: any[]): void { - this.namedLog(LogLevel.WARNING, message, splat) - } - - error(message: string | Error, ...splat: any[]): void { - this.namedLog(LogLevel.ERROR, message, splat) - } - - private format(data: LogData): string { - return `${this.messagePrefix ? this.messagePrefix + ': ' : ''}${sprintf( - data.message, - ...data.splat, - )}` - } - - private internalLog(level: LogLevel, data: LogData): void { - if (!globalLogHandler) { - return - } - - if (level < globalLogLevel) { - return - } - - globalLogHandler.log(level, this.format(data)) - - if (data.error && data.error instanceof Error) { - getErrorHandler().handleError(data.error) - } - } - - private namedLog(level: LogLevel, message: string | Error, splat: any[]): void { - let error: Error | undefined - - if (message instanceof Error) { - error = message - message = error.message - this.internalLog(level, { - error, - message, - splat, - }) - return - } - - if (splat.length === 0) { - this.internalLog(level, { - message, - splat, - }) - return - } - - const last = splat[splat.length - 1] - if (last instanceof Error) { - error = last - splat.splice(-1) - } - - this.internalLog(level, { message, error, splat }) - } -} - -let globalLogManager: LogManager = new DefaultLogManager() - -export function getLogger(name?: string): LoggerFacade { - return globalLogManager.getLogger(name) -} - -export function setLogHandler(logger: LogHandler | null) { - globalLogHandler = logger -} - -export function setLogLevel(level: LogLevel | string) { - level = coerceLogLevel(level) - if (!isValidEnum(LogLevel, level) || level === undefined) { - globalLogLevel = LogLevel.ERROR - } else { - globalLogLevel = level - } -} - -export function getLogLevel(): LogLevel { - return globalLogLevel -} - -/** - * Resets all global logger state to it's original - */ -export function resetLogger() { - globalLogManager = new DefaultLogManager() - globalLogLevel = LogLevel.NOTSET -} diff --git a/packages/logging/src/models.ts b/packages/logging/src/models.ts deleted file mode 100644 index cd3223932..000000000 --- a/packages/logging/src/models.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export enum LogLevel { - NOTSET = 0, - DEBUG = 1, - INFO = 2, - WARNING = 3, - ERROR = 4, -} - -export interface LoggerFacade { - log(level: LogLevel | string, message: string, ...splat: any[]): void - - info(message: string | Error, ...splat: any[]): void - - debug(message: string | Error, ...splat: any[]): void - - warn(message: string | Error, ...splat: any[]): void - - error(message: string | Error, ...splat: any[]): void -} - -export interface LogManager { - getLogger(name?: string): LoggerFacade -} - -export interface LogHandler { - log(level: LogLevel, message: string, ...splat: any[]): void -} diff --git a/packages/logging/tsconfig.json b/packages/logging/tsconfig.json deleted file mode 100644 index 2ba8d7164..000000000 --- a/packages/logging/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./lib" - }, - "include": [ - "./src", - ], - "exclude": [ - "./lib", - "./src/**/*.spec.ts" - ] -} \ No newline at end of file diff --git a/packages/optimizely-sdk/.prettierrc b/packages/optimizely-sdk/.prettierrc deleted file mode 100644 index 62301e2e3..000000000 --- a/packages/optimizely-sdk/.prettierrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "printWidth": 120, - "tabWidth": 2, - "useTabs": false, - "semi": true, - "singleQuote": true, - "trailingComma": "es5", - "bracketSpacing": true, - "jsxBracketSameLine": false -} diff --git a/packages/optimizely-sdk/README.md b/packages/optimizely-sdk/README.md deleted file mode 100644 index d312b97a9..000000000 --- a/packages/optimizely-sdk/README.md +++ /dev/null @@ -1,120 +0,0 @@ -# JavaScript SDK for Optimizely X Full Stack -[![npm](https://img.shields.io/npm/v/%40optimizely%2Foptimizely-sdk.svg)](https://www.npmjs.com/package/@optimizely/optimizely-sdk) -[![npm](https://img.shields.io/npm/dm/%40optimizely%2Foptimizely-sdk.svg)](https://www.npmjs.com/package/@optimizely/optimizely-sdk) -[![Travis CI](https://img.shields.io/travis/optimizely/javascript-sdk.svg)](https://travis-ci.org/optimizely/javascript-sdk) -[![Coveralls](https://img.shields.io/coveralls/optimizely/javascript-sdk.svg)](https://coveralls.io/github/optimizely/javascript-sdk) -[![license](https://img.shields.io/github/license/optimizely/javascript-sdk.svg)](https://choosealicense.com/licenses/apache-2.0/) - - -Optimizely X Full Stack is A/B testing and feature management for product development teams. Experiment in any application. Make every feature on your roadmap an opportunity to learn. Learn more at the [landing page](https://www.optimizely.com/products/full-stack/), or see the [documentation](https://docs.developers.optimizely.com/full-stack/docs). - -This directory contains the source code for the JavaScript SDK, which is usable in Node.js, browsers, and beyond. - -## Getting Started - -### Prerequisites - -Ensure the SDK supports all of the platforms you're targeting. In particular, the SDK targets any ES5-compliant JavaScript environment. We officially support: -- Node.js >= 8.0.0. By extension, environments like AWS Lambda, Google Cloud Functions, and Auth0 Webtasks are supported as well. Older Node.js releases likely work too (try `npm test` to validate for yourself), but are not formally supported. -- [Web browsers](https://caniuse.com/#feat=es5) - -Other environments likely are compatible, too, but note that we don't officially support them: -- Progressive Web Apps, WebViews, and hybrid mobile apps like those built with React Native and Apache Cordova. -- [Cloudflare Workers](https://developers.cloudflare.com/workers/) and [Fly](https://fly.io/), both of which are powered by recent releases of V8. -- Anywhere else you can think of that might embed a JavaScript engine. The sky is the limit; experiment everywhere! 🚀 - -Once you've validated that the SDK supports the platforms you're targeting, fetch the package from [NPM](https://www.npmjs.com/package/@optimizely/optimizely-sdk). Using `npm`: - -``` -npm install --save @optimizely/optimizely-sdk -``` - -### Usage -See the Optimizely X Full Stack [developer documentation](http://developers.optimizely.com/server/reference/index.html) to learn how to set up your first JavaScript project and use the SDK. - -The package's entry point is a CommonJS module, which can be used directly in environments which support it (e.g., Node.js, or loaded in a browser via Browserify or RequireJS). Additionally, for ease of use during initial evaluations you can include a standalone bundle of the SDK in your web page by fetching it from [unpkg](https://unpkg.com/): - -```html -<script src="https://unpkg.com/@optimizely/optimizely-sdk/dist/optimizely.browser.umd.min.js"></script> - -<!-- You can also use the unminified version if necessary --> -<script src="https://unpkg.com/@optimizely/optimizely-sdk/dist/optimizely.browser.umd.js"></script> -``` - -When evaluated, that bundle assigns the SDK's exports to `window.optimizelySdk`. If you wish to use the asset locally (for example, if unpkg is down), you can find it in your local copy of the package at dist/optimizely.browser.umd.min.js. We do not recommend using this method in production settings as it introduces a third-party performance dependency. - -Regarding `EventDispatcher`s: In Node.js and browser environments, the default `EventDispatcher` is powered by the [`http/s`](https://nodejs.org/api/http.html) modules and by [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest#Browser_compatibility), respectively. In all other environments, you must supply your own `EventDispatcher`. - -### Migrating from 1.x.x - -This version represents a major version change and, as such, introduces some breaking changes: - -- The Node.js SDK is now combined with the JavaScript SDK. We now have just one package, `@optimizely/optimizely-sdk`, that works in many JavaScript environments. - -- We no longer support Node.js < 4.0.0, which collectively [reached end-of-life](https://github.com/nodejs/Release#end-of-life-releases) on 2016-12-31. - -- You will no longer be able to pass in `revenue` value as a stand-alone argument to the `track` call. Instead you will need to pass it as an entry in the [`eventTags`](https://developers.optimizely.com/x/solutions/sdks/reference/index.html?language=javascript#event-tags). - -### Feature Management access - -To access Feature Management in the Optimizely web application, please contact your Optimizely account executive. - -## Contributing -This information is relevant only if you plan on contributing to the SDK itself. - -```sh -# Prerequisite: Install dependencies. -npm install - -# Run unit tests with mocha. -npm test - -# Run unit tests in many browsers, currently via BrowserStack. -# For this to work, the following environment variables must be set: -# - BROWSER_STACK_USERNAME -# - BROWSER_STACK_PASSWORD -npm run test-xbrowser -``` - -[.travis.yml](/.travis.yml) contains the definitions for `BROWSER_STACK_USERNAME` and `BROWSER_STACK_ACCESS_KEY` used in CI. These values are Optimizely's BrowserStack credentials, encrypted with our Travis CI public key. These creds can be rotated by following [these docs](https://docs.travis-ci.com/user/environment-variables/#Defining-encrypted-variables-in-.travis.yml). - -## Credits - -First-party code (under lib/) is copyright Optimizely, Inc. and contributors, licensed under Apache 2.0. - -## Additional Code - -Prod dependencies are as follows: - -```json -{ - "json-schema@0.2.3": { - "licenses": [ - "AFLv2.1", - "BSD" - ], - "publisher": "Kris Zyp", - "repository": "https://github.com/kriszyp/json-schema" - }, - "murmurhash@0.0.2": { - "licenses": "MIT*", - "repository": "https://github.com/perezd/node-murmurhash" - }, - "uuid@3.3.2": { - "licenses": "MIT", - "repository": "https://github.com/kelektiv/node-uuid" - }, - "decompress-response@4.2.1": { - "licenses": "MIT", - "repository": "https://github.com/sindresorhus/decompress-response" - } -} -``` - -To regenerate this, run the following command: - -```sh -npx license-checker --production --json | jq 'map_values({ licenses, publisher, repository }) | del(.[][] | nulls)' -``` - -and remove the self (`@optimizely/optimizely-sdk`) entry. diff --git a/packages/optimizely-sdk/__mocks__/@react-native-async-storage/async-storage.ts b/packages/optimizely-sdk/__mocks__/@react-native-async-storage/async-storage.ts deleted file mode 100644 index 2cbc7fd9a..000000000 --- a/packages/optimizely-sdk/__mocks__/@react-native-async-storage/async-storage.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - - let items: {[key: string]: string} = {} -export default class AsyncStorage { - static getItem(key: string, callback?: (error?: Error, result?: string) => void): Promise<string | null> { - return new Promise((resolve, reject) => { - switch (key) { - case 'keyThatExists': - resolve('{ "name": "Awesome Object" }') - break - case 'keyThatDoesNotExist': - resolve(null) - break - case 'keyWithInvalidJsonObject': - resolve('bad json }') - break - default: - setTimeout(() => resolve(items[key] || null), 1) - } - }) - } - - static setItem(key: string, value: string, callback?: (error?: Error) => void): Promise<void> { - return new Promise((resolve) => { - setTimeout(() => { - items[key] = value - resolve() - }, 1) - }) - } - - static removeItem(key: string, callback?: (error?: Error, result?: string) => void): Promise<string | null> { - return new Promise(resolve => { - setTimeout(() => { - items[key] && delete items[key] - // @ts-ignore - resolve() - }, 1) - }) - } - - static dumpItems(): {[key: string]: string} { - return items - } - - static clearStore(): void { - items = {} - } -} diff --git a/packages/optimizely-sdk/jest.config.js b/packages/optimizely-sdk/jest.config.js deleted file mode 100644 index 64694672c..000000000 --- a/packages/optimizely-sdk/jest.config.js +++ /dev/null @@ -1,26 +0,0 @@ -module.exports = { - "transform": { - "^.+\\.(ts|tsx|js|jsx)$": "ts-jest", - }, - "testRegex": "(/tests/.*|(\\.|/)(test|spec))\\.tsx?$", - moduleNameMapper: { - // Force module uuid to resolve with the CJS entry point, because Jest does not support package.json.exports. See https://github.com/uuidjs/uuid/issues/451 - "uuid": require.resolve('uuid'), - }, - "testPathIgnorePatterns" : [ - "tests/testUtils.ts" - ], - "moduleFileExtensions": [ - "ts", - "tsx", - "js", - "jsx", - "json", - "node" - ], - "resetMocks": false, - "setupFiles": [ - "jest-localstorage-mock", - ], - testEnvironment: "jsdom" -} diff --git a/packages/optimizely-sdk/lib/core/decision_service/index.ts b/packages/optimizely-sdk/lib/core/decision_service/index.ts deleted file mode 100644 index 2c126c04a..000000000 --- a/packages/optimizely-sdk/lib/core/decision_service/index.ts +++ /dev/null @@ -1,1263 +0,0 @@ -/**************************************************************************** - * Copyright 2017-2022 Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ -import { LogHandler } from '../../modules/logging'; -import { sprintf } from '../../utils/fns'; - -import fns from '../../utils/fns'; -import { bucket } from '../bucketer'; -import { - AUDIENCE_EVALUATION_TYPES, - CONTROL_ATTRIBUTES, - DECISION_SOURCES, - ERROR_MESSAGES, - LOG_LEVEL, - LOG_MESSAGES, -} from '../../utils/enums'; -import { - getAudiencesById, - getExperimentAudienceConditions, - getExperimentFromId, - getExperimentFromKey, - getFlagVariationByKey, - getTrafficAllocation, - getVariationIdFromExperimentAndVariationKey, - getVariationFromId, - getVariationKeyFromId, - isActive, - ProjectConfig, -} from '../project_config'; -import { AudienceEvaluator, createAudienceEvaluator } from '../audience_evaluator'; -import * as stringValidator from '../../utils/string_value_validator'; -import { - BucketerParams, - DecisionResponse, - Experiment, - ExperimentBucketMap, - FeatureFlag, - OptimizelyDecideOption, - OptimizelyUserContext, - UserAttributes, - UserProfile, - UserProfileService, - Variation, -} from '../../shared_types'; - -const MODULE_NAME = 'DECISION_SERVICE'; - -export interface DecisionObj { - experiment: Experiment | null; - variation: Variation | null; - decisionSource: string; -} - -interface DecisionServiceOptions { - userProfileService: UserProfileService | null; - logger: LogHandler; - UNSTABLE_conditionEvaluators: unknown; -} - -interface DeliveryRuleResponse<T, K> extends DecisionResponse<T> { - skipToEveryoneElse: K; -} - -/** - * Optimizely's decision service that determines which variation of an experiment the user will be allocated to. - * - * The decision service contains all logic around how a user decision is made. This includes all of the following (in order): - * 1. Checking experiment status - * 2. Checking forced bucketing - * 3. Checking whitelisting - * 4. Checking user profile service for past bucketing decisions (sticky bucketing) - * 5. Checking audience targeting - * 6. Using Murmurhash3 to bucket the user. - * - * @constructor - * @param {DecisionServiceOptions} options - * @returns {DecisionService} - */ -export class DecisionService { - private logger: LogHandler; - private audienceEvaluator: AudienceEvaluator; - private forcedVariationMap: { [key: string]: { [id: string]: string } }; - private userProfileService: UserProfileService | null; - - constructor(options: DecisionServiceOptions) { - this.audienceEvaluator = createAudienceEvaluator(options.UNSTABLE_conditionEvaluators); - this.forcedVariationMap = {}; - this.logger = options.logger; - this.userProfileService = options.userProfileService || null; - } - - /** - * Gets variation where visitor will be bucketed. - * @param {ProjectConfig} configObj The parsed project configuration object - * @param {Experiment} experiment - * @param {OptimizelyUserContext} user A user context - * @param {[key: string]: boolean} options Optional map of decide options - * @return {DecisionResponse<string|null>} DecisionResponse containing the variation the user is bucketed into - * and the decide reasons. - */ - getVariation( - configObj: ProjectConfig, - experiment: Experiment, - user: OptimizelyUserContext, - options: { [key: string]: boolean } = {} - ): DecisionResponse<string | null> { - const userId = user.getUserId(); - const attributes = user.getAttributes(); - // by default, the bucketing ID should be the user ID - const bucketingId = this.getBucketingId(userId, attributes); - const decideReasons: (string | number)[][] = []; - const experimentKey = experiment.key; - if (!this.checkIfExperimentIsActive(configObj, experimentKey)) { - this.logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.EXPERIMENT_NOT_RUNNING, MODULE_NAME, experimentKey); - decideReasons.push([LOG_MESSAGES.EXPERIMENT_NOT_RUNNING, MODULE_NAME, experimentKey]); - return { - result: null, - reasons: decideReasons, - }; - } - const decisionForcedVariation = this.getForcedVariation(configObj, experimentKey, userId); - decideReasons.push(...decisionForcedVariation.reasons); - const forcedVariationKey = decisionForcedVariation.result; - - if (forcedVariationKey) { - return { - result: forcedVariationKey, - reasons: decideReasons, - }; - } - const decisionWhitelistedVariation = this.getWhitelistedVariation(experiment, userId); - decideReasons.push(...decisionWhitelistedVariation.reasons); - let variation = decisionWhitelistedVariation.result; - if (variation) { - return { - result: variation.key, - reasons: decideReasons, - }; - } - - const shouldIgnoreUPS = options[OptimizelyDecideOption.IGNORE_USER_PROFILE_SERVICE]; - const experimentBucketMap = this.resolveExperimentBucketMap(userId, attributes); - - // check for sticky bucketing if decide options do not include shouldIgnoreUPS - if (!shouldIgnoreUPS) { - variation = this.getStoredVariation(configObj, experiment, userId, experimentBucketMap); - if (variation) { - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.RETURNING_STORED_VARIATION, - MODULE_NAME, - variation.key, - experimentKey, - userId, - ); - decideReasons.push([ - LOG_MESSAGES.RETURNING_STORED_VARIATION, - MODULE_NAME, - variation.key, - experimentKey, - userId, - ]); - return { - result: variation.key, - reasons: decideReasons, - }; - } - } - - // Perform regular targeting and bucketing - const decisionifUserIsInAudience = this.checkIfUserIsInAudience( - configObj, - experiment, - AUDIENCE_EVALUATION_TYPES.EXPERIMENT, - user, - '' - ); - decideReasons.push(...decisionifUserIsInAudience.reasons); - if (!decisionifUserIsInAudience.result) { - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.USER_NOT_IN_EXPERIMENT, - MODULE_NAME, - userId, - experimentKey, - ); - decideReasons.push([ - LOG_MESSAGES.USER_NOT_IN_EXPERIMENT, - MODULE_NAME, - userId, - experimentKey, - ]); - return { - result: null, - reasons: decideReasons, - }; - } - - const bucketerParams = this.buildBucketerParams(configObj, experiment, bucketingId, userId); - const decisionVariation = bucket(bucketerParams); - decideReasons.push(...decisionVariation.reasons); - const variationId = decisionVariation.result; - if (variationId) { - variation = configObj.variationIdMap[variationId]; - } - if (!variation) { - this.logger.log( - LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_NO_VARIATION, - MODULE_NAME, - userId, - experimentKey, - ); - decideReasons.push([ - LOG_MESSAGES.USER_HAS_NO_VARIATION, - MODULE_NAME, - userId, - experimentKey, - ]); - return { - result: null, - reasons: decideReasons, - }; - } - - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.USER_HAS_VARIATION, - MODULE_NAME, - userId, - variation.key, - experimentKey, - ); - decideReasons.push([ - LOG_MESSAGES.USER_HAS_VARIATION, - MODULE_NAME, - userId, - variation.key, - experimentKey, - ]); - // persist bucketing if decide options do not include shouldIgnoreUPS - if (!shouldIgnoreUPS) { - this.saveUserProfile(experiment, variation, userId, experimentBucketMap); - } - - return { - result: variation.key, - reasons: decideReasons, - }; - } - - /** - * Merges attributes from attributes[STICKY_BUCKETING_KEY] and userProfileService - * @param {string} userId - * @param {UserAttributes} attributes - * @return {ExperimentBucketMap} finalized copy of experiment_bucket_map - */ - private resolveExperimentBucketMap( - userId: string, - attributes?: UserAttributes - ): ExperimentBucketMap { - attributes = attributes || {}; - - const userProfile = this.getUserProfile(userId) || {} as UserProfile; - const attributeExperimentBucketMap = attributes[CONTROL_ATTRIBUTES.STICKY_BUCKETING_KEY]; - return fns.assign({}, userProfile.experiment_bucket_map, attributeExperimentBucketMap); - } - - /** - * Checks whether the experiment is running - * @param {ProjectConfig} configObj The parsed project configuration object - * @param {string} experimentKey Key of experiment being validated - * @return {boolean} True if experiment is running - */ - private checkIfExperimentIsActive(configObj: ProjectConfig, experimentKey: string): boolean { - return isActive(configObj, experimentKey); - } - - /** - * Checks if user is whitelisted into any variation and return that variation if so - * @param {Experiment} experiment - * @param {string} userId - * @return {DecisionResponse<Variation|null>} DecisionResponse containing the forced variation if it exists - * or user ID and the decide reasons. - */ - private getWhitelistedVariation( - experiment: Experiment, - userId: string - ): DecisionResponse<Variation | null> { - const decideReasons: (string | number)[][] = []; - if (experiment.forcedVariations && experiment.forcedVariations.hasOwnProperty(userId)) { - const forcedVariationKey = experiment.forcedVariations[userId]; - if (experiment.variationKeyMap.hasOwnProperty(forcedVariationKey)) { - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.USER_FORCED_IN_VARIATION, - MODULE_NAME, - userId, - forcedVariationKey, - ); - decideReasons.push([ - LOG_MESSAGES.USER_FORCED_IN_VARIATION, - MODULE_NAME, - userId, - forcedVariationKey, - ]); - return { - result: experiment.variationKeyMap[forcedVariationKey], - reasons: decideReasons, - }; - } else { - this.logger.log( - LOG_LEVEL.ERROR, - LOG_MESSAGES.FORCED_BUCKETING_FAILED, - MODULE_NAME, - forcedVariationKey, - userId, - ); - decideReasons.push([ - LOG_MESSAGES.FORCED_BUCKETING_FAILED, - MODULE_NAME, - forcedVariationKey, - userId, - ]); - return { - result: null, - reasons: decideReasons, - }; - } - } - - return { - result: null, - reasons: decideReasons, - }; - } - - /** - * Checks whether the user is included in experiment audience - * @param {ProjectConfig} configObj The parsed project configuration object - * @param {string} experimentKey Key of experiment being validated - * @param {string} evaluationAttribute String representing experiment key or rule - * @param {string} userId ID of user - * @param {UserAttributes} attributes Optional parameter for user's attributes - * @param {string} loggingKey String representing experiment key or rollout rule. To be used in log messages only. - * @return {DecisionResponse<boolean>} DecisionResponse DecisionResponse containing result true if user meets audience conditions and - * the decide reasons. - */ - private checkIfUserIsInAudience( - configObj: ProjectConfig, - experiment: Experiment, - evaluationAttribute: string, - user: OptimizelyUserContext, - loggingKey?: string | number, - ): DecisionResponse<boolean> { - const decideReasons: (string | number)[][] = []; - const experimentAudienceConditions = getExperimentAudienceConditions(configObj, experiment.id); - const audiencesById = getAudiencesById(configObj); - this.logger.log( - LOG_LEVEL.DEBUG, - LOG_MESSAGES.EVALUATING_AUDIENCES_COMBINED, - MODULE_NAME, - evaluationAttribute, - loggingKey || experiment.key, - JSON.stringify(experimentAudienceConditions), - ); - decideReasons.push([ - LOG_MESSAGES.EVALUATING_AUDIENCES_COMBINED, - MODULE_NAME, - evaluationAttribute, - loggingKey || experiment.key, - JSON.stringify(experimentAudienceConditions), - ]); - const result = this.audienceEvaluator.evaluate(experimentAudienceConditions, audiencesById, user); - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT_COMBINED, - MODULE_NAME, - evaluationAttribute, - loggingKey || experiment.key, - result.toString().toUpperCase(), - ); - decideReasons.push([ - LOG_MESSAGES.AUDIENCE_EVALUATION_RESULT_COMBINED, - MODULE_NAME, - evaluationAttribute, - loggingKey || experiment.key, - result.toString().toUpperCase(), - ]); - - return { - result: result, - reasons: decideReasons, - }; - } - - /** - * Given an experiment key and user ID, returns params used in bucketer call - * @param {ProjectConfig} configObj The parsed project configuration object - * @param {string} experimentKey Experiment key used for bucketer - * @param {string} bucketingId ID to bucket user into - * @param {string} userId ID of user to be bucketed - * @return {BucketerParams} - */ - private buildBucketerParams( - configObj: ProjectConfig, - experiment: Experiment, - bucketingId: string, - userId: string - ): BucketerParams { - return { - bucketingId, - experimentId: experiment.id, - experimentKey: experiment.key, - experimentIdMap: configObj.experimentIdMap, - experimentKeyMap: configObj.experimentKeyMap, - groupIdMap: configObj.groupIdMap, - logger: this.logger, - trafficAllocationConfig: getTrafficAllocation(configObj, experiment.id), - userId, - variationIdMap: configObj.variationIdMap, - } - } - - /** - * Pull the stored variation out of the experimentBucketMap for an experiment/userId - * @param {ProjectConfig} configObj The parsed project configuration object - * @param {Experiment} experiment - * @param {string} userId - * @param {ExperimentBucketMap} experimentBucketMap mapping experiment => { variation_id: <variationId> } - * @return {Variation|null} the stored variation or null if the user profile does not have one for the given experiment - */ - private getStoredVariation( - configObj: ProjectConfig, - experiment: Experiment, - userId: string, - experimentBucketMap: ExperimentBucketMap - ): Variation | null { - if (experimentBucketMap.hasOwnProperty(experiment.id)) { - const decision = experimentBucketMap[experiment.id]; - const variationId = decision.variation_id; - if (configObj.variationIdMap.hasOwnProperty(variationId)) { - return configObj.variationIdMap[decision.variation_id]; - } else { - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.SAVED_VARIATION_NOT_FOUND, - MODULE_NAME, userId, - variationId, - experiment.key, - ); - } - } - - return null; - } - - /** - * Get the user profile with the given user ID - * @param {string} userId - * @return {UserProfile|null} the stored user profile or null if one isn't found - */ - private getUserProfile(userId: string): UserProfile | null { - const userProfile = { - user_id: userId, - experiment_bucket_map: {}, - }; - - if (!this.userProfileService) { - return userProfile; - } - - try { - return this.userProfileService.lookup(userId); - } catch (ex: any) { - this.logger.log( - LOG_LEVEL.ERROR, - ERROR_MESSAGES.USER_PROFILE_LOOKUP_ERROR, - MODULE_NAME, - userId, - ex.message, - ); - } - - return null; - } - - /** - * Saves the bucketing decision to the user profile - * @param {Experiment} experiment - * @param {Variation} variation - * @param {string} userId - * @param {ExperimentBucketMap} experimentBucketMap - */ - private saveUserProfile( - experiment: Experiment, - variation: Variation, - userId: string, - experimentBucketMap: ExperimentBucketMap - ): void { - if (!this.userProfileService) { - return; - } - - try { - experimentBucketMap[experiment.id] = { - variation_id: variation.id - }; - - this.userProfileService.save({ - user_id: userId, - experiment_bucket_map: experimentBucketMap, - }); - - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.SAVED_VARIATION, - MODULE_NAME, - variation.key, - experiment.key, - userId, - ); - } catch (ex: any) { - this.logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.USER_PROFILE_SAVE_ERROR, MODULE_NAME, userId, ex.message); - } - } - - /** - * Given a feature, user ID, and attributes, returns a decision response containing - * an object representing a decision and decide reasons. If the user was bucketed into - * a variation for the given feature and attributes, the decision object will have variation and - * experiment properties (both objects), as well as a decisionSource property. - * decisionSource indicates whether the decision was due to a rollout or an - * experiment. - * @param {ProjectConfig} configObj The parsed project configuration object - * @param {FeatureFlag} feature A feature flag object from project configuration - * @param {OptimizelyUserContext} user A user context - * @param {[key: string]: boolean} options Map of decide options - * @return {DecisionResponse} DecisionResponse DecisionResponse containing an object with experiment, variation, and decisionSource - * properties and decide reasons. If the user was not bucketed into a variation, the variation - * property in decision object is null. - */ - getVariationForFeature( - configObj: ProjectConfig, - feature: FeatureFlag, - user: OptimizelyUserContext, - options: { [key: string]: boolean } = {} - ): DecisionResponse<DecisionObj> { - - const decideReasons: (string | number)[][] = []; - const decisionVariation = this.getVariationForFeatureExperiment(configObj, feature, user, options); - decideReasons.push(...decisionVariation.reasons); - const experimentDecision = decisionVariation.result; - - if (experimentDecision.variation !== null) { - return { - result: experimentDecision, - reasons: decideReasons, - }; - } - - const decisionRolloutVariation = this.getVariationForRollout(configObj, feature, user); - decideReasons.push(...decisionRolloutVariation.reasons); - const rolloutDecision = decisionRolloutVariation.result; - const userId = user.getUserId(); - if (rolloutDecision.variation) { - this.logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.USER_IN_ROLLOUT, MODULE_NAME, userId, feature.key); - decideReasons.push([LOG_MESSAGES.USER_IN_ROLLOUT, MODULE_NAME, userId, feature.key]); - return { - result: rolloutDecision, - reasons: decideReasons, - }; - } - - this.logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.USER_NOT_IN_ROLLOUT, MODULE_NAME, userId, feature.key); - decideReasons.push([LOG_MESSAGES.USER_NOT_IN_ROLLOUT, MODULE_NAME, userId, feature.key]); - return { - result: rolloutDecision, - reasons: decideReasons, - }; - } - - private getVariationForFeatureExperiment( - configObj: ProjectConfig, - feature: FeatureFlag, - user: OptimizelyUserContext, - options: { [key: string]: boolean } = {} - ): DecisionResponse<DecisionObj> { - - const decideReasons: (string | number)[][] = []; - let variationKey = null; - let decisionVariation; - let index; - let variationForFeatureExperiment; - - // Check if the feature flag is under an experiment and the the user is bucketed into one of these experiments - if (feature.experimentIds.length > 0) { - // Evaluate each experiment ID and return the first bucketed experiment variation - for (index = 0; index < feature.experimentIds.length; index++) { - const experiment = getExperimentFromId(configObj, feature.experimentIds[index], this.logger); - if (experiment) { - decisionVariation = this.getVariationFromExperimentRule(configObj, feature.key, experiment, user, options); - decideReasons.push(...decisionVariation.reasons); - variationKey = decisionVariation.result; - if (variationKey) { - let variation = null; - variation = experiment.variationKeyMap[variationKey]; - if (!variation) { - variation = getFlagVariationByKey(configObj, feature.key, variationKey); - } - variationForFeatureExperiment = { - experiment: experiment, - variation: variation, - decisionSource: DECISION_SOURCES.FEATURE_TEST, - }; - - return { - result: variationForFeatureExperiment, - reasons: decideReasons, - } - } - } - } - } else { - this.logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.FEATURE_HAS_NO_EXPERIMENTS, MODULE_NAME, feature.key); - decideReasons.push([LOG_MESSAGES.FEATURE_HAS_NO_EXPERIMENTS, MODULE_NAME, feature.key]); - } - - variationForFeatureExperiment = { - experiment: null, - variation: null, - decisionSource: DECISION_SOURCES.FEATURE_TEST, - }; - - return { - result: variationForFeatureExperiment, - reasons: decideReasons, - }; - } - - private getVariationForRollout( - configObj: ProjectConfig, - feature: FeatureFlag, - user: OptimizelyUserContext, - ): DecisionResponse<DecisionObj> { - const decideReasons: (string | number)[][] = []; - let decisionObj: DecisionObj; - if (!feature.rolloutId) { - this.logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.NO_ROLLOUT_EXISTS, MODULE_NAME, feature.key); - decideReasons.push([LOG_MESSAGES.NO_ROLLOUT_EXISTS, MODULE_NAME, feature.key]); - decisionObj = { - experiment: null, - variation: null, - decisionSource: DECISION_SOURCES.ROLLOUT, - }; - - return { - result: decisionObj, - reasons: decideReasons, - }; - } - - const rollout = configObj.rolloutIdMap[feature.rolloutId]; - if (!rollout) { - this.logger.log( - LOG_LEVEL.ERROR, - ERROR_MESSAGES.INVALID_ROLLOUT_ID, - MODULE_NAME, - feature.rolloutId, - feature.key, - ); - decideReasons.push([ERROR_MESSAGES.INVALID_ROLLOUT_ID, MODULE_NAME, feature.rolloutId, feature.key]); - decisionObj = { - experiment: null, - variation: null, - decisionSource: DECISION_SOURCES.ROLLOUT, - }; - return { - result: decisionObj, - reasons: decideReasons, - }; - } - - const rolloutRules = rollout.experiments; - if (rolloutRules.length === 0) { - this.logger.log( - LOG_LEVEL.ERROR, - LOG_MESSAGES.ROLLOUT_HAS_NO_EXPERIMENTS, - MODULE_NAME, - feature.rolloutId, - ); - decideReasons.push([LOG_MESSAGES.ROLLOUT_HAS_NO_EXPERIMENTS, MODULE_NAME, feature.rolloutId]); - decisionObj = { - experiment: null, - variation: null, - decisionSource: DECISION_SOURCES.ROLLOUT, - }; - return { - result: decisionObj, - reasons: decideReasons, - }; - } - let decisionVariation; - let skipToEveryoneElse; - let variation; - let rolloutRule; - let index = 0; - while (index < rolloutRules.length) { - decisionVariation = this.getVariationFromDeliveryRule(configObj, feature.key, rolloutRules, index, user); - decideReasons.push(...decisionVariation.reasons); - variation = decisionVariation.result; - skipToEveryoneElse = decisionVariation.skipToEveryoneElse; - if (variation) { - rolloutRule = configObj.experimentIdMap[rolloutRules[index].id]; - decisionObj = { - experiment: rolloutRule, - variation: variation, - decisionSource: DECISION_SOURCES.ROLLOUT - }; - return { - result: decisionObj, - reasons: decideReasons, - }; - } - // the last rule is special for "Everyone Else" - index = skipToEveryoneElse ? (rolloutRules.length - 1) : (index + 1); - } - - decisionObj = { - experiment: null, - variation: null, - decisionSource: DECISION_SOURCES.ROLLOUT, - }; - - return { - result: decisionObj, - reasons: decideReasons, - }; - } - - /** - * Get bucketing Id from user attributes. - * @param {string} userId - * @param {UserAttributes} attributes - * @returns {string} Bucketing Id if it is a string type in attributes, user Id otherwise. - */ - private getBucketingId(userId: string, attributes?: UserAttributes): string { - let bucketingId = userId; - - // If the bucketing ID key is defined in attributes, than use that in place of the userID for the murmur hash key - if ( - attributes != null && - typeof attributes === 'object' && - attributes.hasOwnProperty(CONTROL_ATTRIBUTES.BUCKETING_ID) - ) { - if (typeof attributes[CONTROL_ATTRIBUTES.BUCKETING_ID] === 'string') { - bucketingId = attributes[CONTROL_ATTRIBUTES.BUCKETING_ID]; - this.logger.log(LOG_LEVEL.DEBUG, LOG_MESSAGES.VALID_BUCKETING_ID, MODULE_NAME, bucketingId); - } else { - this.logger.log(LOG_LEVEL.WARNING, LOG_MESSAGES.BUCKETING_ID_NOT_STRING, MODULE_NAME); - } - } - - return bucketingId; - } - - /** - * Finds a validated forced decision for specific flagKey and optional ruleKey. - * @param {ProjectConfig} config A projectConfig. - * @param {OptimizelyUserContext} user A Optimizely User Context. - * @param {string} flagKey A flagKey. - * @param {ruleKey} ruleKey A ruleKey (optional). - * @return {DecisionResponse<Variation|null>} DecisionResponse object containing valid variation object and decide reasons. - */ - findValidatedForcedDecision( - config: ProjectConfig, - user: OptimizelyUserContext, - flagKey: string, - ruleKey?: string - ): DecisionResponse<Variation | null> { - - const decideReasons: (string | number)[][] = []; - const forcedDecision = user.getForcedDecision({ flagKey, ruleKey }); - let variation = null; - let variationKey; - const userId = user.getUserId() - if (config && forcedDecision) { - variationKey = forcedDecision.variationKey; - variation = getFlagVariationByKey(config, flagKey, variationKey); - if (variation) { - if (ruleKey) { - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, - variationKey, - flagKey, - ruleKey, - userId - ); - decideReasons.push([ - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED, - variationKey, - flagKey, - ruleKey, - userId - ]); - } else { - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, - variationKey, - flagKey, - userId - ); - decideReasons.push([ - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED, - variationKey, - flagKey, - userId - ]) - } - } else { - if (ruleKey) { - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, - flagKey, - ruleKey, - userId - ); - decideReasons.push([ - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID, - flagKey, - ruleKey, - userId - ]); - } else { - this.logger.log( - LOG_LEVEL.INFO, - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, - flagKey, - userId - ); - decideReasons.push([ - LOG_MESSAGES.USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID, - flagKey, - userId - ]) - } - } - } - - return { - result: variation, - reasons: decideReasons, - } - } - - /** - * Removes forced variation for given userId and experimentKey - * @param {string} userId String representing the user id - * @param {string} experimentId Number representing the experiment id - * @param {string} experimentKey Key representing the experiment id - * @throws If the user id is not valid or not in the forced variation map - */ - removeForcedVariation(userId: string, experimentId: string, experimentKey: string): void { - if (!userId) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_USER_ID, MODULE_NAME)); - } - - if (this.forcedVariationMap.hasOwnProperty(userId)) { - delete this.forcedVariationMap[userId][experimentId]; - this.logger.log( - LOG_LEVEL.DEBUG, - LOG_MESSAGES.VARIATION_REMOVED_FOR_USER, - MODULE_NAME, - experimentKey, - userId, - ); - } else { - throw new Error(sprintf(ERROR_MESSAGES.USER_NOT_IN_FORCED_VARIATION, MODULE_NAME, userId)); - } - } - - /** - * Sets forced variation for given userId and experimentKey - * @param {string} userId String representing the user id - * @param {string} experimentId Number representing the experiment id - * @param {number} variationId Number representing the variation id - * @throws If the user id is not valid - */ - private setInForcedVariationMap(userId: string, experimentId: string, variationId: string): void { - if (this.forcedVariationMap.hasOwnProperty(userId)) { - this.forcedVariationMap[userId][experimentId] = variationId; - } else { - this.forcedVariationMap[userId] = {}; - this.forcedVariationMap[userId][experimentId] = variationId; - } - - this.logger.log( - LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_MAPPED_TO_FORCED_VARIATION, - MODULE_NAME, - variationId, - experimentId, - userId, - ); - } - - /** - * Gets the forced variation key for the given user and experiment. - * @param {ProjectConfig} configObj Object representing project configuration - * @param {string} experimentKey Key for experiment. - * @param {string} userId The user Id. - * @return {DecisionResponse<string|null>} DecisionResponse containing variation which the given user and experiment - * should be forced into and the decide reasons. - */ - getForcedVariation( - configObj: ProjectConfig, - experimentKey: string, - userId: string - ): DecisionResponse<string | null> { - const decideReasons: (string | number)[][] = []; - const experimentToVariationMap = this.forcedVariationMap[userId]; - if (!experimentToVariationMap) { - this.logger.log( - LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION, - MODULE_NAME, - userId, - ); - - return { - result: null, - reasons: decideReasons, - }; - } - - let experimentId; - try { - const experiment = getExperimentFromKey(configObj, experimentKey); - if (experiment.hasOwnProperty('id')) { - experimentId = experiment['id']; - } else { - // catching improperly formatted experiments - this.logger.log( - LOG_LEVEL.ERROR, - ERROR_MESSAGES.IMPROPERLY_FORMATTED_EXPERIMENT, - MODULE_NAME, - experimentKey, - ); - decideReasons.push([ - ERROR_MESSAGES.IMPROPERLY_FORMATTED_EXPERIMENT, - MODULE_NAME, - experimentKey, - ]); - - return { - result: null, - reasons: decideReasons, - }; - } - } catch (ex: any) { - // catching experiment not in datafile - this.logger.log(LOG_LEVEL.ERROR, ex.message); - decideReasons.push(ex.message); - - return { - result: null, - reasons: decideReasons, - }; - } - - const variationId = experimentToVariationMap[experimentId]; - if (!variationId) { - this.logger.log( - LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, - MODULE_NAME, - experimentKey, - userId, - ); - return { - result: null, - reasons: decideReasons, - }; - } - - const variationKey = getVariationKeyFromId(configObj, variationId); - if (variationKey) { - this.logger.log( - LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_FORCED_VARIATION, - MODULE_NAME, - variationKey, - experimentKey, - userId, - ); - decideReasons.push([ - LOG_MESSAGES.USER_HAS_FORCED_VARIATION, - MODULE_NAME, - variationKey, - experimentKey, - userId, - ]); - } else { - this.logger.log( - LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT, - MODULE_NAME, - experimentKey, - userId, - ); - } - - return { - result: variationKey, - reasons: decideReasons, - }; - } - - /** - * Sets the forced variation for a user in a given experiment - * @param {ProjectConfig} configObj Object representing project configuration - * @param {string} experimentKey Key for experiment. - * @param {string} userId The user Id. - * @param {string|null} variationKey Key for variation. If null, then clear the existing experiment-to-variation mapping - * @return {boolean} A boolean value that indicates if the set completed successfully. - */ - setForcedVariation( - configObj: ProjectConfig, - experimentKey: string, - userId: string, - variationKey: string | null - ): boolean { - if (variationKey != null && !stringValidator.validate(variationKey)) { - this.logger.log(LOG_LEVEL.ERROR, ERROR_MESSAGES.INVALID_VARIATION_KEY, MODULE_NAME); - return false; - } - - let experimentId; - try { - const experiment = getExperimentFromKey(configObj, experimentKey); - if (experiment.hasOwnProperty('id')) { - experimentId = experiment['id']; - } else { - // catching improperly formatted experiments - this.logger.log( - LOG_LEVEL.ERROR, - ERROR_MESSAGES.IMPROPERLY_FORMATTED_EXPERIMENT, - MODULE_NAME, - experimentKey, - ); - return false; - } - } catch (ex: any) { - // catching experiment not in datafile - this.logger.log(LOG_LEVEL.ERROR, ex.message); - return false; - } - - if (variationKey == null) { - try { - this.removeForcedVariation(userId, experimentId, experimentKey); - return true; - } catch (ex: any) { - this.logger.log(LOG_LEVEL.ERROR, ex.message); - return false; - } - } - - const variationId = getVariationIdFromExperimentAndVariationKey(configObj, experimentKey, variationKey); - - if (!variationId) { - this.logger.log( - LOG_LEVEL.ERROR, - ERROR_MESSAGES.NO_VARIATION_FOR_EXPERIMENT_KEY, - MODULE_NAME, - variationKey, - experimentKey, - ); - return false; - } - - try { - this.setInForcedVariationMap(userId, experimentId, variationId); - return true; - } catch (ex: any) { - this.logger.log(LOG_LEVEL.ERROR, ex.message); - return false; - } - } - - getVariationFromExperimentRule( - configObj: ProjectConfig, - flagKey: string, - rule: Experiment, - user: OptimizelyUserContext, - options: { [key: string]: boolean } = {} - ): DecisionResponse<string | null> { - const decideReasons: (string | number)[][] = []; - - // check forced decision first - const forcedDecisionResponse = this.findValidatedForcedDecision(configObj, user, flagKey, rule.key); - decideReasons.push(...forcedDecisionResponse.reasons); - - const forcedVariation = forcedDecisionResponse.result; - if (forcedVariation) { - return { - result: forcedVariation.key, - reasons: decideReasons, - }; - } - const decisionVariation = this.getVariation(configObj, rule, user, options); - decideReasons.push(...decisionVariation.reasons); - const variationKey = decisionVariation.result; - - return { - result: variationKey, - reasons: decideReasons, - }; - } - - getVariationFromDeliveryRule( - configObj: ProjectConfig, - flagKey: string, - rules: Experiment[], - ruleIndex: number, - user: OptimizelyUserContext - ): DeliveryRuleResponse<Variation | null, boolean> { - const decideReasons: (string | number)[][] = []; - let skipToEveryoneElse = false; - - // check forced decision first - const rule = rules[ruleIndex]; - const forcedDecisionResponse = this.findValidatedForcedDecision(configObj, user, flagKey, rule.key); - decideReasons.push(...forcedDecisionResponse.reasons); - - const forcedVariation = forcedDecisionResponse.result; - if (forcedVariation) { - return { - result: forcedVariation, - reasons: decideReasons, - skipToEveryoneElse, - }; - } - - const userId = user.getUserId(); - const attributes = user.getAttributes(); - const bucketingId = this.getBucketingId(userId, attributes); - const everyoneElse = ruleIndex === rules.length - 1; - const loggingKey = everyoneElse ? "Everyone Else" : ruleIndex + 1; - - let bucketedVariation = null; - let bucketerVariationId; - let bucketerParams; - let decisionVariation; - const decisionifUserIsInAudience = this.checkIfUserIsInAudience( - configObj, - rule, - AUDIENCE_EVALUATION_TYPES.RULE, - user, - loggingKey - ); - decideReasons.push(...decisionifUserIsInAudience.reasons); - if (decisionifUserIsInAudience.result) { - this.logger.log( - LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, - MODULE_NAME, - userId, - loggingKey - ); - decideReasons.push([ - LOG_MESSAGES.USER_MEETS_CONDITIONS_FOR_TARGETING_RULE, - MODULE_NAME, - userId, - loggingKey - ]); - - bucketerParams = this.buildBucketerParams(configObj, rule, bucketingId, userId); - decisionVariation = bucket(bucketerParams); - decideReasons.push(...decisionVariation.reasons); - bucketerVariationId = decisionVariation.result; - if (bucketerVariationId) { - bucketedVariation = getVariationFromId(configObj, bucketerVariationId); - } - if (bucketedVariation) { - this.logger.log( - LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_BUCKETED_INTO_TARGETING_RULE, - MODULE_NAME, - userId, - loggingKey - ); - decideReasons.push([ - LOG_MESSAGES.USER_BUCKETED_INTO_TARGETING_RULE, - MODULE_NAME, - userId, - loggingKey]); - } else if (!everyoneElse) { - // skip this logging for EveryoneElse since this has a message not for EveryoneElse - this.logger.log( - LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_NOT_BUCKETED_INTO_TARGETING_RULE, - MODULE_NAME, - userId, - loggingKey - ); - decideReasons.push([ - LOG_MESSAGES.USER_NOT_BUCKETED_INTO_TARGETING_RULE, - MODULE_NAME, - userId, - loggingKey - ]); - - // skip the rest of rollout rules to the everyone-else rule if audience matches but not bucketed - skipToEveryoneElse = true; - } - } else { - this.logger.log( - LOG_LEVEL.DEBUG, - LOG_MESSAGES.USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, - MODULE_NAME, - userId, - loggingKey - ); - decideReasons.push([ - LOG_MESSAGES.USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE, - MODULE_NAME, - userId, - loggingKey - ]); - } - - return { - result: bucketedVariation, - reasons: decideReasons, - skipToEveryoneElse, - }; - } -} - -/** - * Creates an instance of the DecisionService. - * @param {DecisionServiceOptions} options Configuration options - * @return {Object} An instance of the DecisionService - */ -export function createDecisionService(options: DecisionServiceOptions): DecisionService { - return new DecisionService(options); -} diff --git a/packages/optimizely-sdk/lib/core/event_builder/index.tests.js b/packages/optimizely-sdk/lib/core/event_builder/index.tests.js deleted file mode 100644 index 39ed140ad..000000000 --- a/packages/optimizely-sdk/lib/core/event_builder/index.tests.js +++ /dev/null @@ -1,1708 +0,0 @@ -/** - * Copyright 2016-2021, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import sinon from 'sinon'; -import { assert } from 'chai'; - -import fns from '../../utils/fns'; -import testData from '../../tests/test_data'; -import projectConfig from '../project_config'; -import packageJSON from '../../../package.json'; -import { getConversionEvent, getImpressionEvent } from './'; - -describe('lib/core/event_builder', function() { - describe('APIs', function() { - var mockLogger; - var configObj; - var clock; - - beforeEach(function() { - configObj = projectConfig.createProjectConfig(testData.getTestProjectConfig()); - clock = sinon.useFakeTimers(new Date().getTime()); - sinon.stub(fns, 'uuid').returns('a68cf1ad-0393-4e18-af87-efe8f01a7c9c'); - mockLogger = { - log: sinon.stub(), - }; - }); - - afterEach(function() { - clock.restore(); - fns.uuid.restore(); - }); - - describe('getImpressionEvent', function() { - it('should create proper params for getImpressionEvent without attributes', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - attributes: [], - visitor_id: 'testUser', - snapshots: [ - { - decisions: [ - { - variation_id: '111128', - experiment_id: '111127', - campaign_id: '4', - metadata: { - flag_key: 'flagKey1', - rule_key: 'exp1', - rule_type: 'experiment', - variation_key: 'control', - enabled: true, - }, - }, - ], - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '4', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'campaign_activated', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - experimentId: '111127', - ruleKey: 'exp1', - flagKey: 'flagKey1', - enabled: true, - ruleType: 'experiment', - variationId: '111128', - userId: 'testUser', - }; - - var actualParams = getImpressionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should create proper params for getImpressionEvent with attributes as a string value', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - attributes: [ - { - entity_id: '111094', - type: 'custom', - value: 'firefox', - key: 'browser_type', - }, - ], - visitor_id: 'testUser', - snapshots: [ - { - decisions: [ - { - variation_id: '111128', - experiment_id: '111127', - campaign_id: '4', - metadata: { - flag_key: 'flagKey1', - rule_key: 'exp1', - rule_type: 'experiment', - variation_key: 'control', - enabled: false, - }, - }, - ], - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '4', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'campaign_activated', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - var eventOptions = { - attributes: { browser_type: 'firefox' }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - experimentId: '111127', - variationId: '111128', - ruleKey: 'exp1', - flagKey: 'flagKey1', - enabled: false, - ruleType: 'experiment', - userId: 'testUser', - }; - - var actualParams = getImpressionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should create proper params for getImpressionEvent with attributes as a false value', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - attributes: [ - { - entity_id: '111094', - type: 'custom', - value: false, - key: 'browser_type', - }, - ], - visitor_id: 'testUser', - snapshots: [ - { - decisions: [ - { - variation_id: '111128', - experiment_id: '111127', - campaign_id: '4', - metadata: { - flag_key: 'flagKey1', - rule_key: 'exp1', - rule_type: 'experiment', - variation_key: 'control', - enabled: true, - }, - }, - ], - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '4', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'campaign_activated', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { browser_type: false }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - experimentId: '111127', - ruleKey: 'exp1', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: true, - variationId: '111128', - userId: 'testUser', - }; - - var actualParams = getImpressionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should create proper params for getImpressionEvent with attributes as a zero value', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - attributes: [ - { - entity_id: '111094', - type: 'custom', - value: 0, - key: 'browser_type', - }, - ], - visitor_id: 'testUser', - snapshots: [ - { - decisions: [ - { - variation_id: '111128', - experiment_id: '111127', - campaign_id: '4', - metadata: { - flag_key: 'flagKey1', - rule_key: 'exp1', - rule_type: 'experiment', - variation_key: 'control', - enabled: true, - }, - }, - ], - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '4', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'campaign_activated', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { browser_type: 0 }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - experimentId: '111127', - ruleKey: 'exp1', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: true, - variationId: '111128', - userId: 'testUser', - }; - - var actualParams = getImpressionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should not fill in userFeatures for getImpressionEvent when attribute is not in the datafile', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - attributes: [], - visitor_id: 'testUser', - snapshots: [ - { - decisions: [ - { - variation_id: '111128', - experiment_id: '111127', - campaign_id: '4', - metadata: { - flag_key: 'flagKey1', - rule_key: 'exp1', - rule_type: 'experiment', - variation_key: 'control', - enabled: false, - }, - }, - ], - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '4', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'campaign_activated', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { invalid_attribute: 'sorry_not_sorry' }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - experimentId: '111127', - ruleKey: 'exp1', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: false, - variationId: '111128', - userId: 'testUser', - logger: mockLogger, - }; - - var actualParams = getImpressionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should fill in userFeatures for user agent and bot filtering (bot filtering enabled)', function() { - var v4ConfigObj = projectConfig.createProjectConfig(testData.getTestProjectConfigWithFeatures()); - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '572018', - project_id: '594001', - visitors: [ - { - attributes: [ - { - entity_id: '$opt_user_agent', - key: '$opt_user_agent', - type: 'custom', - value: 'Chrome', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - visitor_id: 'testUser', - snapshots: [ - { - decisions: [ - { - variation_id: '595008', - experiment_id: '595010', - campaign_id: '595005', - metadata: { - flag_key: 'flagKey2', - rule_key: 'exp2', - rule_type: 'experiment', - variation_key: 'var', - enabled: false, - }, - }, - ], - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '595005', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'campaign_activated', - }, - ], - }, - ], - }, - ], - revision: '35', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: true, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { $opt_user_agent: 'Chrome' }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: v4ConfigObj, - experimentId: '595010', - ruleKey: 'exp2', - flagKey: 'flagKey2', - ruleType: 'experiment', - enabled: false, - variationId: '595008', - userId: 'testUser', - }; - - var actualParams = getImpressionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should fill in userFeatures for user agent and bot filtering (bot filtering disabled)', function() { - var v4ConfigObj = projectConfig.createProjectConfig(testData.getTestProjectConfigWithFeatures()); - v4ConfigObj.botFiltering = false; - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '572018', - project_id: '594001', - visitors: [ - { - attributes: [ - { - entity_id: '$opt_user_agent', - key: '$opt_user_agent', - type: 'custom', - value: 'Chrome', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: false, - }, - ], - visitor_id: 'testUser', - snapshots: [ - { - decisions: [ - { - variation_id: '595008', - experiment_id: '595010', - campaign_id: '595005', - metadata: { - flag_key: 'flagKey2', - rule_key: 'exp2', - rule_type: 'experiment', - variation_key: 'var', - enabled: false, - }, - }, - ], - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '595005', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'campaign_activated', - }, - ], - }, - ], - }, - ], - revision: '35', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: true, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { $opt_user_agent: 'Chrome' }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: v4ConfigObj, - experimentId: '595010', - ruleKey: 'exp2', - flagKey: 'flagKey2', - ruleType: 'experiment', - enabled: false, - variationId: '595008', - userId: 'testUser', - }; - - var actualParams = getImpressionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should create proper params for getImpressionEvent with typed attributes', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - attributes: [ - { - entity_id: '111094', - key: 'browser_type', - type: 'custom', - value: 'Chrome', - }, - { - entity_id: '323434545', - key: 'boolean_key', - type: 'custom', - value: true, - }, - { - entity_id: '616727838', - key: 'integer_key', - type: 'custom', - value: 10, - }, - { - entity_id: '808797686', - key: 'double_key', - type: 'custom', - value: 3.14, - }, - ], - visitor_id: 'testUser', - snapshots: [ - { - decisions: [ - { - variation_id: '111128', - experiment_id: '111127', - campaign_id: '4', - metadata: { - flag_key: 'flagKey1', - rule_key: 'exp1', - rule_type: 'experiment', - variation_key: 'control', - enabled: false, - }, - }, - ], - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '4', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'campaign_activated', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { - browser_type: 'Chrome', - boolean_key: true, - integer_key: 10, - double_key: 3.14, - }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - experimentId: '111127', - ruleKey: 'exp1', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: false, - variationId: '111128', - userId: 'testUser', - }; - - var actualParams = getImpressionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should remove invalid params from impression event payload', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - attributes: [ - { - entity_id: '111094', - key: 'browser_type', - type: 'custom', - value: 'Chrome', - }, - { - entity_id: '808797687', - key: 'valid_positive_number', - type: 'custom', - value: Math.pow(2, 53), - }, - { - entity_id: '808797688', - key: 'valid_negative_number', - type: 'custom', - value: -Math.pow(2, 53), - }, - ], - visitor_id: 'testUser', - snapshots: [ - { - decisions: [ - { - variation_id: '111128', - experiment_id: '111127', - campaign_id: '4', - metadata: { - flag_key: 'flagKey1', - rule_key: 'exp1', - rule_type: 'experiment', - variation_key: 'control', - enabled: true, - }, - }, - ], - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '4', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'campaign_activated', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { - browser_type: 'Chrome', - valid_positive_number: Math.pow(2, 53), - valid_negative_number: -Math.pow(2, 53), - invalid_number: Math.pow(2, 53) + 2, - array: [1, 2, 3], - }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - experimentId: '111127', - ruleKey: 'exp1', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: true, - variationId: '111128', - userId: 'testUser', - }; - - var actualParams = getImpressionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - }); - - describe('getConversionEvent', function() { - it('should create proper params for getConversionEvent without attributes or event value', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - visitor_id: 'testUser', - attributes: [], - snapshots: [ - { - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '111095', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'testEvent', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should create proper params for getConversionEvent with attributes', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - visitor_id: 'testUser', - attributes: [ - { - entity_id: '111094', - type: 'custom', - value: 'firefox', - key: 'browser_type', - }, - ], - snapshots: [ - { - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '111095', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'testEvent', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { browser_type: 'firefox' }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should create proper params for getConversionEvent with event value', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - client_version: packageJSON.version, - project_id: '111001', - visitors: [ - { - attributes: [], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - tags: { - revenue: 4200, - }, - timestamp: Math.round(new Date().getTime()), - revenue: 4200, - key: 'testEvent', - entity_id: '111095', - }, - ], - }, - ], - }, - ], - account_id: '12001', - client_name: 'node-sdk', - revision: '42', - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - eventTags: { - revenue: 4200, - }, - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should create proper params for getConversionEvent with attributes and event value', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - client_version: packageJSON.version, - project_id: '111001', - visitors: [ - { - attributes: [ - { - entity_id: '111094', - type: 'custom', - value: 'firefox', - key: 'browser_type', - }, - ], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - tags: { - revenue: 4200, - }, - timestamp: Math.round(new Date().getTime()), - revenue: 4200, - key: 'testEvent', - entity_id: '111095', - }, - ], - }, - ], - }, - ], - account_id: '12001', - client_name: 'node-sdk', - revision: '42', - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { browser_type: 'firefox' }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - eventTags: { - revenue: 4200, - }, - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should not fill in userFeatures for getConversion when attribute is not in the datafile', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - client_version: packageJSON.version, - project_id: '111001', - visitors: [ - { - attributes: [], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - timestamp: Math.round(new Date().getTime()), - key: 'testEvent', - entity_id: '111095', - }, - ], - }, - ], - }, - ], - account_id: '12001', - client_name: 'node-sdk', - revision: '42', - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { invalid_attribute: 'sorry_not_sorry' }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - sinon.assert.calledOnce(mockLogger.log); - assert.deepEqual(actualParams, expectedParams); - }); - - it('should fill in userFeatures for user agent and bot filtering (bot filtering enabled)', function() { - var v4ConfigObj = projectConfig.createProjectConfig(testData.getTestProjectConfigWithFeatures()); - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '572018', - project_id: '594001', - visitors: [ - { - attributes: [ - { - entity_id: '$opt_user_agent', - key: '$opt_user_agent', - type: 'custom', - value: 'Chrome', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '594089', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'item_bought', - }, - ], - }, - ], - }, - ], - revision: '35', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: true, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { $opt_user_agent: 'Chrome' }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: v4ConfigObj, - eventKey: 'item_bought', - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should fill in userFeatures for user agent and bot filtering (bot filtering disabled)', function() { - var v4ConfigObj = projectConfig.createProjectConfig(testData.getTestProjectConfigWithFeatures()); - v4ConfigObj.botFiltering = false; - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '572018', - project_id: '594001', - visitors: [ - { - attributes: [ - { - entity_id: '$opt_user_agent', - key: '$opt_user_agent', - type: 'custom', - value: 'Chrome', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: false, - }, - ], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '594089', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'item_bought', - }, - ], - }, - ], - }, - ], - revision: '35', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: true, - enrich_decisions: true, - }, - }; - - var eventOptions = { - attributes: { $opt_user_agent: 'Chrome' }, - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: v4ConfigObj, - eventKey: 'item_bought', - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should create the correct snapshot for multiple experiments attached to the event', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - visitor_id: 'testUser', - attributes: [], - snapshots: [ - { - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '111100', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'testEventWithMultipleExperiments', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEventWithMultipleExperiments', - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should remove invalid params from conversion event payload', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - visitor_id: 'testUser', - attributes: [ - { - entity_id: '111094', - key: 'browser_type', - type: 'custom', - value: 'Chrome', - }, - { - entity_id: '808797687', - key: 'valid_positive_number', - type: 'custom', - value: Math.pow(2, 53), - }, - { - entity_id: '808797688', - key: 'valid_negative_number', - type: 'custom', - value: -Math.pow(2, 53), - }, - ], - snapshots: [ - { - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '111100', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'testEventWithMultipleExperiments', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEventWithMultipleExperiments', - logger: mockLogger, - userId: 'testUser', - attributes: { - browser_type: 'Chrome', - valid_positive_number: Math.pow(2, 53), - valid_negative_number: -Math.pow(2, 53), - invalid_number: -Math.pow(2, 53) - 2, - array: [1, 2, 3], - }, - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - describe('and event tags are passed it', function() { - it('should create proper params for getConversionEvent with event tags', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - client_version: packageJSON.version, - project_id: '111001', - visitors: [ - { - attributes: [], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - tags: { - 'non-revenue': 'cool', - }, - timestamp: Math.round(new Date().getTime()), - key: 'testEvent', - entity_id: '111095', - }, - ], - }, - ], - }, - ], - account_id: '12001', - client_name: 'node-sdk', - revision: '42', - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - eventTags: { - 'non-revenue': 'cool', - }, - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - describe('and the event tags contain an entry for "revenue"', function() { - it('should include the revenue value in the event object', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - client_version: packageJSON.version, - project_id: '111001', - visitors: [ - { - attributes: [], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - tags: { - 'non-revenue': 'cool', - revenue: 4200, - }, - timestamp: Math.round(new Date().getTime()), - revenue: 4200, - key: 'testEvent', - entity_id: '111095', - }, - ], - }, - ], - }, - ], - account_id: '12001', - client_name: 'node-sdk', - revision: '42', - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - eventTags: { - revenue: 4200, - 'non-revenue': 'cool', - }, - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should include revenue value of 0 in the event object', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - client_version: packageJSON.version, - project_id: '111001', - visitors: [ - { - attributes: [], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - tags: { - revenue: 0, - }, - timestamp: Math.round(new Date().getTime()), - revenue: 0, - key: 'testEvent', - entity_id: '111095', - }, - ], - }, - ], - }, - ], - account_id: '12001', - client_name: 'node-sdk', - revision: '42', - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - eventTags: { - revenue: 0, - }, - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - describe('and the revenue value is invalid', function() { - it('should not include the revenue value in the event object', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - client_version: packageJSON.version, - project_id: '111001', - visitors: [ - { - attributes: [], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - tags: { - 'non-revenue': 'cool', - revenue: 'invalid revenue', - }, - timestamp: Math.round(new Date().getTime()), - key: 'testEvent', - entity_id: '111095', - }, - ], - }, - ], - }, - ], - account_id: '12001', - client_name: 'node-sdk', - revision: '42', - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - eventTags: { - revenue: 'invalid revenue', - 'non-revenue': 'cool', - }, - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - }); - }); - - describe('and the event tags contain an entry for "value"', function() { - it('should include the event value in the event object', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - client_version: packageJSON.version, - project_id: '111001', - visitors: [ - { - attributes: [], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - tags: { - 'non-revenue': 'cool', - value: '13.37', - }, - timestamp: Math.round(new Date().getTime()), - value: 13.37, - key: 'testEvent', - entity_id: '111095', - }, - ], - }, - ], - }, - ], - account_id: '12001', - client_name: 'node-sdk', - revision: '42', - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - eventTags: { - value: '13.37', - 'non-revenue': 'cool', - }, - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - it('should include the falsy event values in the event object', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - client_version: packageJSON.version, - project_id: '111001', - visitors: [ - { - attributes: [], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - tags: { - value: '0.0', - }, - timestamp: Math.round(new Date().getTime()), - value: 0.0, - key: 'testEvent', - entity_id: '111095', - }, - ], - }, - ], - }, - ], - account_id: '12001', - client_name: 'node-sdk', - revision: '42', - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - eventTags: { - value: '0.0', - }, - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - - describe('and the event value is invalid', function() { - it('should not include the event value in the event object', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - client_version: packageJSON.version, - project_id: '111001', - visitors: [ - { - attributes: [], - visitor_id: 'testUser', - snapshots: [ - { - events: [ - { - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - tags: { - 'non-revenue': 'cool', - value: 'invalid value', - }, - timestamp: Math.round(new Date().getTime()), - key: 'testEvent', - entity_id: '111095', - }, - ], - }, - ], - }, - ], - account_id: '12001', - client_name: 'node-sdk', - revision: '42', - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - eventTags: { - value: 'invalid value', - 'non-revenue': 'cool', - }, - logger: mockLogger, - userId: 'testUser', - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - }); - }); - }); - - describe('createEventWithBucketingId', function() { - it('should send proper bucketingID with user attributes', function() { - var expectedParams = { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: { - account_id: '12001', - project_id: '111001', - visitors: [ - { - visitor_id: 'testUser', - attributes: [ - { - entity_id: '$opt_bucketing_id', - key: '$opt_bucketing_id', - type: 'custom', - value: 'variation', - }, - ], - snapshots: [ - { - events: [ - { - timestamp: Math.round(new Date().getTime()), - entity_id: '111095', - uuid: 'a68cf1ad-0393-4e18-af87-efe8f01a7c9c', - key: 'testEvent', - }, - ], - }, - ], - }, - ], - revision: '42', - client_name: 'node-sdk', - client_version: packageJSON.version, - anonymize_ip: false, - enrich_decisions: true, - }, - }; - - var eventOptions = { - clientEngine: 'node-sdk', - clientVersion: packageJSON.version, - configObj: configObj, - eventKey: 'testEvent', - logger: mockLogger, - userId: 'testUser', - attributes: { $opt_bucketing_id: 'variation' }, - }; - - var actualParams = getConversionEvent(eventOptions); - - assert.deepEqual(actualParams, expectedParams); - }); - }); - }); - }); -}); diff --git a/packages/optimizely-sdk/lib/core/event_builder/index.ts b/packages/optimizely-sdk/lib/core/event_builder/index.ts deleted file mode 100644 index 093994aa0..000000000 --- a/packages/optimizely-sdk/lib/core/event_builder/index.ts +++ /dev/null @@ -1,322 +0,0 @@ -/** - * Copyright 2016-2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { LoggerFacade } from '../../modules/logging'; -import { EventV1 as CommonEventParams } from '../../modules/event_processor'; - -import fns from '../../utils/fns'; -import { CONTROL_ATTRIBUTES, RESERVED_EVENT_KEYWORDS } from '../../utils/enums'; -import { - getAttributeId, - getEventId, - getLayerId, - getVariationKeyFromId, - ProjectConfig, -} from '../project_config'; -import * as eventTagUtils from '../../utils/event_tag_utils'; -import { isAttributeValid } from '../../utils/attributes_validator'; -import { EventTags, UserAttributes, Event as EventLoggingEndpoint } from '../../shared_types'; - -const ACTIVATE_EVENT_KEY = 'campaign_activated'; -const CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom'; -const ENDPOINT = 'https://logx.optimizely.com/v1/events'; -const HTTP_VERB = 'POST'; - -interface ImpressionOptions { - // Object representing user attributes and values which need to be recorded - attributes?: UserAttributes; - // The client we are using: node or javascript - clientEngine: string; - // The version of the client - clientVersion: string; - // Object representing project configuration, including datafile information and mappings for quick lookup - configObj: ProjectConfig; - // Experiment for which impression needs to be recorded - experimentId: string | null; - // Key of an experiment for which impression needs to be recorded - ruleKey: string; - // Key for a feature flag - flagKey: string; - // Boolean representing if feature is enabled - enabled: boolean; - // Type for the decision source - ruleType: string; - // Event key representing the event which needs to be recorded - eventKey?: string; - // ID for variation which would be presented to user - variationId: string | null; - // Logger object - logger: LoggerFacade; - // ID for user - userId: string; -} - -interface ConversionEventOptions { - // Object representing user attributes and values which need to be recorded - attributes?: UserAttributes; - // The client we are using: node or javascript - clientEngine: string; - // The version of the client - clientVersion: string; - // Object representing project configuration, including datafile information and mappings for quick lookup - configObj: ProjectConfig; - // Event key representing the event which needs to be recorded - eventKey: string; - // Logger object - logger: LoggerFacade; - // ID for user - userId: string; - // Object with event-specific tags - eventTags?: EventTags; -} - -type Metadata = { - flag_key: string; - rule_key: string; - rule_type: string; - variation_key: string; - enabled: boolean; -} - -type Decision = { - campaign_id: string | null; - experiment_id: string | null; - variation_id: string | null; - metadata: Metadata; -} - -type SnapshotEvent = { - entity_id: string | null; - timestamp: number; - uuid: string; - key: string; - revenue?: number; - value?: number; - tags?: EventTags; -} - -interface Snapshot { - decisions?: Decision[]; - events: SnapshotEvent[]; -} - -/** - * Get params which are used same in both conversion and impression events - * @param {ImpressionOptions|ConversionEventOptions} options Object containing values needed to build impression/conversion event - * @return {CommonEventParams} Common params with properties that are used in both conversion and impression events - */ -function getCommonEventParams({ - attributes, - userId, - clientEngine, - clientVersion, - configObj, - logger, -}: ImpressionOptions | ConversionEventOptions): CommonEventParams { - - const anonymize_ip = configObj.anonymizeIP ? configObj.anonymizeIP : false; - const botFiltering = configObj.botFiltering; - - const visitor = { - snapshots: [], - visitor_id: userId, - attributes: [], - }; - - const commonParams: CommonEventParams = { - account_id: configObj.accountId, - project_id: configObj.projectId, - visitors: [visitor], - revision: configObj.revision, - client_name: clientEngine, - client_version: clientVersion, - anonymize_ip: anonymize_ip, - enrich_decisions: true, - }; - - if (attributes) { - // Omit attribute values that are not supported by the log endpoint. - Object.keys(attributes || {}).forEach(function(attributeKey) { - const attributeValue = attributes[attributeKey]; - if (isAttributeValid(attributeKey, attributeValue)) { - const attributeId = getAttributeId(configObj, attributeKey, logger); - if (attributeId) { - commonParams.visitors[0].attributes.push({ - entity_id: attributeId, - key: attributeKey, - type: CUSTOM_ATTRIBUTE_FEATURE_TYPE, - value: attributes[attributeKey], - }); - } - } - }); - } - - - if (typeof botFiltering === 'boolean') { - commonParams.visitors[0].attributes.push({ - entity_id: CONTROL_ATTRIBUTES.BOT_FILTERING, - key: CONTROL_ATTRIBUTES.BOT_FILTERING, - type: CUSTOM_ATTRIBUTE_FEATURE_TYPE, - value: botFiltering, - }); - } - - return commonParams; -} - -/** - * Creates object of params specific to impression events - * @param {ProjectConfig} configObj Object representing project configuration - * @param {string|null} experimentId ID of experiment for which impression needs to be recorded - * @param {string|null} variationId ID for variation which would be presented to user - * @param {string} ruleKey Key of experiment for which impression needs to be recorded - * @param {string} ruleType Type for the decision source - * @param {string} flagKey Key for a feature flag - * @param {boolean} enabled Boolean representing if feature is enabled - * @return {Snapshot} Impression event params - */ -function getImpressionEventParams( - configObj: ProjectConfig, - experimentId: string | null, - variationId: string | null, - ruleKey: string, - ruleType: string, - flagKey: string, - enabled: boolean -): Snapshot { - - const campaignId = experimentId ? getLayerId(configObj, experimentId) : null; - - let variationKey = variationId ? getVariationKeyFromId(configObj, variationId) : null; - variationKey = variationKey || ''; - - const impressionEventParams = { - decisions: [ - { - campaign_id: campaignId, - experiment_id: experimentId, - variation_id: variationId, - metadata: { - flag_key: flagKey, - rule_key: ruleKey, - rule_type: ruleType, - variation_key: variationKey, - enabled: enabled, - } - }, - ], - events: [ - { - entity_id: campaignId, - timestamp: fns.currentTimestamp(), - key: ACTIVATE_EVENT_KEY, - uuid: fns.uuid(), - }, - ], - }; - - return impressionEventParams; -} - -/** - * Creates object of params specific to conversion events - * @param {ProjectConfig} configObj Object representing project configuration - * @param {string} eventKey Event key representing the event which needs to be recorded - * @param {LoggerFacade} logger Logger object - * @param {EventTags} eventTags Values associated with the event. - * @return {Snapshot} Conversion event params - */ -function getVisitorSnapshot( - configObj: ProjectConfig, - eventKey: string, - logger: LoggerFacade, - eventTags?: EventTags, -): Snapshot { - const snapshot: Snapshot = { - events: [], - }; - - const eventDict: SnapshotEvent = { - entity_id: getEventId(configObj, eventKey), - timestamp: fns.currentTimestamp(), - uuid: fns.uuid(), - key: eventKey, - }; - - if (eventTags) { - const revenue = eventTagUtils.getRevenueValue(eventTags, logger); - if (revenue !== null) { - eventDict[RESERVED_EVENT_KEYWORDS.REVENUE] = revenue; - } - - const eventValue = eventTagUtils.getEventValue(eventTags, logger); - if (eventValue !== null) { - eventDict[RESERVED_EVENT_KEYWORDS.VALUE] = eventValue; - } - - eventDict['tags'] = eventTags; - } - snapshot.events.push(eventDict); - - return snapshot; -} - -/** - * Create impression event params to be sent to the logging endpoint - * @param {ImpressionOptions} options Object containing values needed to build impression event - * @return {EventLoggingEndpoint} Params to be used in impression event logging endpoint call - */ -export function getImpressionEvent(options: ImpressionOptions): EventLoggingEndpoint { - const commonParams = getCommonEventParams(options); - const impressionEventParams = getImpressionEventParams( - options.configObj, - options.experimentId, - options.variationId, - options.ruleKey, - options.ruleType, - options.flagKey, - options.enabled, - ); - commonParams.visitors[0].snapshots.push(impressionEventParams); - - const impressionEvent: EventLoggingEndpoint = { - httpVerb: HTTP_VERB, - url: ENDPOINT, - params: commonParams, - } - - return impressionEvent; -} - -/** - * Create conversion event params to be sent to the logging endpoint - * @param {ConversionEventOptions} options Object containing values needed to build conversion event - * @return {EventLoggingEndpoint} Params to be used in conversion event logging endpoint call - */ -export function getConversionEvent(options: ConversionEventOptions): EventLoggingEndpoint { - - const commonParams = getCommonEventParams(options); - const snapshot = getVisitorSnapshot(options.configObj, options.eventKey, options.logger, options.eventTags); - commonParams.visitors[0].snapshots = [snapshot]; - - const conversionEvent: EventLoggingEndpoint = { - httpVerb: HTTP_VERB, - url: ENDPOINT, - params: commonParams, - } - - return conversionEvent; -} diff --git a/packages/optimizely-sdk/lib/core/notification_center/index.ts b/packages/optimizely-sdk/lib/core/notification_center/index.ts deleted file mode 100644 index a0a91dffe..000000000 --- a/packages/optimizely-sdk/lib/core/notification_center/index.ts +++ /dev/null @@ -1,246 +0,0 @@ -/** - * Copyright 2020, 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { LogHandler, ErrorHandler } from '../../modules/logging'; -import { objectValues } from '../../utils/fns'; -import { NotificationListener, ListenerPayload } from '../../shared_types'; - -import { - LOG_LEVEL, - LOG_MESSAGES, - NOTIFICATION_TYPES, -} from '../../utils/enums'; - -const MODULE_NAME = 'NOTIFICATION_CENTER'; - -interface NotificationCenterOptions { - logger: LogHandler; - errorHandler: ErrorHandler; -} - -interface ListenerEntry { - id: number; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - callback: (notificationData: any) => void; -} - -type NotificationListeners = { - [key: string]: ListenerEntry[]; -} - -/** - * NotificationCenter allows registration and triggering of callback functions using - * notification event types defined in NOTIFICATION_TYPES of utils/enums/index.js: - * - ACTIVATE: An impression event will be sent to Optimizely. - * - TRACK a conversion event will be sent to Optimizely - */ -export class NotificationCenter { - private logger: LogHandler; - private errorHandler: ErrorHandler; - private notificationListeners: NotificationListeners; - private listenerId: number; - - /** - * @constructor - * @param {NotificationCenterOptions} options - * @param {LogHandler} options.logger An instance of a logger to log messages with - * @param {ErrorHandler} options.errorHandler An instance of errorHandler to handle any unexpected error - */ - constructor(options: NotificationCenterOptions) { - this.logger = options.logger; - this.errorHandler = options.errorHandler; - this.notificationListeners = {}; - objectValues(NOTIFICATION_TYPES).forEach( - (notificationTypeEnum) => { - this.notificationListeners[notificationTypeEnum] = []; - } - ); - this.listenerId = 1; - } - - /** - * Add a notification callback to the notification center - * @param {string} notificationType One of the values from NOTIFICATION_TYPES in utils/enums/index.js - * @param {NotificationListener<T>} callback Function that will be called when the event is triggered - * @returns {number} If the callback was successfully added, returns a listener ID which can be used - * to remove the callback by calling removeNotificationListener. The ID is a number greater than 0. - * If there was an error and the listener was not added, addNotificationListener returns -1. This - * can happen if the first argument is not a valid notification type, or if the same callback - * function was already added as a listener by a prior call to this function. - */ - addNotificationListener<T extends ListenerPayload>( - notificationType: string, - callback: NotificationListener<T> - ): number { - try { - const notificationTypeValues: string[] = objectValues(NOTIFICATION_TYPES); - const isNotificationTypeValid = notificationTypeValues.indexOf(notificationType) > -1; - if (!isNotificationTypeValid) { - return -1; - } - - if (!this.notificationListeners[notificationType]) { - this.notificationListeners[notificationType] = []; - } - - let callbackAlreadyAdded = false; - (this.notificationListeners[notificationType] || []).forEach( - (listenerEntry) => { - if (listenerEntry.callback === callback) { - callbackAlreadyAdded = true; - return; - } - }); - - if (callbackAlreadyAdded) { - return -1; - } - - this.notificationListeners[notificationType].push({ - id: this.listenerId, - callback: callback, - }); - - const returnId = this.listenerId; - this.listenerId += 1; - return returnId; - } catch (e: any) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); - return -1; - } - } - - /** - * Remove a previously added notification callback - * @param {number} listenerId ID of listener to be removed - * @returns {boolean} Returns true if the listener was found and removed, and false - * otherwise. - */ - removeNotificationListener(listenerId: number): boolean { - try { - let indexToRemove: number | undefined; - let typeToRemove: string | undefined; - - Object.keys(this.notificationListeners).some( - (notificationType) => { - const listenersForType = this.notificationListeners[notificationType]; - (listenersForType || []).every((listenerEntry, i) => { - if (listenerEntry.id === listenerId) { - indexToRemove = i; - typeToRemove = notificationType; - return false; - } - - return true; - }); - - if (indexToRemove !== undefined && typeToRemove !== undefined) { - return true; - } - - return false; - } - ); - - if (indexToRemove !== undefined && typeToRemove !== undefined) { - this.notificationListeners[typeToRemove].splice(indexToRemove, 1); - return true; - } - } catch (e: any) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); - } - - return false; - } - - /** - * Removes all previously added notification listeners, for all notification types - */ - clearAllNotificationListeners(): void { - try { - objectValues(NOTIFICATION_TYPES).forEach( - (notificationTypeEnum) => { - this.notificationListeners[notificationTypeEnum] = []; - } - ); - } catch (e: any) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); - } - } - - /** - * Remove all previously added notification listeners for the argument type - * @param {NOTIFICATION_TYPES} notificationType One of NOTIFICATION_TYPES - */ - clearNotificationListeners(notificationType: NOTIFICATION_TYPES): void { - try { - this.notificationListeners[notificationType] = []; - } catch (e: any) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); - } - } - - /** - * Fires notifications for the argument type. All registered callbacks for this type will be - * called. The notificationData object will be passed on to callbacks called. - * @param {string} notificationType One of NOTIFICATION_TYPES - * @param {Object} notificationData Will be passed to callbacks called - */ - sendNotifications<T extends ListenerPayload>( - notificationType: string, - notificationData?: T - ): void { - try { - (this.notificationListeners[notificationType] || []).forEach( - (listenerEntry) => { - const callback = listenerEntry.callback; - try { - callback(notificationData); - } catch (ex: any) { - this.logger.log( - LOG_LEVEL.ERROR, - LOG_MESSAGES.NOTIFICATION_LISTENER_EXCEPTION, - MODULE_NAME, - notificationType, - ex.message, - ); - } - } - ); - } catch (e: any) { - this.logger.log(LOG_LEVEL.ERROR, e.message); - this.errorHandler.handleError(e); - } - } -} - -/** - * Create an instance of NotificationCenter - * @param {NotificationCenterOptions} options - * @returns {NotificationCenter} An instance of NotificationCenter - */ -export function createNotificationCenter(options: NotificationCenterOptions): NotificationCenter { - return new NotificationCenter(options); -} - -export interface NotificationSender { - // TODO[OASIS-6649]: Don't use any type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - sendNotifications(notificationType: NOTIFICATION_TYPES, notificationData?: any): void -} diff --git a/packages/optimizely-sdk/lib/core/odp/index.ts b/packages/optimizely-sdk/lib/core/odp/index.ts deleted file mode 100644 index 77a5a49e1..000000000 --- a/packages/optimizely-sdk/lib/core/odp/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ \ No newline at end of file diff --git a/packages/optimizely-sdk/lib/core/odp/lru_cache/CacheElement.tests.ts b/packages/optimizely-sdk/lib/core/odp/lru_cache/CacheElement.tests.ts deleted file mode 100644 index 728026c7f..000000000 --- a/packages/optimizely-sdk/lib/core/odp/lru_cache/CacheElement.tests.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { assert } from 'chai' -import { CacheElement } from './CacheElement'; - -const sleep = async (ms: number) => { - return await new Promise(r => setTimeout(r, ms)) -} - -describe('/odp/lru_cache/CacheElement', () => { - let element: CacheElement<string> - - beforeEach(() => { - element = new CacheElement('foo') - }) - - it('should initialize a valid CacheElement', () => { - assert.exists(element) - assert.equal(element.value, 'foo') - assert.isNotNull(element.time) - assert.doesNotThrow(() => element.is_stale(0)) - }) - - it('should return false if not stale based on timeout', () => { - const timeoutLong = 1000 - assert.equal(element.is_stale(timeoutLong), false) - }) - - it('should return false if not stale because timeout is less than or equal to 0', () => { - const timeoutNone = 0 - assert.equal(element.is_stale(timeoutNone), false) - }) - - it('should return true if stale based on timeout', async () => { - await sleep(100) - const timeoutShort = 1 - assert.equal(element.is_stale(timeoutShort), true) - }) -}) \ No newline at end of file diff --git a/packages/optimizely-sdk/lib/core/odp/lru_cache/CacheElement.ts b/packages/optimizely-sdk/lib/core/odp/lru_cache/CacheElement.ts deleted file mode 100644 index a8734527f..000000000 --- a/packages/optimizely-sdk/lib/core/odp/lru_cache/CacheElement.ts +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * CacheElement represents an individual generic item within the LRUCache - */ -export class CacheElement<V> { - private _value: V | null - private _time: number - - get value(): V | null { return this._value } - get time(): number { return this._time } - - constructor(value: V | null = null) { - this._value = value - this._time = Date.now() - } - - public is_stale(timeout: number): boolean { - if (timeout <= 0) return false - return Date.now() - this._time >= timeout - } -} - -export default CacheElement \ No newline at end of file diff --git a/packages/optimizely-sdk/lib/core/odp/lru_cache/LRUCache.tests.ts b/packages/optimizely-sdk/lib/core/odp/lru_cache/LRUCache.tests.ts deleted file mode 100644 index 9f888e598..000000000 --- a/packages/optimizely-sdk/lib/core/odp/lru_cache/LRUCache.tests.ts +++ /dev/null @@ -1,307 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { assert } from 'chai' -import { LRUCache, ClientLRUCache, ServerLRUCache } from './LRUCache' - -const sleep = async (ms: number) => { - return await new Promise(r => setTimeout(r, ms)) -} - -describe('/lib/core/odp/lru_cache (Default)', () => { - let cache: LRUCache<unknown, unknown>; - - describe('LRU Cache > Initialization', () => { - it('should successfully create a new cache with maxSize > 0 and timeout > 0', () => { - cache = new LRUCache({ - maxSize: 1000, - timeout: 2000 - }) - - assert.exists(cache) - - assert.equal(cache.maxSize, 1000) - assert.equal(cache.timeout, 2000) - }) - - it('should successfully create a new cache with maxSize == 0 and timeout == 0', () => { - cache = new LRUCache({ - maxSize: 0, - timeout: 0 - }) - - assert.exists(cache) - - assert.equal(cache.maxSize, 0) - assert.equal(cache.timeout, 0) - }) - }) - - describe('LRU Cache > Save & Lookup', () => { - const maxCacheSize = 2 - - beforeEach(() => { - cache = new LRUCache({ - maxSize: maxCacheSize, - timeout: 1000 - }) - }) - - it('should have no values in the cache upon initialization', () => { - assert.isNull(cache.peek(1)) - }) - - it('should save keys and values of any valid type', () => { - cache.save({ key: 'a', value: 1 }) // { a: 1 } - assert.equal(cache.peek('a'), 1) - - cache.save({ key: 2, value: 'b' }) // { a: 1, 2: 'b' } - assert.equal(cache.peek(2), 'b') - - const foo = Symbol('foo') - const bar = {} - cache.save({ key: foo, value: bar }) // { 2: 'b', Symbol('foo'): {} } - assert.deepEqual({}, cache.peek(foo)) - }) - - it('should save values up to its maxSize', () => { - cache.save({ key: 'a', value: 1 }) // { a: 1 } - assert.equal(cache.peek('a'), 1) - - cache.save({ key: 'b', value: 2 }) // { a: 1, b: 2 } - assert.equal(cache.peek('a'), 1) - assert.equal(cache.peek('b'), 2) - - cache.save({ key: 'c', value: 3 }) // { b: 2, c: 3 } - assert.equal(cache.peek('a'), null) - assert.equal(cache.peek('b'), 2) - assert.equal(cache.peek('c'), 3) - }) - - it('should override values of matching keys when saving', () => { - cache.save({ key: 'a', value: 1 }) // { a: 1 } - assert.equal(cache.peek('a'), 1) - - cache.save({ key: 'a', value: 2 }) // { a: 2 } - assert.equal(cache.peek('a'), 2) - - cache.save({ key: 'a', value: 3 }) // { a: 3 } - assert.equal(cache.peek('a'), 3) - }) - - it('should update cache accordingly when using lookup/peek', () => { - assert.isNull(cache.lookup(3)) - - cache.save({ key: 'b', value: 201 }) // { b: 201 } - cache.save({ key: 'a', value: 101 }) // { b: 201, a: 101 } - - assert.equal(cache.lookup('b'), 201) // { a: 101, b: 201 } - - cache.save({ key: 'c', value: 302 }) // { b: 201, c: 302 } - - assert.isNull(cache.peek(1)) - assert.equal(cache.peek('b'), 201) - assert.equal(cache.peek('c'), 302) - assert.equal(cache.lookup('c'), 302) // { b: 201, c: 302 } - - cache.save({ key: 'a', value: 103 }) // { c: 302, a: 103 } - assert.equal(cache.peek('a'), 103) - assert.isNull(cache.peek('b')) - assert.equal(cache.peek('c'), 302) - }) - }) - - describe('LRU Cache > Size', () => { - it('should keep LRU Cache map size capped at cache.capacity', () => { - const maxCacheSize = 2 - - cache = new LRUCache({ - maxSize: maxCacheSize, - timeout: 1000 - }) - - cache.save({ key: 'a', value: 1 }) // { a: 1 } - cache.save({ key: 'b', value: 2 }) // { a: 1, b: 2 } - - assert.equal(cache.map.size, maxCacheSize) - assert.equal(cache.map.size, cache.maxSize) - }) - - it('should not save to cache if maxSize is 0', () => { - cache = new LRUCache({ - maxSize: 0, - timeout: 1000 - }) - - assert.isNull(cache.lookup('a')) - cache.save({ key: 'a', value: 100 }) - assert.isNull(cache.lookup('a')) - }) - - it('should not save to cache if maxSize is negative', () => { - cache = new LRUCache({ - maxSize: -500, - timeout: 1000 - }) - - assert.isNull(cache.lookup('a')) - cache.save({ key: 'a', value: 100 }) - assert.isNull(cache.lookup('a')) - }) - }) - - describe('LRU Cache > Timeout', () => { - it('should discard stale entries in the cache on peek/lookup when timeout is greater than 0', async () => { - const maxTimeout = 100 - - cache = new LRUCache({ - maxSize: 1000, - timeout: maxTimeout - }) - - cache.save({ key: 'a', value: 100 }) // { a: 100 } - cache.save({ key: 'b', value: 200 }) // { a: 100, b: 200 } - cache.save({ key: 'c', value: 300 }) // { a: 100, b: 200, c: 300 } - - assert.equal(cache.peek('a'), 100) - assert.equal(cache.peek('b'), 200) - assert.equal(cache.peek('c'), 300) - - await sleep(150) - - assert.isNull(cache.lookup('a')) - assert.isNull(cache.lookup('b')) - assert.isNull(cache.lookup('c')) - - cache.save({ key: 'd', value: 400 }) // { d: 400 } - cache.save({ key: 'a', value: 101 }) // { d: 400, a: 101 } - - assert.equal(cache.lookup('a'), 101) // { d: 400, a: 101 } - assert.equal(cache.lookup('d'), 400) // { a: 101, d: 400 } - }) - - it('should never have stale entries if timeout is 0', async () => { - const maxTimeout = 0 - - cache = new LRUCache({ - maxSize: 1000, - timeout: maxTimeout - }) - - cache.save({ key: 'a', value: 100 }) // { a: 100 } - cache.save({ key: 'b', value: 200 }) // { a: 100, b: 200 } - - await sleep(100) - assert.equal(cache.lookup('a'), 100) - assert.equal(cache.lookup('b'), 200) - }) - - it('should never have stale entries if timeout is less than 0', async () => { - const maxTimeout = -500 - - cache = new LRUCache({ - maxSize: 1000, - timeout: maxTimeout - }) - - cache.save({ key: 'a', value: 100 }) // { a: 100 } - cache.save({ key: 'b', value: 200 }) // { a: 100, b: 200 } - - await sleep(100) - assert.equal(cache.lookup('a'), 100) - assert.equal(cache.lookup('b'), 200) - }) - }) - - describe('LRU Cache > Reset', () => { - it('should be able to reset the cache', async () => { - cache = new LRUCache({ maxSize: 2, timeout: 100 }) - cache.save({ key: 'a', value: 100 }) // { a: 100 } - cache.save({ key: 'b', value: 200 }) // { a: 100, b: 200 } - - await sleep(0) - - assert.equal(cache.map.size, 2) - cache.reset() // { } - - await sleep(150) - - assert.equal(cache.map.size, 0) - - it('should be fully functional after resetting the cache', () => { - cache.save({ key: 'c', value: 300 }) // { c: 300 } - cache.save({ key: 'd', value: 400 }) // { c: 300, d: 400 } - assert.isNull(cache.peek('b')) - assert.equal(cache.peek('c'), 300) - assert.equal(cache.peek('d'), 400) - - cache.save({ key: 'a', value: 500 }) // { d: 400, a: 500 } - cache.save({ key: 'b', value: 600 }) // { a: 500, b: 600 } - assert.isNull(cache.peek('c')) - assert.equal(cache.peek('a'), 500) - assert.equal(cache.peek('b'), 600) - - const _ = cache.lookup('a') // { b: 600, a: 500 } - assert.equal(500, _) - - cache.save({ key: 'c', value: 700 }) // { a: 500, c: 700 } - assert.isNull(cache.peek('b')) - assert.equal(cache.peek('a'), 500) - assert.equal(cache.peek('c'), 700) - }) - }) - }) -}) - -describe('/lib/core/odp/lru_cache (Client)', () => { - let cache: ClientLRUCache<unknown, unknown>; - - it('should create and test the default client LRU Cache', () => { - cache = new ClientLRUCache() - assert.exists(cache) - assert.isNull(cache.lookup('a')) - assert.equal(cache.maxSize, 100) - assert.equal(cache.timeout, 600 * 1000) - - cache.save({ key: 'a', value: 100 }) - cache.save({ key: 'b', value: 200 }) - cache.save({ key: 'c', value: 300 }) - assert.equal(cache.map.size, 3) - assert.equal(cache.peek('a'), 100) - assert.equal(cache.lookup('b'), 200) - assert.deepEqual(cache.map.keys().next().value, 'a') - }) -}) - -describe('/lib/core/odp/lru_cache (Server)', () => { - let cache: ServerLRUCache<unknown, unknown>; - - it('should create and test the default server LRU Cache', () => { - cache = new ServerLRUCache() - assert.exists(cache) - assert.isNull(cache.lookup('a')) - assert.equal(cache.maxSize, 10000) - assert.equal(cache.timeout, 600 * 1000) - - cache.save({ key: 'a', value: 100 }) - cache.save({ key: 'b', value: 200 }) - cache.save({ key: 'c', value: 300 }) - assert.equal(cache.map.size, 3) - assert.equal(cache.peek('a'), 100) - assert.equal(cache.lookup('b'), 200) - assert.deepEqual(cache.map.keys().next().value, 'a') - }) -}) diff --git a/packages/optimizely-sdk/lib/core/odp/lru_cache/LRUCache.ts b/packages/optimizely-sdk/lib/core/odp/lru_cache/LRUCache.ts deleted file mode 100644 index 83d474365..000000000 --- a/packages/optimizely-sdk/lib/core/odp/lru_cache/LRUCache.ts +++ /dev/null @@ -1,118 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import CacheElement from "./CacheElement" - -/** - * Least-Recently Used Cache (LRU Cache) Implementation with Generic Key-Value Pairs - * Analogous to a Map that has a specified max size and a timeout per element. - * - Removes the least-recently used element from the cache if max size exceeded. - * - Removes stale elements (entries older than their timeout) from the cache. - */ -export class LRUCache<K, V> { - private _map: Map<K, CacheElement<V>> = new Map() - private _maxSize // Defines maximum size of _map - private _timeout // Milliseconds each entry has before it becomes stale - - get map(): Map<K, CacheElement<V>> { return this._map } - get maxSize(): number { return this._maxSize } - get timeout(): number { return this._timeout } - - constructor({ maxSize, timeout }: { maxSize: number, timeout: number }) { - this._maxSize = maxSize - this._timeout = timeout - } - - /** - * Returns a valid, non-stale value from LRU Cache based on an input key. - * Additionally moves the element to the end of the cache and removes from cache if stale. - */ - public lookup(key: K): V | null { - if (this._maxSize <= 0) { return null } - - const element: CacheElement<V> | undefined = this._map.get(key) - - if (!element) return null - - if (element.is_stale(this._timeout)) { - this._map.delete(key) - return null - } - - this._map.delete(key) - this._map.set(key, element) - - return element.value - } - - /** - * Inserts/moves an input key-value pair to the end of the LRU Cache. - * Removes the least-recently used element if the cache exceeds it's maxSize. - */ - public save({ key, value }: { key: K, value: V }): void { - if (this._maxSize <= 0) return - - const element: CacheElement<V> | undefined = this._map.get(key) - if (element) this._map.delete(key) - this._map.set(key, new CacheElement(value)) - - if (this._map.size > this._maxSize) { - const firstMapEntryKey = this._map.keys().next().value - this._map.delete(firstMapEntryKey) - } - } - - /** - * Clears the LRU Cache - */ - public reset(): void { - if (this._maxSize <= 0) return - - this._map.clear() - } - - /** - * Reads value from specified key without moving elements in the LRU Cache. - * @param {K} key - */ - public peek(key: K): V | null { - if (this._maxSize <= 0) return null - - const element: CacheElement<V> | undefined = this._map.get(key) - - return element?.value ?? null - } -} - -export class ClientLRUCache<K, V> extends LRUCache<K, V> { - constructor() { - super({ - maxSize: 100, - timeout: 600 * 1000 // 600 secs - }) - } -} - -export class ServerLRUCache<K, V> extends LRUCache<K, V> { - constructor() { - super({ - maxSize: 10000, - timeout: 600 * 1000 // 600 secs - }) - } -} - -export default LRUCache \ No newline at end of file diff --git a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.tests.js b/packages/optimizely-sdk/lib/core/project_config/project_config_manager.tests.js deleted file mode 100644 index bc44e62eb..000000000 --- a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.tests.js +++ /dev/null @@ -1,457 +0,0 @@ -/** - * Copyright 2019-2020, 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import sinon from 'sinon'; -import { assert } from 'chai'; -import { cloneDeep } from 'lodash'; - -import { sprintf } from '../../utils/fns'; -import * as logging from '../../modules/logging'; -import datafileManager from '../../modules/datafile-manager/index.node'; - -import * as projectConfig from './index'; -import { ERROR_MESSAGES, LOG_MESSAGES } from '../../utils/enums'; -import testData from '../../tests/test_data'; -import * as projectConfigManager from './project_config_manager'; -import * as optimizelyConfig from '../optimizely_config'; -import * as jsonSchemaValidator from '../../utils/json_schema_validator'; -import { createHttpPollingDatafileManager } from '../../plugins/datafile_manager/http_polling_datafile_manager'; - -const logger = logging.getLogger(); - -describe('lib/core/project_config/project_config_manager', function() { - var globalStubErrorHandler; - var stubLogHandler; - beforeEach(function() { - sinon.stub(datafileManager, 'HttpPollingDatafileManager').returns({ - start: sinon.stub(), - stop: sinon.stub(), - get: sinon.stub().returns(null), - on: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns({ then: function() {} }), - }); - globalStubErrorHandler = { - handleError: sinon.stub(), - }; - logging.setErrorHandler(globalStubErrorHandler); - logging.setLogLevel('notset'); - stubLogHandler = { - log: sinon.stub(), - }; - logging.setLogHandler(stubLogHandler); - }); - - afterEach(function() { - datafileManager.HttpPollingDatafileManager.restore(); - logging.resetErrorHandler(); - logging.resetLogger(); - }); - - it('should call the error handler and fulfill onReady with an unsuccessful result if neither datafile nor sdkKey are passed into the constructor', function() { - var manager = projectConfigManager.createProjectConfigManager({}); - sinon.assert.calledOnce(globalStubErrorHandler.handleError); - var errorMessage = globalStubErrorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.DATAFILE_AND_SDK_KEY_MISSING, 'PROJECT_CONFIG_MANAGER')); - return manager.onReady().then(function(result) { - assert.include(result, { - success: false, - }); - }); - }); - - it('should call the error handler and fulfill onReady with an unsuccessful result if the datafile JSON is malformed', function() { - var invalidDatafileJSON = 'abc'; - var manager = projectConfigManager.createProjectConfigManager({ - datafile: invalidDatafileJSON, - }); - sinon.assert.calledOnce(globalStubErrorHandler.handleError); - var errorMessage = globalStubErrorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_DATAFILE_MALFORMED, 'CONFIG_VALIDATOR')); - return manager.onReady().then(function(result) { - assert.include(result, { - success: false, - }); - }); - }); - - it('should call the error handler and fulfill onReady with an unsuccessful result if the datafile is not valid', function() { - var invalidDatafile = testData.getTestProjectConfig(); - delete invalidDatafile['projectId']; - var manager = projectConfigManager.createProjectConfigManager({ - datafile: invalidDatafile, - jsonSchemaValidator: jsonSchemaValidator, - }); - sinon.assert.calledOnce(globalStubErrorHandler.handleError); - var errorMessage = globalStubErrorHandler.handleError.lastCall.args[0].message; - assert.strictEqual( - errorMessage, - sprintf(ERROR_MESSAGES.INVALID_DATAFILE, 'JSON_SCHEMA_VALIDATOR (Project Config JSON Schema)', 'projectId', 'is missing and it is required'), - ); - return manager.onReady().then(function(result) { - assert.include(result, { - success: false, - }); - }); - }); - - it('should call the error handler and fulfill onReady with an unsuccessful result if the datafile version is not supported', function() { - var manager = projectConfigManager.createProjectConfigManager({ - datafile: testData.getUnsupportedVersionConfig(), - jsonSchemaValidator: jsonSchemaValidator, - }); - sinon.assert.calledOnce(globalStubErrorHandler.handleError); - var errorMessage = globalStubErrorHandler.handleError.lastCall.args[0].message; - assert.strictEqual(errorMessage, sprintf(ERROR_MESSAGES.INVALID_DATAFILE_VERSION, 'CONFIG_VALIDATOR', '5')); - return manager.onReady().then(function(result) { - assert.include(result, { - success: false, - }); - }); - }); - - describe('skipping JSON schema validation', function() { - beforeEach(function() { - sinon.spy(jsonSchemaValidator, 'validate'); - }); - - afterEach(function() { - jsonSchemaValidator.validate.restore(); - }); - - it('should skip JSON schema validation if jsonSchemaValidator is not provided', function() { - var manager = projectConfigManager.createProjectConfigManager({ - datafile: testData.getTestProjectConfig(), - }); - sinon.assert.notCalled(jsonSchemaValidator.validate); - return manager.onReady(); - }); - - it('should not skip JSON schema validation if jsonSchemaValidator is provided', function() { - var manager = projectConfigManager.createProjectConfigManager({ - datafile: testData.getTestProjectConfig(), - jsonSchemaValidator: jsonSchemaValidator, - }); - sinon.assert.calledOnce(jsonSchemaValidator.validate); - sinon.assert.calledOnce(stubLogHandler.log); - var logMessage = stubLogHandler.log.args[0][1]; - assert.strictEqual(logMessage, sprintf(LOG_MESSAGES.VALID_DATAFILE, 'PROJECT_CONFIG')); - - return manager.onReady(); - }); - }); - - it('should return a valid datafile from getConfig and resolve onReady with a successful result', function() { - var configWithFeatures = testData.getTestProjectConfigWithFeatures(); - var manager = projectConfigManager.createProjectConfigManager({ - datafile: cloneDeep(configWithFeatures), - }); - assert.deepEqual(manager.getConfig(), projectConfig.createProjectConfig(configWithFeatures)); - return manager.onReady().then(function(result) { - assert.include(result, { - success: true, - }); - }); - }); - - it('does not call onUpdate listeners after becoming ready when constructed with a valid datafile and without sdkKey', function() { - var configWithFeatures = testData.getTestProjectConfigWithFeatures(); - var manager = projectConfigManager.createProjectConfigManager({ - datafile: configWithFeatures, - }); - var onUpdateSpy = sinon.spy(); - manager.onUpdate(onUpdateSpy); - return manager.onReady().then(function() { - sinon.assert.notCalled(onUpdateSpy); - }); - }); - - describe('with a datafile manager', function() { - it('passes the correct options to datafile manager', function() { - var config = testData.getTestProjectConfig() - let datafileOptions = { - autoUpdate: true, - updateInterval: 10000, - } - projectConfigManager.createProjectConfigManager({ - datafile: config, - sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger, config, datafileOptions), - }); - sinon.assert.calledOnce(datafileManager.HttpPollingDatafileManager); - sinon.assert.calledWithExactly( - datafileManager.HttpPollingDatafileManager, - sinon.match({ - datafile: JSON.stringify(config), - sdkKey: '12345', - autoUpdate: true, - updateInterval: 10000, - }) - ); - }); - - describe('when constructed with sdkKey and without datafile', function() { - it('updates itself when the datafile manager is ready, fulfills its onReady promise with a successful result, and then emits updates', function() { - var configWithFeatures = testData.getTestProjectConfigWithFeatures(); - datafileManager.HttpPollingDatafileManager.returns({ - start: sinon.stub(), - stop: sinon.stub(), - get: sinon.stub().returns(JSON.stringify(cloneDeep(configWithFeatures))), - on: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns(Promise.resolve()), - }); - var manager = projectConfigManager.createProjectConfigManager({ - sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger), - }); - assert.isNull(manager.getConfig()); - return manager.onReady().then(function(result) { - assert.include(result, { - success: true, - }); - assert.deepEqual(manager.getConfig(), projectConfig.createProjectConfig(configWithFeatures)); - - var nextDatafile = testData.getTestProjectConfigWithFeatures(); - nextDatafile.experiments.push({ - key: 'anotherTestExp', - status: 'Running', - forcedVariations: {}, - audienceIds: [], - layerId: '253442', - trafficAllocation: [{ entityId: '99977477477747747', endOfRange: 10000 }], - id: '1237847778', - variations: [{ key: 'variation', id: '99977477477747747' }], - }); - nextDatafile.revision = '36'; - var fakeDatafileManager = datafileManager.HttpPollingDatafileManager.getCall(0).returnValue; - fakeDatafileManager.get.returns(cloneDeep(nextDatafile)); - var updateListener = fakeDatafileManager.on.getCall(0).args[1]; - updateListener({ datafile: nextDatafile }); - assert.deepEqual(manager.getConfig(), projectConfig.createProjectConfig(nextDatafile)); - }); - }); - - it('calls onUpdate listeners after becoming ready, and after the datafile manager emits updates', function() { - datafileManager.HttpPollingDatafileManager.returns({ - start: sinon.stub(), - stop: sinon.stub(), - get: sinon.stub().returns(JSON.stringify(testData.getTestProjectConfigWithFeatures())), - on: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns(Promise.resolve()), - }); - var manager = projectConfigManager.createProjectConfigManager({ - sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger), - }); - var onUpdateSpy = sinon.spy(); - manager.onUpdate(onUpdateSpy); - return manager.onReady().then(function() { - sinon.assert.calledOnce(onUpdateSpy); - - var fakeDatafileManager = datafileManager.HttpPollingDatafileManager.getCall(0).returnValue; - var updateListener = fakeDatafileManager.on.getCall(0).args[1]; - var newDatafile = testData.getTestProjectConfigWithFeatures(); - newDatafile.revision = '36'; - fakeDatafileManager.get.returns(newDatafile); - - updateListener({ datafile: newDatafile }); - sinon.assert.calledTwice(onUpdateSpy); - }); - }); - - it('can remove onUpdate listeners using the function returned from onUpdate', function() { - datafileManager.HttpPollingDatafileManager.returns({ - start: sinon.stub(), - stop: sinon.stub(), - get: sinon.stub().returns(JSON.stringify(testData.getTestProjectConfigWithFeatures())), - on: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns(Promise.resolve()), - }); - var manager = projectConfigManager.createProjectConfigManager({ - sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger), - }); - return manager.onReady().then(function() { - var onUpdateSpy = sinon.spy(); - var unsubscribe = manager.onUpdate(onUpdateSpy); - - var fakeDatafileManager = datafileManager.HttpPollingDatafileManager.getCall(0).returnValue; - var updateListener = fakeDatafileManager.on.getCall(0).args[1]; - var newDatafile = testData.getTestProjectConfigWithFeatures(); - newDatafile.revision = '36'; - fakeDatafileManager.get.returns(newDatafile); - updateListener({ datafile: newDatafile }); - - sinon.assert.calledOnce(onUpdateSpy); - - unsubscribe(); - - newDatafile = testData.getTestProjectConfigWithFeatures(); - newDatafile.revision = '37'; - fakeDatafileManager.get.returns(newDatafile); - updateListener({ datafile: newDatafile }); - // // Should not call onUpdateSpy again since we unsubscribed - updateListener({ datafile: testData.getTestProjectConfigWithFeatures() }); - sinon.assert.calledOnce(onUpdateSpy); - }); - }); - - it('fulfills its ready promise with an unsuccessful result when the datafile manager emits an invalid datafile', function() { - var invalidDatafile = testData.getTestProjectConfig(); - delete invalidDatafile['projectId']; - datafileManager.HttpPollingDatafileManager.returns({ - start: sinon.stub(), - stop: sinon.stub(), - get: sinon.stub().returns(JSON.stringify(invalidDatafile)), - on: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns(Promise.resolve()), - }); - var manager = projectConfigManager.createProjectConfigManager({ - jsonSchemaValidator: jsonSchemaValidator, - sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger), - }); - return manager.onReady().then(function(result) { - assert.include(result, { - success: false, - }); - }); - }); - - it('fullfils its ready promise with an unsuccessful result when the datafile manager onReady promise rejects', function() { - datafileManager.HttpPollingDatafileManager.returns({ - start: sinon.stub(), - stop: sinon.stub(), - get: sinon.stub().returns(null), - on: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns(Promise.reject(new Error('Failed to become ready'))), - }); - var manager = projectConfigManager.createProjectConfigManager({ - jsonSchemaValidator: jsonSchemaValidator, - sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger), - }); - return manager.onReady().then(function(result) { - assert.include(result, { - success: false, - }); - }); - }); - - it('calls stop on its datafile manager when its stop method is called', function() { - var manager = projectConfigManager.createProjectConfigManager({ - sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger), - }); - manager.stop(); - sinon.assert.calledOnce(datafileManager.HttpPollingDatafileManager.getCall(0).returnValue.stop); - }); - - it('does not log an error message', function() { - projectConfigManager.createProjectConfigManager({ - sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger), - }); - sinon.assert.notCalled(stubLogHandler.log); - }); - }); - - describe('when constructed with sdkKey and with a valid datafile object', function() { - it('fulfills its onReady promise with a successful result, and does not call onUpdate listeners after becoming ready', function() { - datafileManager.HttpPollingDatafileManager.returns({ - start: sinon.stub(), - stop: sinon.stub(), - get: sinon.stub().returns(JSON.stringify(testData.getTestProjectConfigWithFeatures())), - on: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns(Promise.resolve()), - }); - var configWithFeatures = testData.getTestProjectConfigWithFeatures(); - var manager = projectConfigManager.createProjectConfigManager({ - datafile: configWithFeatures, - sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger, configWithFeatures), - }); - var onUpdateSpy = sinon.spy(); - manager.onUpdate(onUpdateSpy); - return manager.onReady().then(function(result) { - assert.include(result, { - success: true, - }); - // Datafile is the same as what it was constructed with, so should - // not have called update listener - sinon.assert.notCalled(onUpdateSpy); - }); - }); - }); - - describe('when constructed with sdkKey and with a valid datafile string', function() { - it('fulfills its onReady promise with a successful result, and does not call onUpdate listeners after becoming ready', function() { - datafileManager.HttpPollingDatafileManager.returns({ - start: sinon.stub(), - stop: sinon.stub(), - get: sinon.stub().returns(JSON.stringify(testData.getTestProjectConfigWithFeatures())), - on: sinon.stub().returns(function() {}), - onReady: sinon.stub().returns(Promise.resolve()), - }); - var configWithFeatures = testData.getTestProjectConfigWithFeatures(); - var manager = projectConfigManager.createProjectConfigManager({ - datafile: JSON.stringify(configWithFeatures), - sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger, JSON.stringify(configWithFeatures)), - }); - var onUpdateSpy = sinon.spy(); - manager.onUpdate(onUpdateSpy); - return manager.onReady().then(function(result) { - assert.include(result, { - success: true, - }); - // Datafile is the same as what it was constructed with, so should - // not have called update listener - sinon.assert.notCalled(onUpdateSpy); - }); - }); - }); - - describe('test caching of optimizely config', function() { - beforeEach(function() { - sinon.stub(optimizelyConfig, 'createOptimizelyConfig'); - }); - - afterEach(function() { - optimizelyConfig.createOptimizelyConfig.restore(); - }); - - it('should return the same config until revision is changed', function() { - var manager = projectConfigManager.createProjectConfigManager({ - datafile: testData.getTestProjectConfig(), - sdkKey: '12345', - datafileManager: createHttpPollingDatafileManager('12345', logger, testData.getTestProjectConfig()), - }); - // validate it should return the existing optimizely config - manager.getOptimizelyConfig(); - sinon.assert.calledOnce(optimizelyConfig.createOptimizelyConfig); - // create config with new revision - var fakeDatafileManager = datafileManager.HttpPollingDatafileManager.getCall(0).returnValue; - var updateListener = fakeDatafileManager.on.getCall(0).args[1]; - var newDatafile = testData.getTestProjectConfigWithFeatures(); - newDatafile.revision = '36'; - fakeDatafileManager.get.returns(newDatafile); - updateListener({ datafile: newDatafile }); - manager.getOptimizelyConfig(); - // verify the optimizely config is updated - sinon.assert.calledTwice(optimizelyConfig.createOptimizelyConfig); - }); - }); - }); -}); diff --git a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.ts b/packages/optimizely-sdk/lib/core/project_config/project_config_manager.ts deleted file mode 100644 index aea5951bd..000000000 --- a/packages/optimizely-sdk/lib/core/project_config/project_config_manager.ts +++ /dev/null @@ -1,274 +0,0 @@ -/** - * Copyright 2019-2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { getLogger } from '../../modules/logging'; -import { sprintf } from '../../utils/fns'; - -import { ERROR_MESSAGES } from '../../utils/enums'; -import { createOptimizelyConfig } from '../optimizely_config'; -import { - OnReadyResult, - OptimizelyConfig, - DatafileManager, -} from '../../shared_types'; -import { ProjectConfig, toDatafile, tryCreatingProjectConfig } from '../project_config'; - -const logger = getLogger(); -const MODULE_NAME = 'PROJECT_CONFIG_MANAGER'; - -interface ProjectConfigManagerConfig { - // TODO[OASIS-6649]: Don't use object type - // eslint-disable-next-line @typescript-eslint/ban-types - datafile?: string | object, - jsonSchemaValidator?: { - validate(jsonObject: unknown): boolean, - }; - sdkKey?: string, - datafileManager?: DatafileManager -} - -/** - * Return an error message derived from a thrown value. If the thrown value is - * an error, return the error's message property. Otherwise, return a default - * provided by the second argument. - * @param {Error|null} maybeError - * @param {string} defaultMessage - * @return {string} - */ -function getErrorMessage(maybeError: Error | null, defaultMessage?: string): string { - if (maybeError instanceof Error) { - return maybeError.message; - } - return defaultMessage || 'Unknown error'; -} - -/** - * ProjectConfigManager provides project config objects via its methods - * getConfig and onUpdate. It uses a DatafileManager to fetch datafiles. It is - * responsible for parsing and validating datafiles, and converting datafile - * string into project config objects. - * @param {ProjectConfigManagerConfig} config - */ -export class ProjectConfigManager { - private updateListeners: Array<(config: ProjectConfig) => void> = []; - private configObj: ProjectConfig | null = null; - private optimizelyConfigObj: OptimizelyConfig | null = null; - private readyPromise: Promise<OnReadyResult>; - public jsonSchemaValidator: { validate(jsonObject: unknown): boolean } | undefined; - public datafileManager: DatafileManager | null = null; - - constructor(config: ProjectConfigManagerConfig) { - try { - this.jsonSchemaValidator = config.jsonSchemaValidator; - - if (!config.datafile && !config.sdkKey) { - const datafileAndSdkKeyMissingError = new Error(sprintf(ERROR_MESSAGES.DATAFILE_AND_SDK_KEY_MISSING, MODULE_NAME)); - this.readyPromise = Promise.resolve({ - success: false, - reason: getErrorMessage(datafileAndSdkKeyMissingError), - }); - logger.error(datafileAndSdkKeyMissingError); - return; - } - - let handleNewDatafileException = null; - if (config.datafile) { - handleNewDatafileException = this.handleNewDatafile(config.datafile); - } - - if (config.sdkKey && config.datafileManager) { - this.datafileManager = config.datafileManager; - this.datafileManager.start(); - this.readyPromise = this.datafileManager - .onReady() - .then(this.onDatafileManagerReadyFulfill.bind(this), this.onDatafileManagerReadyReject.bind(this)); - this.datafileManager.on('update', this.onDatafileManagerUpdate.bind(this)); - } else if (this.configObj) { - this.readyPromise = Promise.resolve({ - success: true, - }); - } else { - this.readyPromise = Promise.resolve({ - success: false, - reason: getErrorMessage(handleNewDatafileException, 'Invalid datafile'), - }); - } - } catch (ex: any) { - logger.error(ex); - this.readyPromise = Promise.resolve({ - success: false, - reason: getErrorMessage(ex, 'Error in initialize'), - }); - } - } - - /** - * Respond to datafile manager's onReady promise becoming fulfilled. - * If there are validation or parse failures using the datafile provided by - * DatafileManager, ProjectConfigManager's ready promise is resolved with an - * unsuccessful result. Otherwise, ProjectConfigManager updates its own project - * config object from the new datafile, and its ready promise is resolved with a - * successful result. - */ - private onDatafileManagerReadyFulfill(): OnReadyResult { - if (this.datafileManager) { - const newDatafileError = this.handleNewDatafile(this.datafileManager.get()); - if (newDatafileError) { - return { - success: false, - reason: getErrorMessage(newDatafileError), - }; - } - return { success: true }; - } - - return { - success: false, - reason: getErrorMessage(null, 'Datafile manager is not provided'), - } - } - - /** - * Respond to datafile manager's onReady promise becoming rejected. - * When DatafileManager's onReady promise is rejected, there is no possibility - * of obtaining a datafile. In this case, ProjectConfigManager's ready promise - * is fulfilled with an unsuccessful result. - * @param {Error} err - * @returns {Object} - */ - private onDatafileManagerReadyReject(err: Error): OnReadyResult { - return { - success: false, - reason: getErrorMessage(err, 'Failed to become ready'), - }; - } - - /** - * Respond to datafile manager's update event. Attempt to update own config - * object using latest datafile from datafile manager. Call own registered - * update listeners if successful - */ - private onDatafileManagerUpdate(): void { - if (this.datafileManager) { - this.handleNewDatafile(this.datafileManager.get()); - } - } - - /** - * Handle new datafile by attemping to create a new Project Config object. If successful and - * the new config object's revision is newer than the current one, sets/updates the project config - * and optimizely config object instance variables and returns null for the error. If unsuccessful, - * the project config and optimizely config objects will not be updated, and the error is returned. - * @param {string | object} newDatafile - * @returns {Error|null} error or null - */ - // TODO[OASIS-6649]: Don't use object type - // eslint-disable-next-line @typescript-eslint/ban-types - private handleNewDatafile(newDatafile: string | object): Error | null { - const { configObj, error } = tryCreatingProjectConfig({ - datafile: newDatafile, - jsonSchemaValidator: this.jsonSchemaValidator, - logger: logger - }); - - if (error) { - logger.error(error); - } else { - const oldRevision = this.configObj ? this.configObj.revision : 'null'; - if (configObj && oldRevision !== configObj.revision) { - this.configObj = configObj; - this.optimizelyConfigObj = null; - this.updateListeners.forEach((listener) => listener(configObj)); - } - } - - return error; - } - - /** - * Returns the current project config object, or null if no project config object - * is available - * @return {ProjectConfig|null} - */ - getConfig(): ProjectConfig | null { - return this.configObj; - } - - /** - * Returns the optimizely config object or null - * @return {OptimizelyConfig|null} - */ - getOptimizelyConfig(): OptimizelyConfig | null { - if (!this.optimizelyConfigObj && this.configObj) { - this.optimizelyConfigObj = createOptimizelyConfig(this.configObj, toDatafile(this.configObj)); - } - return this.optimizelyConfigObj; - } - - /** - * Returns a Promise that fulfills when this ProjectConfigManager is ready to - * use (meaning it has a valid project config object), or has failed to become - * ready. - * - * Failure can be caused by the following: - * - At least one of sdkKey or datafile is not provided in the constructor argument - * - The provided datafile was invalid - * - The datafile provided by the datafile manager was invalid - * - The datafile manager failed to fetch a datafile - * - * The returned Promise is fulfilled with a result object containing these - * properties: - * - success (boolean): True if this instance is ready to use with a valid - * project config object, or false if it failed to - * become ready - * - reason (string=): If success is false, this is a string property with - * an explanatory message. - * @return {Promise} - */ - onReady(): Promise<OnReadyResult> { - return this.readyPromise; - } - - /** - * Add a listener for project config updates. The listener will be called - * whenever this instance has a new project config object available. - * Returns a dispose function that removes the subscription - * @param {Function} listener - * @return {Function} - */ - onUpdate(listener: (config: ProjectConfig) => void): (() => void) { - this.updateListeners.push(listener); - return () => { - const index = this.updateListeners.indexOf(listener); - if (index > -1) { - this.updateListeners.splice(index, 1); - } - }; - } - - /** - * Stop the internal datafile manager and remove all update listeners - */ - stop(): void { - if (this.datafileManager) { - this.datafileManager.stop(); - } - this.updateListeners = []; - } -} - -export function createProjectConfigManager(config: ProjectConfigManagerConfig): ProjectConfigManager { - return new ProjectConfigManager(config); -} diff --git a/packages/optimizely-sdk/lib/export_types.ts b/packages/optimizely-sdk/lib/export_types.ts deleted file mode 100644 index 17d307ae8..000000000 --- a/packages/optimizely-sdk/lib/export_types.ts +++ /dev/null @@ -1,41 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export { - UserAttributes, - OptimizelyConfig, - OptimizelyVariable, - OptimizelyVariation, - OptimizelyExperiment, - OptimizelyFeature, - OptimizelyDecisionContext, - OptimizelyForcedDecision, - EventTags, - Event, - EventDispatcher, - DatafileOptions, - OptimizelyOptions, - UserProfileService, - UserProfile, - ListenerPayload, - OptimizelyDecision, - OptimizelyUserContext, - NotificationListener, - Config, - Client, - ActivateListenerPayload, - TrackListenerPayload, - NotificationCenter -} from './shared_types' diff --git a/packages/optimizely-sdk/lib/index.browser.tests.js b/packages/optimizely-sdk/lib/index.browser.tests.js deleted file mode 100644 index 69822f46c..000000000 --- a/packages/optimizely-sdk/lib/index.browser.tests.js +++ /dev/null @@ -1,536 +0,0 @@ -/** - * Copyright 2016-2020, 2022 Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import logging from './modules/logging/logger'; - -import { assert } from 'chai'; -import sinon from 'sinon'; -import { default as eventProcessor } from './plugins/event_processor'; -import Optimizely from './optimizely'; -import testData from './tests/test_data'; -import packageJSON from '../package.json'; -import optimizelyFactory from './index.browser'; -import configValidator from './utils/config_validator'; -import eventProcessorConfigValidator from './utils/event_processor_config_validator'; - -var LocalStoragePendingEventsDispatcher = eventProcessor.LocalStoragePendingEventsDispatcher; - -describe('javascript-sdk', function () { - var clock; - beforeEach(function () { - sinon.stub(optimizelyFactory.eventDispatcher, 'dispatchEvent'); - clock = sinon.useFakeTimers(new Date()); - }); - - afterEach(function () { - optimizelyFactory.eventDispatcher.dispatchEvent.restore(); - clock.restore(); - }); - - describe('APIs', function () { - it('should expose logger, errorHandler, eventDispatcher and enums', function () { - assert.isDefined(optimizelyFactory.logging); - assert.isDefined(optimizelyFactory.logging.createLogger); - assert.isDefined(optimizelyFactory.logging.createNoOpLogger); - assert.isDefined(optimizelyFactory.errorHandler); - assert.isDefined(optimizelyFactory.eventDispatcher); - assert.isDefined(optimizelyFactory.enums); - }); - - describe('createInstance', function () { - var fakeErrorHandler = { handleError: function () { } }; - var fakeEventDispatcher = { dispatchEvent: function () { } }; - var silentLogger; - - beforeEach(function () { - silentLogger = optimizelyFactory.logging.createLogger({ - logLevel: optimizelyFactory.enums.LOG_LEVEL.INFO, - logToConsole: false, - }); - sinon.spy(console, 'error'); - sinon.stub(configValidator, 'validate'); - - global.XMLHttpRequest = sinon.useFakeXMLHttpRequest(); - - sinon.stub(LocalStoragePendingEventsDispatcher.prototype, 'sendPendingEvents'); - }); - - afterEach(function () { - LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents.restore(); - optimizelyFactory.__internalResetRetryState(); - console.error.restore(); - configValidator.validate.restore(); - delete global.XMLHttpRequest - }); - - describe('when an eventDispatcher is not passed in', function () { - it('should wrap the default eventDispatcher and invoke sendPendingEvents', function () { - var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, - errorHandler: fakeErrorHandler, - logger: silentLogger, - }); - // Invalid datafile causes onReady Promise rejection - catch this error - optlyInstance.onReady().catch(function () { }); - - sinon.assert.calledOnce(LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents); - }); - }); - - describe('when an eventDispatcher is passed in', function () { - it('should NOT wrap the default eventDispatcher and invoke sendPendingEvents', function () { - var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: silentLogger, - }); - // Invalid datafile causes onReady Promise rejection - catch this error - optlyInstance.onReady().catch(function () { }); - - sinon.assert.notCalled(LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents); - }); - }); - - it('should invoke resendPendingEvents at most once', function () { - var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, - errorHandler: fakeErrorHandler, - logger: silentLogger, - }); - // Invalid datafile causes onReady Promise rejection - catch this error - optlyInstance.onReady().catch(function () { }); - - sinon.assert.calledOnce(LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents); - - optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, - errorHandler: fakeErrorHandler, - logger: silentLogger, - }); - optlyInstance.onReady().catch(function () { }); - - sinon.assert.calledOnce(LocalStoragePendingEventsDispatcher.prototype.sendPendingEvents); - }); - - it('should not throw if the provided config is not valid', function () { - configValidator.validate.throws(new Error('Invalid config or something')); - assert.doesNotThrow(function () { - var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, - logger: silentLogger, - }); - // Invalid datafile causes onReady Promise rejection - catch this error - optlyInstance.onReady().catch(function () { }); - }); - }); - - it('should create an instance of optimizely', function () { - var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: silentLogger, - }); - // Invalid datafile causes onReady Promise rejection - catch this error - optlyInstance.onReady().catch(function () { }); - - assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '4.9.2'); - }); - - it('should set the JavaScript client engine and version', function () { - var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: silentLogger, - }); - // Invalid datafile causes onReady Promise rejection - catch this error - optlyInstance.onReady().catch(function () { }); - assert.equal('javascript-sdk', optlyInstance.clientEngine); - assert.equal(packageJSON.version, optlyInstance.clientVersion); - }); - - it('should allow passing of "react-sdk" as the clientEngine', function () { - var optlyInstance = optimizelyFactory.createInstance({ - clientEngine: 'react-sdk', - datafile: {}, - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: silentLogger, - }); - // Invalid datafile causes onReady Promise rejection - catch this error - optlyInstance.onReady().catch(function () { }); - assert.equal('react-sdk', optlyInstance.clientEngine); - }); - - it('should activate with provided event dispatcher', function () { - var optlyInstance = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), - errorHandler: fakeErrorHandler, - eventDispatcher: optimizelyFactory.eventDispatcher, - logger: silentLogger, - }); - var activate = optlyInstance.activate('testExperiment', 'testUser'); - assert.strictEqual(activate, 'control'); - }); - - it('should be able to set and get a forced variation', function () { - var optlyInstance = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), - errorHandler: fakeErrorHandler, - eventDispatcher: optimizelyFactory.eventDispatcher, - logger: silentLogger, - }); - - var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); - assert.strictEqual(didSetVariation, true); - - var variation = optlyInstance.getForcedVariation('testExperiment', 'testUser'); - assert.strictEqual(variation, 'control'); - }); - - it('should be able to set and unset a forced variation', function () { - var optlyInstance = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), - errorHandler: fakeErrorHandler, - eventDispatcher: optimizelyFactory.eventDispatcher, - logger: silentLogger, - }); - - var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); - assert.strictEqual(didSetVariation, true); - - var variation = optlyInstance.getForcedVariation('testExperiment', 'testUser'); - assert.strictEqual(variation, 'control'); - - var didSetVariation2 = optlyInstance.setForcedVariation('testExperiment', 'testUser', null); - assert.strictEqual(didSetVariation2, true); - - var variation2 = optlyInstance.getForcedVariation('testExperiment', 'testUser'); - assert.strictEqual(variation2, null); - }); - - it('should be able to set multiple experiments for one user', function () { - var optlyInstance = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), - errorHandler: fakeErrorHandler, - eventDispatcher: optimizelyFactory.eventDispatcher, - logger: silentLogger, - }); - - var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); - assert.strictEqual(didSetVariation, true); - - var didSetVariation2 = optlyInstance.setForcedVariation( - 'testExperimentLaunched', - 'testUser', - 'controlLaunched' - ); - assert.strictEqual(didSetVariation2, true); - - var variation = optlyInstance.getForcedVariation('testExperiment', 'testUser'); - assert.strictEqual(variation, 'control'); - - var variation2 = optlyInstance.getForcedVariation('testExperimentLaunched', 'testUser'); - assert.strictEqual(variation2, 'controlLaunched'); - }); - - it('should be able to set multiple experiments for one user, and unset one', function () { - var optlyInstance = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), - errorHandler: fakeErrorHandler, - eventDispatcher: optimizelyFactory.eventDispatcher, - logger: silentLogger, - }); - - var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); - assert.strictEqual(didSetVariation, true); - - var didSetVariation2 = optlyInstance.setForcedVariation( - 'testExperimentLaunched', - 'testUser', - 'controlLaunched' - ); - assert.strictEqual(didSetVariation2, true); - - var didSetVariation2 = optlyInstance.setForcedVariation('testExperimentLaunched', 'testUser', null); - assert.strictEqual(didSetVariation2, true); - - var variation = optlyInstance.getForcedVariation('testExperiment', 'testUser'); - assert.strictEqual(variation, 'control'); - - var variation2 = optlyInstance.getForcedVariation('testExperimentLaunched', 'testUser'); - assert.strictEqual(variation2, null); - }); - - it('should be able to set multiple experiments for one user, and reset one', function () { - var optlyInstance = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), - errorHandler: fakeErrorHandler, - eventDispatcher: optimizelyFactory.eventDispatcher, - logger: silentLogger, - }); - - var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); - assert.strictEqual(didSetVariation, true); - - var didSetVariation2 = optlyInstance.setForcedVariation( - 'testExperimentLaunched', - 'testUser', - 'controlLaunched' - ); - assert.strictEqual(didSetVariation2, true); - - var didSetVariation2 = optlyInstance.setForcedVariation( - 'testExperimentLaunched', - 'testUser', - 'variationLaunched' - ); - assert.strictEqual(didSetVariation2, true); - - var variation = optlyInstance.getForcedVariation('testExperiment', 'testUser'); - assert.strictEqual(variation, 'control'); - - var variation2 = optlyInstance.getForcedVariation('testExperimentLaunched', 'testUser'); - assert.strictEqual(variation2, 'variationLaunched'); - }); - - it('should override bucketing when setForcedVariation is called', function () { - var optlyInstance = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), - errorHandler: fakeErrorHandler, - eventDispatcher: optimizelyFactory.eventDispatcher, - logger: silentLogger, - }); - - var didSetVariation = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'control'); - assert.strictEqual(didSetVariation, true); - - var variation = optlyInstance.getVariation('testExperiment', 'testUser'); - assert.strictEqual(variation, 'control'); - - var didSetVariation2 = optlyInstance.setForcedVariation('testExperiment', 'testUser', 'variation'); - assert.strictEqual(didSetVariation2, true); - - var variation = optlyInstance.getVariation('testExperiment', 'testUser'); - assert.strictEqual(variation, 'variation'); - }); - - it('should override bucketing when setForcedVariation is called for a not running experiment', function () { - var optlyInstance = optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), - errorHandler: fakeErrorHandler, - eventDispatcher: optimizelyFactory.eventDispatcher, - logger: silentLogger, - }); - - var didSetVariation = optlyInstance.setForcedVariation( - 'testExperimentNotRunning', - 'testUser', - 'controlNotRunning' - ); - assert.strictEqual(didSetVariation, true); - - var variation = optlyInstance.getVariation('testExperimentNotRunning', 'testUser'); - assert.strictEqual(variation, null); - }); - - describe('when passing in logLevel', function () { - beforeEach(function () { - sinon.stub(logging, 'setLogLevel'); - }); - - afterEach(function () { - logging.setLogLevel.restore(); - }); - - it('should call logging.setLogLevel', function () { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), - logLevel: optimizelyFactory.enums.LOG_LEVEL.ERROR, - }); - sinon.assert.calledOnce(logging.setLogLevel); - sinon.assert.calledWithExactly(logging.setLogLevel, optimizelyFactory.enums.LOG_LEVEL.ERROR); - }); - }); - - describe('when passing in logger', function () { - beforeEach(function () { - sinon.stub(logging, 'setLogHandler'); - }); - - afterEach(function () { - logging.setLogHandler.restore(); - }); - - it('should call logging.setLogHandler with the supplied logger', function () { - var fakeLogger = { log: function () { } }; - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), - logger: fakeLogger, - }); - sinon.assert.calledOnce(logging.setLogHandler); - sinon.assert.calledWithExactly(logging.setLogHandler, fakeLogger); - }); - }); - - describe('event processor configuration', function () { - beforeEach(function () { - sinon.stub(eventProcessor, 'createEventProcessor'); - }); - - afterEach(function () { - eventProcessor.createEventProcessor.restore(); - }); - - it('should use default event flush interval when none is provided', function () { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: silentLogger, - }); - sinon.assert.calledWithExactly( - eventProcessor.createEventProcessor, - sinon.match({ - flushInterval: 1000, - }) - ); - }); - - describe('with an invalid flush interval', function () { - beforeEach(function () { - sinon.stub(eventProcessorConfigValidator, 'validateEventFlushInterval').returns(false); - }); - - afterEach(function () { - eventProcessorConfigValidator.validateEventFlushInterval.restore(); - }); - - it('should ignore the event flush interval and use the default instead', function () { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: silentLogger, - eventFlushInterval: ['invalid', 'flush', 'interval'], - }); - sinon.assert.calledWithExactly( - eventProcessor.createEventProcessor, - sinon.match({ - flushInterval: 1000, - }) - ); - }); - }); - - describe('with a valid flush interval', function () { - beforeEach(function () { - sinon.stub(eventProcessorConfigValidator, 'validateEventFlushInterval').returns(true); - }); - - afterEach(function () { - eventProcessorConfigValidator.validateEventFlushInterval.restore(); - }); - - it('should use the provided event flush interval', function () { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: silentLogger, - eventFlushInterval: 9000, - }); - sinon.assert.calledWithExactly( - eventProcessor.createEventProcessor, - sinon.match({ - flushInterval: 9000, - }) - ); - }); - }); - - it('should use default event batch size when none is provided', function () { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: silentLogger, - }); - sinon.assert.calledWithExactly( - eventProcessor.createEventProcessor, - sinon.match({ - batchSize: 10, - }) - ); - }); - - describe('with an invalid event batch size', function () { - beforeEach(function () { - sinon.stub(eventProcessorConfigValidator, 'validateEventBatchSize').returns(false); - }); - - afterEach(function () { - eventProcessorConfigValidator.validateEventBatchSize.restore(); - }); - - it('should ignore the event batch size and use the default instead', function () { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: silentLogger, - eventBatchSize: null, - }); - sinon.assert.calledWithExactly( - eventProcessor.createEventProcessor, - sinon.match({ - batchSize: 10, - }) - ); - }); - }); - - describe('with a valid event batch size', function () { - beforeEach(function () { - sinon.stub(eventProcessorConfigValidator, 'validateEventBatchSize').returns(true); - }); - - afterEach(function () { - eventProcessorConfigValidator.validateEventBatchSize.restore(); - }); - - it('should use the provided event batch size', function () { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: silentLogger, - eventBatchSize: 300, - }); - sinon.assert.calledWithExactly( - eventProcessor.createEventProcessor, - sinon.match({ - batchSize: 300, - }) - ); - }); - }); - }); - }); - }); -}); diff --git a/packages/optimizely-sdk/lib/index.browser.ts b/packages/optimizely-sdk/lib/index.browser.ts deleted file mode 100644 index 1c8cf4068..000000000 --- a/packages/optimizely-sdk/lib/index.browser.ts +++ /dev/null @@ -1,192 +0,0 @@ -/** - * Copyright 2016-2017, 2019-2022 Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import logHelper from './modules/logging/logger'; -import { - getLogger, - setErrorHandler, - getErrorHandler, - LogLevel, -} from './modules/logging'; -import { LocalStoragePendingEventsDispatcher } from './modules/event_processor'; -import configValidator from './utils/config_validator'; -import defaultErrorHandler from './plugins/error_handler'; -import defaultEventDispatcher from './plugins/event_dispatcher/index.browser'; -import * as enums from './utils/enums'; -import * as loggerPlugin from './plugins/logger'; -import Optimizely from './optimizely'; -import eventProcessorConfigValidator from './utils/event_processor_config_validator'; -import { createNotificationCenter } from './core/notification_center'; -import { default as eventProcessor } from './plugins/event_processor'; -import { OptimizelyDecideOption, Client, Config } from './shared_types'; -import { createHttpPollingDatafileManager } from './plugins/datafile_manager/browser_http_polling_datafile_manager'; - -const logger = getLogger(); -logHelper.setLogHandler(loggerPlugin.createLogger()); -logHelper.setLogLevel(LogLevel.INFO); - -const MODULE_NAME = 'INDEX_BROWSER'; -const DEFAULT_EVENT_BATCH_SIZE = 10; -const DEFAULT_EVENT_FLUSH_INTERVAL = 1000; // Unit is ms, default is 1s -const DEFAULT_EVENT_MAX_QUEUE_SIZE = 10000; - -let hasRetriedEvents = false; - -/** - * Creates an instance of the Optimizely class - * @param {Config} config - * @return {Client|null} the Optimizely client object - * null on error - */ -const createInstance = function(config: Config): Client | null { - try { - // TODO warn about setting per instance errorHandler / logger / logLevel - let isValidInstance = false - - if (config.errorHandler) { - setErrorHandler(config.errorHandler); - } - if (config.logger) { - logHelper.setLogHandler(config.logger); - // respect the logger's shouldLog functionality - logHelper.setLogLevel(LogLevel.NOTSET); - } - if (config.logLevel !== undefined) { - logHelper.setLogLevel(config.logLevel); - } - - try { - configValidator.validate(config); - isValidInstance = true; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (ex: any) { - logger.error(ex); - } - - let eventDispatcher; - // prettier-ignore - if (config.eventDispatcher == null) { // eslint-disable-line eqeqeq - // only wrap the event dispatcher with pending events retry if the user didnt override - eventDispatcher = new LocalStoragePendingEventsDispatcher({ - eventDispatcher: defaultEventDispatcher, - }); - - if (!hasRetriedEvents) { - eventDispatcher.sendPendingEvents(); - hasRetriedEvents = true; - } - } else { - eventDispatcher = config.eventDispatcher; - } - - let eventBatchSize = config.eventBatchSize; - let eventFlushInterval = config.eventFlushInterval; - - if (!eventProcessorConfigValidator.validateEventBatchSize(config.eventBatchSize)) { - logger.warn('Invalid eventBatchSize %s, defaulting to %s', config.eventBatchSize, DEFAULT_EVENT_BATCH_SIZE); - eventBatchSize = DEFAULT_EVENT_BATCH_SIZE; - } - if (!eventProcessorConfigValidator.validateEventFlushInterval(config.eventFlushInterval)) { - logger.warn( - 'Invalid eventFlushInterval %s, defaulting to %s', - config.eventFlushInterval, - DEFAULT_EVENT_FLUSH_INTERVAL - ); - eventFlushInterval = DEFAULT_EVENT_FLUSH_INTERVAL; - } - - const errorHandler = getErrorHandler(); - const notificationCenter = createNotificationCenter({ logger: logger, errorHandler: errorHandler }); - - const eventProcessorConfig = { - dispatcher: eventDispatcher, - flushInterval: eventFlushInterval, - batchSize: eventBatchSize, - maxQueueSize: config.eventMaxQueueSize || DEFAULT_EVENT_MAX_QUEUE_SIZE, - notificationCenter, - } - - const optimizelyOptions = { - clientEngine: enums.JAVASCRIPT_CLIENT_ENGINE, - ...config, - eventProcessor: eventProcessor.createEventProcessor(eventProcessorConfig), - logger, - errorHandler, - datafileManager: config.sdkKey ? createHttpPollingDatafileManager(config.sdkKey, logger, config.datafile, config.datafileOptions) : undefined, - notificationCenter, - isValidInstance: isValidInstance - }; - - const optimizely = new Optimizely(optimizelyOptions); - - try { - if (typeof window.addEventListener === 'function') { - const unloadEvent = 'onpagehide' in window ? 'pagehide' : 'unload'; - window.addEventListener( - unloadEvent, - () => { - optimizely.close(); - }, - false - ); - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - logger.error(enums.LOG_MESSAGES.UNABLE_TO_ATTACH_UNLOAD, MODULE_NAME, e.message); - } - - return optimizely; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - logger.error(e); - return null; - } -}; - -const __internalResetRetryState = function(): void { - hasRetriedEvents = false; -}; - -/** - * Entry point into the Optimizely Browser SDK - */ - -const setLogHandler = logHelper.setLogHandler -const setLogLevel = logHelper.setLogLevel -export { - loggerPlugin as logging, - defaultErrorHandler as errorHandler, - defaultEventDispatcher as eventDispatcher, - enums, - setLogHandler as setLogger, - setLogLevel, - createInstance, - __internalResetRetryState, - OptimizelyDecideOption, -}; - -export default { - logging: loggerPlugin, - errorHandler: defaultErrorHandler, - eventDispatcher: defaultEventDispatcher, - enums, - setLogger: setLogHandler, - setLogLevel, - createInstance, - __internalResetRetryState, - OptimizelyDecideOption, -}; - -export * from './export_types' diff --git a/packages/optimizely-sdk/lib/index.lite.tests.js b/packages/optimizely-sdk/lib/index.lite.tests.js deleted file mode 100644 index 7e642722e..000000000 --- a/packages/optimizely-sdk/lib/index.lite.tests.js +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Copyright 2021-2022 Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { assert } from 'chai'; -import sinon from 'sinon'; - -import * as enums from './utils/enums'; -import Optimizely from './optimizely'; -import * as loggerPlugin from './plugins/logger'; -import optimizelyFactory from './index.lite'; -import configValidator from './utils/config_validator'; - -describe('optimizelyFactory', function () { - describe('APIs', function () { - it('should expose logger, errorHandler, eventDispatcher and enums', function () { - assert.isDefined(optimizelyFactory.logging); - assert.isDefined(optimizelyFactory.logging.createLogger); - assert.isDefined(optimizelyFactory.logging.createNoOpLogger); - assert.isDefined(optimizelyFactory.errorHandler); - assert.isDefined(optimizelyFactory.eventDispatcher); - assert.isDefined(optimizelyFactory.enums); - }); - - describe('createInstance', function () { - var fakeErrorHandler = { handleError: function () { } }; - var fakeEventDispatcher = { dispatchEvent: function () { } }; - var fakeLogger; - - beforeEach(function () { - fakeLogger = { log: sinon.spy(), setLogLevel: sinon.spy() }; - sinon.stub(loggerPlugin, 'createLogger').returns(fakeLogger); - sinon.stub(configValidator, 'validate'); - sinon.stub(console, 'error'); - }); - - afterEach(function () { - loggerPlugin.createLogger.restore(); - configValidator.validate.restore(); - console.error.restore(); - }); - - it('should not throw if the provided config is not valid and log an error if logger is passed in', function () { - configValidator.validate.throws(new Error('Invalid config or something')); - var localLogger = loggerPlugin.createLogger({ logLevel: enums.LOG_LEVEL.INFO }); - assert.doesNotThrow(function () { - var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, - logger: localLogger, - }); - // Invalid datafile causes onReady Promise rejection - catch this - optlyInstance.onReady().catch(function () { }); - }); - sinon.assert.calledWith(localLogger.log, enums.LOG_LEVEL.ERROR); - }); - - it('should create an instance of optimizely', function () { - var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: fakeLogger, - }); - // Invalid datafile causes onReady Promise rejection - catch this - optlyInstance.onReady().catch(function () { }); - - assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '4.9.2'); - }); - }); - }); -}); diff --git a/packages/optimizely-sdk/lib/index.lite.ts b/packages/optimizely-sdk/lib/index.lite.ts deleted file mode 100644 index cba4b2f16..000000000 --- a/packages/optimizely-sdk/lib/index.lite.ts +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Copyright 2021-2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - import { - getLogger, - setErrorHandler, - getErrorHandler, - LogLevel, - setLogHandler, - setLogLevel - } from './modules/logging'; -import configValidator from './utils/config_validator'; -import defaultErrorHandler from './plugins/error_handler'; -import noOpEventDispatcher from './plugins/event_dispatcher/no_op'; -import * as enums from './utils/enums'; -import * as loggerPlugin from './plugins/logger'; -import Optimizely from './optimizely'; -import { createNotificationCenter } from './core/notification_center'; -import { createForwardingEventProcessor } from './plugins/event_processor/forwarding_event_processor'; -import { OptimizelyDecideOption, Client, ConfigLite } from './shared_types'; -import { createNoOpDatafileManager } from './plugins/datafile_manager/no_op_datafile_manager'; - -const logger = getLogger(); -setLogHandler(loggerPlugin.createLogger()); -setLogLevel(LogLevel.ERROR); - -/** - * Creates an instance of the Optimizely class - * @param {ConfigLite} config - * @return {Client|null} the Optimizely client object - * null on error - */ - const createInstance = function(config: ConfigLite): Client | null { - try { - - // TODO warn about setting per instance errorHandler / logger / logLevel - let isValidInstance = false; - - if (config.errorHandler) { - setErrorHandler(config.errorHandler); - } - if (config.logger) { - setLogHandler(config.logger); - // respect the logger's shouldLog functionality - setLogLevel(LogLevel.NOTSET); - } - if (config.logLevel !== undefined) { - setLogLevel(config.logLevel); - } - - try { - configValidator.validate(config); - isValidInstance = true; - } catch (ex: any) { - logger.error(ex); - } - - const errorHandler = getErrorHandler(); - const notificationCenter = createNotificationCenter({ logger: logger, errorHandler: errorHandler }); - const eventDispatcher = config.eventDispatcher || noOpEventDispatcher; - const eventProcessor = createForwardingEventProcessor(eventDispatcher, notificationCenter); - - const optimizelyOptions = { - clientEngine: enums.JAVASCRIPT_CLIENT_ENGINE, - ...config, - logger, - errorHandler, - datafileManager: createNoOpDatafileManager(), - eventProcessor, - notificationCenter, - isValidInstance: isValidInstance, - }; - - const optimizely = new Optimizely(optimizelyOptions); - return optimizely; - } catch (e: any) { - logger.error(e); - return null; - } -}; - -export { - loggerPlugin as logging, - defaultErrorHandler as errorHandler, - noOpEventDispatcher as eventDispatcher, - enums, - setLogHandler as setLogger, - setLogLevel, - createInstance, - OptimizelyDecideOption, -}; - -export default { - logging: loggerPlugin, - errorHandler: defaultErrorHandler, - eventDispatcher: noOpEventDispatcher, - enums, - setLogger: setLogHandler, - setLogLevel, - createInstance, - OptimizelyDecideOption, -}; - -export * from './export_types' diff --git a/packages/optimizely-sdk/lib/index.node.tests.js b/packages/optimizely-sdk/lib/index.node.tests.js deleted file mode 100644 index 52ad076b8..000000000 --- a/packages/optimizely-sdk/lib/index.node.tests.js +++ /dev/null @@ -1,202 +0,0 @@ -/** - * Copyright 2016-2020, 2022 Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { assert } from 'chai'; -import sinon from 'sinon'; -import * as eventProcessor from './plugins/event_processor'; - -import * as enums from './utils/enums'; -import Optimizely from './optimizely'; -import testData from './tests/test_data'; -import * as loggerPlugin from './plugins/logger'; -import optimizelyFactory from './index.node'; -import configValidator from './utils/config_validator'; - -describe('optimizelyFactory', function () { - describe('APIs', function () { - it('should expose logger, errorHandler, eventDispatcher and enums', function () { - assert.isDefined(optimizelyFactory.logging); - assert.isDefined(optimizelyFactory.logging.createLogger); - assert.isDefined(optimizelyFactory.logging.createNoOpLogger); - assert.isDefined(optimizelyFactory.errorHandler); - assert.isDefined(optimizelyFactory.eventDispatcher); - assert.isDefined(optimizelyFactory.enums); - }); - - describe('createInstance', function () { - var fakeErrorHandler = { handleError: function () { } }; - var fakeEventDispatcher = { dispatchEvent: function () { } }; - var fakeLogger; - - beforeEach(function () { - fakeLogger = { log: sinon.spy(), setLogLevel: sinon.spy() }; - sinon.stub(loggerPlugin, 'createLogger').returns(fakeLogger); - sinon.stub(configValidator, 'validate'); - sinon.stub(console, 'error'); - }); - - afterEach(function () { - loggerPlugin.createLogger.restore(); - configValidator.validate.restore(); - console.error.restore(); - }); - - it('should not throw if the provided config is not valid and log an error if logger is passed in', function () { - configValidator.validate.throws(new Error('Invalid config or something')); - var localLogger = loggerPlugin.createLogger({ logLevel: enums.LOG_LEVEL.INFO }); - assert.doesNotThrow(function () { - var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, - logger: localLogger, - }); - // Invalid datafile causes onReady Promise rejection - catch this - optlyInstance.onReady().catch(function () { }); - }); - sinon.assert.calledWith(localLogger.log, enums.LOG_LEVEL.ERROR); - }); - - it('should not throw if the provided config is not valid and log an error if no logger is provided', function () { - configValidator.validate.throws(new Error('Invalid config or something')); - assert.doesNotThrow(function () { - var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, - }); - // Invalid datafile causes onReady Promise rejection - catch this - optlyInstance.onReady().catch(function () { }); - }); - sinon.assert.calledOnce(console.error); - }); - - it('should create an instance of optimizely', function () { - var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: fakeLogger, - }); - // Invalid datafile causes onReady Promise rejection - catch this - optlyInstance.onReady().catch(function () { }); - - assert.instanceOf(optlyInstance, Optimizely); - assert.equal(optlyInstance.clientVersion, '4.9.2'); - }); - - describe('event processor configuration', function () { - var eventProcessorSpy; - beforeEach(function () { - eventProcessorSpy = sinon.stub(eventProcessor, 'createEventProcessor').callThrough(); - }); - - afterEach(function () { - eventProcessor.createEventProcessor.restore(); - }); - - it('should ignore invalid event flush interval and use default instead', function () { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: fakeLogger, - eventFlushInterval: ['invalid', 'flush', 'interval'], - }); - sinon.assert.calledWithExactly( - eventProcessorSpy, - sinon.match({ - flushInterval: 30000, - }) - ); - }); - - it('should use default event flush interval when none is provided', function () { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: fakeLogger, - }); - sinon.assert.calledWithExactly( - eventProcessorSpy, - sinon.match({ - flushInterval: 30000, - }) - ); - }); - - it('should use provided event flush interval when valid', function () { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: fakeLogger, - eventFlushInterval: 10000, - }); - sinon.assert.calledWithExactly( - eventProcessorSpy, - sinon.match({ - flushInterval: 10000, - }) - ); - }); - - it('should ignore invalid event batch size and use default instead', function () { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: fakeLogger, - eventBatchSize: null, - }); - sinon.assert.calledWithExactly( - eventProcessorSpy, - sinon.match({ - batchSize: 10, - }) - ); - }); - - it('should use default event batch size when none is provided', function () { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: fakeLogger, - }); - sinon.assert.calledWithExactly( - eventProcessorSpy, - sinon.match({ - batchSize: 10, - }) - ); - }); - - it('should use provided event batch size when valid', function () { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - logger: fakeLogger, - eventBatchSize: 300, - }); - sinon.assert.calledWithExactly( - eventProcessorSpy, - sinon.match({ - batchSize: 300, - }) - ); - }); - }); - }); - }); -}); diff --git a/packages/optimizely-sdk/lib/index.node.ts b/packages/optimizely-sdk/lib/index.node.ts deleted file mode 100644 index 56ba69c71..000000000 --- a/packages/optimizely-sdk/lib/index.node.ts +++ /dev/null @@ -1,153 +0,0 @@ -/**************************************************************************** - * Copyright 2016-2017, 2019-2022 Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * https://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ -import { - getLogger, - setErrorHandler, - getErrorHandler, - LogLevel, - setLogHandler, - setLogLevel -} from './modules/logging'; -import Optimizely from './optimizely'; -import * as enums from './utils/enums'; -import * as loggerPlugin from './plugins/logger'; -import configValidator from './utils/config_validator'; -import defaultErrorHandler from './plugins/error_handler'; -import defaultEventDispatcher from './plugins/event_dispatcher/index.node'; -import eventProcessorConfigValidator from './utils/event_processor_config_validator'; -import { createNotificationCenter } from './core/notification_center'; -import { createEventProcessor } from './plugins/event_processor'; -import { OptimizelyDecideOption, Client, Config } from './shared_types'; -import { createHttpPollingDatafileManager } from './plugins/datafile_manager/http_polling_datafile_manager'; - -const logger = getLogger(); -setLogLevel(LogLevel.ERROR); - -const DEFAULT_EVENT_BATCH_SIZE = 10; -const DEFAULT_EVENT_FLUSH_INTERVAL = 30000; // Unit is ms, default is 30s -const DEFAULT_EVENT_MAX_QUEUE_SIZE = 10000; - -/** - * Creates an instance of the Optimizely class - * @param {Config} config - * @return {Client|null} the Optimizely client object - * null on error - */ -const createInstance = function(config: Config): Client | null { - try { - let hasLogger = false; - let isValidInstance = false; - - // TODO warn about setting per instance errorHandler / logger / logLevel - if (config.errorHandler) { - setErrorHandler(config.errorHandler); - } - if (config.logger) { - // only set a logger in node if one is provided, by not setting we are noop-ing - hasLogger = true; - setLogHandler(config.logger); - // respect the logger's shouldLog functionality - setLogLevel(LogLevel.NOTSET); - } - if (config.logLevel !== undefined) { - setLogLevel(config.logLevel); - } - try { - configValidator.validate(config); - isValidInstance = true; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (ex: any) { - if (hasLogger) { - logger.error(ex); - } else { - console.error(ex.message); - } - } - - let eventBatchSize = config.eventBatchSize; - let eventFlushInterval = config.eventFlushInterval; - - if (!eventProcessorConfigValidator.validateEventBatchSize(config.eventBatchSize)) { - logger.warn('Invalid eventBatchSize %s, defaulting to %s', config.eventBatchSize, DEFAULT_EVENT_BATCH_SIZE); - eventBatchSize = DEFAULT_EVENT_BATCH_SIZE; - } - if (!eventProcessorConfigValidator.validateEventFlushInterval(config.eventFlushInterval)) { - logger.warn( - 'Invalid eventFlushInterval %s, defaulting to %s', - config.eventFlushInterval, - DEFAULT_EVENT_FLUSH_INTERVAL - ); - eventFlushInterval = DEFAULT_EVENT_FLUSH_INTERVAL; - } - - const errorHandler = getErrorHandler(); - const notificationCenter = createNotificationCenter({ logger: logger, errorHandler: errorHandler }); - - const eventProcessorConfig = { - dispatcher: config.eventDispatcher || defaultEventDispatcher, - flushInterval: eventFlushInterval, - batchSize: eventBatchSize, - maxQueueSize: config.eventMaxQueueSize || DEFAULT_EVENT_MAX_QUEUE_SIZE, - notificationCenter, - } - - const eventProcessor = createEventProcessor(eventProcessorConfig); - - const optimizelyOptions = { - clientEngine: enums.NODE_CLIENT_ENGINE, - ...config, - eventProcessor, - logger, - errorHandler, - datafileManager: config.sdkKey ? createHttpPollingDatafileManager(config.sdkKey, logger, config.datafile, config.datafileOptions) : undefined, - notificationCenter, - isValidInstance: isValidInstance, - }; - - return new Optimizely(optimizelyOptions); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - logger.error(e); - return null; - } -}; - -/** - * Entry point into the Optimizely Node testing SDK - */ -export { - loggerPlugin as logging, - defaultErrorHandler as errorHandler, - defaultEventDispatcher as eventDispatcher, - enums, - setLogHandler as setLogger, - setLogLevel, - createInstance, - OptimizelyDecideOption, -}; - -export default { - logging: loggerPlugin, - errorHandler: defaultErrorHandler, - eventDispatcher: defaultEventDispatcher, - enums, - setLogger: setLogHandler, - setLogLevel, - createInstance, - OptimizelyDecideOption, -}; - -export * from './export_types' diff --git a/packages/optimizely-sdk/lib/index.react_native.ts b/packages/optimizely-sdk/lib/index.react_native.ts deleted file mode 100644 index 31dd92d45..000000000 --- a/packages/optimizely-sdk/lib/index.react_native.ts +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Copyright 2019-2022 Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { - getLogger, - setErrorHandler, - getErrorHandler, - LogLevel, - setLogHandler, - setLogLevel -} from './modules/logging'; -import * as enums from './utils/enums'; -import Optimizely from './optimizely'; -import configValidator from './utils/config_validator'; -import defaultErrorHandler from './plugins/error_handler'; -import * as loggerPlugin from './plugins/logger/index.react_native'; -import defaultEventDispatcher from './plugins/event_dispatcher/index.browser'; -import eventProcessorConfigValidator from './utils/event_processor_config_validator'; -import { createNotificationCenter } from './core/notification_center'; -import { createEventProcessor } from './plugins/event_processor/index.react_native'; -import { OptimizelyDecideOption, Client, Config } from './shared_types'; -import { createHttpPollingDatafileManager } from - './plugins/datafile_manager/react_native_http_polling_datafile_manager'; - -const logger = getLogger(); -setLogHandler(loggerPlugin.createLogger()); -setLogLevel(LogLevel.INFO); - -const DEFAULT_EVENT_BATCH_SIZE = 10; -const DEFAULT_EVENT_FLUSH_INTERVAL = 1000; // Unit is ms, default is 1s -const DEFAULT_EVENT_MAX_QUEUE_SIZE = 10000; - -/** - * Creates an instance of the Optimizely class - * @param {Config} config - * @return {Client|null} the Optimizely client object - * null on error - */ -const createInstance = function(config: Config): Client | null { - try { - // TODO warn about setting per instance errorHandler / logger / logLevel - let isValidInstance = false; - - if (config.errorHandler) { - setErrorHandler(config.errorHandler); - } - if (config.logger) { - setLogHandler(config.logger); - // respect the logger's shouldLog functionality - setLogLevel(LogLevel.NOTSET); - } - if (config.logLevel !== undefined) { - setLogLevel(config.logLevel); - } - - try { - configValidator.validate(config); - isValidInstance = true; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (ex: any) { - logger.error(ex); - } - - let eventBatchSize = config.eventBatchSize; - let eventFlushInterval = config.eventFlushInterval; - - if (!eventProcessorConfigValidator.validateEventBatchSize(config.eventBatchSize)) { - logger.warn('Invalid eventBatchSize %s, defaulting to %s', config.eventBatchSize, DEFAULT_EVENT_BATCH_SIZE); - eventBatchSize = DEFAULT_EVENT_BATCH_SIZE; - } - if (!eventProcessorConfigValidator.validateEventFlushInterval(config.eventFlushInterval)) { - logger.warn( - 'Invalid eventFlushInterval %s, defaulting to %s', - config.eventFlushInterval, - DEFAULT_EVENT_FLUSH_INTERVAL - ); - eventFlushInterval = DEFAULT_EVENT_FLUSH_INTERVAL; - } - - const errorHandler = getErrorHandler(); - const notificationCenter = createNotificationCenter({ logger: logger, errorHandler: errorHandler }); - - const eventProcessorConfig = { - dispatcher: config.eventDispatcher || defaultEventDispatcher, - flushInterval: eventFlushInterval, - batchSize: eventBatchSize, - maxQueueSize: config.eventMaxQueueSize || DEFAULT_EVENT_MAX_QUEUE_SIZE, - notificationCenter, - } - - const eventProcessor = createEventProcessor(eventProcessorConfig); - - const optimizelyOptions = { - clientEngine: enums.REACT_NATIVE_JS_CLIENT_ENGINE, - ...config, - eventProcessor, - logger, - errorHandler, - datafileManager: config.sdkKey ? createHttpPollingDatafileManager(config.sdkKey, logger, config.datafile, config.datafileOptions) : undefined, - notificationCenter, - isValidInstance: isValidInstance, - }; - - // If client engine is react, convert it to react native. - if (optimizelyOptions.clientEngine === enums.REACT_CLIENT_ENGINE) { - optimizelyOptions.clientEngine = enums.REACT_NATIVE_CLIENT_ENGINE; - } - - return new Optimizely(optimizelyOptions); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (e: any) { - logger.error(e); - return null; - } -}; - -/** - * Entry point into the Optimizely Javascript SDK for React Native - */ -export { - loggerPlugin as logging, - defaultErrorHandler as errorHandler, - defaultEventDispatcher as eventDispatcher, - enums, - setLogHandler as setLogger, - setLogLevel, - createInstance, - OptimizelyDecideOption, -}; - -export default { - logging: loggerPlugin, - errorHandler: defaultErrorHandler, - eventDispatcher: defaultEventDispatcher, - enums, - setLogger: setLogHandler, - setLogLevel, - createInstance, - OptimizelyDecideOption, -}; - -export * from './export_types' diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/backoffController.ts b/packages/optimizely-sdk/lib/modules/datafile-manager/backoffController.ts deleted file mode 100644 index 8021f8cbd..000000000 --- a/packages/optimizely-sdk/lib/modules/datafile-manager/backoffController.ts +++ /dev/null @@ -1,46 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { BACKOFF_BASE_WAIT_SECONDS_BY_ERROR_COUNT } from './config'; - -function randomMilliseconds(): number { - return Math.round(Math.random() * 1000); -} - -export default class BackoffController { - private errorCount = 0; - - getDelay(): number { - if (this.errorCount === 0) { - return 0; - } - const baseWaitSeconds = - BACKOFF_BASE_WAIT_SECONDS_BY_ERROR_COUNT[ - Math.min(BACKOFF_BASE_WAIT_SECONDS_BY_ERROR_COUNT.length - 1, this.errorCount) - ]; - return baseWaitSeconds * 1000 + randomMilliseconds(); - } - - countError(): void { - if (this.errorCount < BACKOFF_BASE_WAIT_SECONDS_BY_ERROR_COUNT.length - 1) { - this.errorCount++; - } - } - - reset(): void { - this.errorCount = 0; - } -} diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/browserDatafileManager.ts b/packages/optimizely-sdk/lib/modules/datafile-manager/browserDatafileManager.ts deleted file mode 100644 index 84ab1b10b..000000000 --- a/packages/optimizely-sdk/lib/modules/datafile-manager/browserDatafileManager.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { makeGetRequest } from './browserRequest'; -import HttpPollingDatafileManager from './httpPollingDatafileManager'; -import { Headers, AbortableRequest } from './http'; -import { DatafileManagerConfig } from './datafileManager'; - -export default class BrowserDatafileManager extends HttpPollingDatafileManager { - protected makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest { - return makeGetRequest(reqUrl, headers); - } - - protected getConfigDefaults(): Partial<DatafileManagerConfig> { - return { - autoUpdate: false, - }; - } -} diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/browserRequest.ts b/packages/optimizely-sdk/lib/modules/datafile-manager/browserRequest.ts deleted file mode 100644 index ce47a63eb..000000000 --- a/packages/optimizely-sdk/lib/modules/datafile-manager/browserRequest.ts +++ /dev/null @@ -1,96 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { AbortableRequest, Response, Headers } from './http'; -import { REQUEST_TIMEOUT_MS } from './config'; -import { getLogger } from '../logging'; - -const logger = getLogger('DatafileManager'); - -const GET_METHOD = 'GET'; -const READY_STATE_DONE = 4; - -function parseHeadersFromXhr(req: XMLHttpRequest): Headers { - const allHeadersString = req.getAllResponseHeaders(); - - if (allHeadersString === null) { - return {}; - } - - const headerLines = allHeadersString.split('\r\n'); - const headers: Headers = {}; - headerLines.forEach(headerLine => { - const separatorIndex = headerLine.indexOf(': '); - if (separatorIndex > -1) { - const headerName = headerLine.slice(0, separatorIndex); - const headerValue = headerLine.slice(separatorIndex + 2); - if (headerValue.length > 0) { - headers[headerName] = headerValue; - } - } - }); - return headers; -} - -function setHeadersInXhr(headers: Headers, req: XMLHttpRequest): void { - Object.keys(headers).forEach(headerName => { - const header = headers[headerName]; - req.setRequestHeader(headerName, header!); - }); -} - -export function makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest { - const req = new XMLHttpRequest(); - - const responsePromise: Promise<Response> = new Promise((resolve, reject) => { - req.open(GET_METHOD, reqUrl, true); - - setHeadersInXhr(headers, req); - - req.onreadystatechange = (): void => { - if (req.readyState === READY_STATE_DONE) { - const statusCode = req.status; - if (statusCode === 0) { - reject(new Error('Request error')); - return; - } - - const headers = parseHeadersFromXhr(req); - const resp: Response = { - statusCode: req.status, - body: req.responseText, - headers, - }; - resolve(resp); - } - }; - - req.timeout = REQUEST_TIMEOUT_MS; - - req.ontimeout = (): void => { - logger.error('Request timed out'); - }; - - req.send(); - }); - - return { - responsePromise, - abort(): void { - req.abort(); - }, - }; -} diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/datafileManager.ts b/packages/optimizely-sdk/lib/modules/datafile-manager/datafileManager.ts deleted file mode 100644 index 5730786c0..000000000 --- a/packages/optimizely-sdk/lib/modules/datafile-manager/datafileManager.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import PersistentKeyValueCache from './persistentKeyValueCache'; - -export interface DatafileUpdate { - datafile: string; -} - -export interface DatafileUpdateListener { - (datafileUpdate: DatafileUpdate): void; -} - -// TODO: Replace this with the one from js-sdk-models -interface Managed { - start(): void; - - stop(): Promise<any>; -} - -export interface DatafileManager extends Managed { - get: () => string; - on: (eventName: string, listener: DatafileUpdateListener) => () => void; - onReady: () => Promise<void>; -} - -export interface DatafileManagerConfig { - autoUpdate?: boolean; - datafile?: string; - sdkKey: string; - updateInterval?: number; - urlTemplate?: string; - cache?: PersistentKeyValueCache; -} - -export interface NodeDatafileManagerConfig extends DatafileManagerConfig { - datafileAccessToken?: string; -} diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/eventEmitter.ts b/packages/optimizely-sdk/lib/modules/datafile-manager/eventEmitter.ts deleted file mode 100644 index 9cc2d8fbe..000000000 --- a/packages/optimizely-sdk/lib/modules/datafile-manager/eventEmitter.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { DatafileUpdate } from "./datafileManager"; - -export type Disposer = () => void; - -export type Listener = (arg?: any) => void; - -interface Listeners { - [index: string]: { - // index is event name - [index: string]: Listener; // index is listener id - }; -} - -export default class EventEmitter { - private listeners: Listeners = {}; - - private listenerId = 1; - - on(eventName: string, listener: Listener): Disposer { - if (!this.listeners[eventName]) { - this.listeners[eventName] = {}; - } - const currentListenerId = String(this.listenerId); - this.listenerId++; - this.listeners[eventName][currentListenerId] = listener; - return (): void => { - if (this.listeners[eventName]) { - delete this.listeners[eventName][currentListenerId]; - } - }; - } - - emit(eventName: string, arg?: DatafileUpdate): void { - const listeners = this.listeners[eventName]; - if (listeners) { - Object.keys(listeners).forEach(listenerId => { - const listener = listeners[listenerId]; - listener(arg); - }); - } - } - - removeAllListeners(): void { - this.listeners = {}; - } -} - -// TODO: Create a typed event emitter for use in TS only (not JS) diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/http.ts b/packages/optimizely-sdk/lib/modules/datafile-manager/http.ts deleted file mode 100644 index d4505dc59..000000000 --- a/packages/optimizely-sdk/lib/modules/datafile-manager/http.ts +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Headers is the interface that bridges between the abstract datafile manager and - * any Node-or-browser-specific http header types. - * It's simplified and can only store one value per header name. - * We can extend or replace this type if requirements change and we need - * to work with multiple values per header name. - */ -export interface Headers { - [header: string]: string | undefined; -} - -export interface Response { - statusCode?: number; - body: string; - headers: Headers; -} - -export interface AbortableRequest { - abort(): void; - responsePromise: Promise<Response>; -} diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/httpPollingDatafileManager.ts b/packages/optimizely-sdk/lib/modules/datafile-manager/httpPollingDatafileManager.ts deleted file mode 100644 index 7d11a14cd..000000000 --- a/packages/optimizely-sdk/lib/modules/datafile-manager/httpPollingDatafileManager.ts +++ /dev/null @@ -1,339 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getLogger } from '../logging'; -import { sprintf } from '../../../lib/utils/fns'; -import { DatafileManager, DatafileManagerConfig, DatafileUpdate } from './datafileManager'; -import EventEmitter, { Disposer } from './eventEmitter'; -import { AbortableRequest, Response, Headers } from './http'; -import { DEFAULT_UPDATE_INTERVAL, MIN_UPDATE_INTERVAL, DEFAULT_URL_TEMPLATE } from './config'; -import BackoffController from './backoffController'; -import PersistentKeyValueCache from './persistentKeyValueCache'; - -const logger = getLogger('DatafileManager'); - -const UPDATE_EVT = 'update'; - -function isValidUpdateInterval(updateInterval: number): boolean { - return updateInterval >= MIN_UPDATE_INTERVAL; -} - -function isSuccessStatusCode(statusCode: number): boolean { - return statusCode >= 200 && statusCode < 400; -} - -const noOpKeyValueCache: PersistentKeyValueCache = { - get(): Promise<string> { - return Promise.resolve(''); - }, - - set(): Promise<void> { - return Promise.resolve(); - }, - - contains(): Promise<boolean> { - return Promise.resolve(false); - }, - - remove(): Promise<void> { - return Promise.resolve(); - }, -}; - -export default abstract class HttpPollingDatafileManager implements DatafileManager { - // Make an HTTP get request to the given URL with the given headers - // Return an AbortableRequest, which has a promise for a Response. - // If we can't get a response, the promise is rejected. - // The request will be aborted if the manager is stopped while the request is in flight. - protected abstract makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest; - - // Return any default configuration options that should be applied - protected abstract getConfigDefaults(): Partial<DatafileManagerConfig>; - - private currentDatafile: string; - - private readonly readyPromise: Promise<void>; - - private isReadyPromiseSettled: boolean; - - private readyPromiseResolver: () => void; - - private readyPromiseRejecter: (err: Error) => void; - - private readonly emitter: EventEmitter; - - private readonly autoUpdate: boolean; - - private readonly updateInterval: number; - - private currentTimeout: any; - - private isStarted: boolean; - - private lastResponseLastModified?: string; - - private datafileUrl: string; - - private currentRequest: AbortableRequest | null; - - private backoffController: BackoffController; - - private cacheKey: string; - - private cache: PersistentKeyValueCache; - - // When true, this means the update interval timeout fired before the current - // sync completed. In that case, we should sync again immediately upon - // completion of the current request, instead of waiting another update - // interval. - private syncOnCurrentRequestComplete: boolean; - - constructor(config: DatafileManagerConfig) { - const configWithDefaultsApplied: DatafileManagerConfig = { - ...this.getConfigDefaults(), - ...config, - }; - const { - datafile, - autoUpdate = false, - sdkKey, - updateInterval = DEFAULT_UPDATE_INTERVAL, - urlTemplate = DEFAULT_URL_TEMPLATE, - cache = noOpKeyValueCache, - } = configWithDefaultsApplied; - - this.cache = cache; - this.cacheKey = 'opt-datafile-' + sdkKey; - this.isReadyPromiseSettled = false; - this.readyPromiseResolver = (): void => {}; - this.readyPromiseRejecter = (): void => {}; - this.readyPromise = new Promise((resolve, reject) => { - this.readyPromiseResolver = resolve; - this.readyPromiseRejecter = reject; - }); - - if (datafile) { - this.currentDatafile = datafile; - if (!sdkKey) { - this.resolveReadyPromise(); - } - } else { - this.currentDatafile = ''; - } - - this.isStarted = false; - - this.datafileUrl = sprintf(urlTemplate, sdkKey); - - this.emitter = new EventEmitter(); - this.autoUpdate = autoUpdate; - if (isValidUpdateInterval(updateInterval)) { - this.updateInterval = updateInterval; - } else { - logger.warn('Invalid updateInterval %s, defaulting to %s', updateInterval, DEFAULT_UPDATE_INTERVAL); - this.updateInterval = DEFAULT_UPDATE_INTERVAL; - } - this.currentTimeout = null; - this.currentRequest = null; - this.backoffController = new BackoffController(); - this.syncOnCurrentRequestComplete = false; - } - - get(): string { - return this.currentDatafile; - } - - start(): void { - if (!this.isStarted) { - logger.debug('Datafile manager started'); - this.isStarted = true; - this.backoffController.reset(); - this.setDatafileFromCacheIfAvailable(); - this.syncDatafile(); - } - } - - stop(): Promise<void> { - logger.debug('Datafile manager stopped'); - this.isStarted = false; - if (this.currentTimeout) { - clearTimeout(this.currentTimeout); - this.currentTimeout = null; - } - - this.emitter.removeAllListeners(); - - if (this.currentRequest) { - this.currentRequest.abort(); - this.currentRequest = null; - } - - return Promise.resolve(); - } - - onReady(): Promise<void> { - return this.readyPromise; - } - - on(eventName: string, listener: (datafileUpdate: DatafileUpdate) => void): Disposer { - return this.emitter.on(eventName, listener); - } - - private onRequestRejected(err: any): void { - if (!this.isStarted) { - return; - } - - this.backoffController.countError(); - - if (err instanceof Error) { - logger.error('Error fetching datafile: %s', err.message, err); - } else if (typeof err === 'string') { - logger.error('Error fetching datafile: %s', err); - } else { - logger.error('Error fetching datafile'); - } - } - - private onRequestResolved(response: Response): void { - if (!this.isStarted) { - return; - } - - if (typeof response.statusCode !== 'undefined' && isSuccessStatusCode(response.statusCode)) { - this.backoffController.reset(); - } else { - this.backoffController.countError(); - } - - this.trySavingLastModified(response.headers); - - const datafile = this.getNextDatafileFromResponse(response); - if (datafile !== '') { - logger.info('Updating datafile from response'); - this.currentDatafile = datafile; - this.cache.set(this.cacheKey, datafile); - if (!this.isReadyPromiseSettled) { - this.resolveReadyPromise(); - } else { - const datafileUpdate: DatafileUpdate = { - datafile, - }; - this.emitter.emit(UPDATE_EVT, datafileUpdate); - } - } - } - - private onRequestComplete(this: HttpPollingDatafileManager): void { - if (!this.isStarted) { - return; - } - - this.currentRequest = null; - - if (!this.isReadyPromiseSettled && !this.autoUpdate) { - // We will never resolve ready, so reject it - this.rejectReadyPromise(new Error('Failed to become ready')); - } - - if (this.autoUpdate && this.syncOnCurrentRequestComplete) { - this.syncDatafile(); - } - this.syncOnCurrentRequestComplete = false; - } - - private syncDatafile(): void { - const headers: Headers = {}; - if (this.lastResponseLastModified) { - headers['if-modified-since'] = this.lastResponseLastModified; - } - - logger.debug('Making datafile request to url %s with headers: %s', this.datafileUrl, () => JSON.stringify(headers)); - this.currentRequest = this.makeGetRequest(this.datafileUrl, headers); - - const onRequestComplete = (): void => { - this.onRequestComplete(); - }; - const onRequestResolved = (response: Response): void => { - this.onRequestResolved(response); - }; - const onRequestRejected = (err: any): void => { - this.onRequestRejected(err); - }; - this.currentRequest.responsePromise - .then(onRequestResolved, onRequestRejected) - .then(onRequestComplete, onRequestComplete); - - if (this.autoUpdate) { - this.scheduleNextUpdate(); - } - } - - private resolveReadyPromise(): void { - this.readyPromiseResolver(); - this.isReadyPromiseSettled = true; - } - - private rejectReadyPromise(err: Error): void { - this.readyPromiseRejecter(err); - this.isReadyPromiseSettled = true; - } - - private scheduleNextUpdate(): void { - const currentBackoffDelay = this.backoffController.getDelay(); - const nextUpdateDelay = Math.max(currentBackoffDelay, this.updateInterval); - logger.debug('Scheduling sync in %s ms', nextUpdateDelay); - this.currentTimeout = setTimeout(() => { - if (this.currentRequest) { - this.syncOnCurrentRequestComplete = true; - } else { - this.syncDatafile(); - } - }, nextUpdateDelay); - } - - private getNextDatafileFromResponse(response: Response): string { - logger.debug('Response status code: %s', response.statusCode); - if (typeof response.statusCode === 'undefined') { - return ''; - } - if (response.statusCode === 304) { - return ''; - } - if (isSuccessStatusCode(response.statusCode)) { - return response.body; - } - return ''; - } - - private trySavingLastModified(headers: Headers): void { - const lastModifiedHeader = headers['last-modified'] || headers['Last-Modified']; - if (typeof lastModifiedHeader !== 'undefined') { - this.lastResponseLastModified = lastModifiedHeader; - logger.debug('Saved last modified header value from response: %s', this.lastResponseLastModified); - } - } - - setDatafileFromCacheIfAvailable(): void { - this.cache.get(this.cacheKey).then(datafile => { - if (this.isStarted && !this.isReadyPromiseSettled && datafile !== '') { - logger.debug('Using datafile from cache'); - this.currentDatafile = datafile; - this.resolveReadyPromise(); - } - }); - } -} diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/index.browser.ts b/packages/optimizely-sdk/lib/modules/datafile-manager/index.browser.ts deleted file mode 100644 index 78a6879d5..000000000 --- a/packages/optimizely-sdk/lib/modules/datafile-manager/index.browser.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export * from './datafileManager'; -export { default as HttpPollingDatafileManager } from './browserDatafileManager'; diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/index.node.ts b/packages/optimizely-sdk/lib/modules/datafile-manager/index.node.ts deleted file mode 100644 index 4015d3adf..000000000 --- a/packages/optimizely-sdk/lib/modules/datafile-manager/index.node.ts +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import NodeDatafileManager from './nodeDatafileManager'; -export * from './datafileManager'; -export { NodeDatafileManager as HttpPollingDatafileManager }; -export default { HttpPollingDatafileManager: NodeDatafileManager }; diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/index.react_native.ts b/packages/optimizely-sdk/lib/modules/datafile-manager/index.react_native.ts deleted file mode 100644 index fa42e20ca..000000000 --- a/packages/optimizely-sdk/lib/modules/datafile-manager/index.react_native.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export * from './datafileManager'; -export { default as HttpPollingDatafileManager } from './reactNativeDatafileManager'; diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/nodeDatafileManager.ts b/packages/optimizely-sdk/lib/modules/datafile-manager/nodeDatafileManager.ts deleted file mode 100644 index d97e14920..000000000 --- a/packages/optimizely-sdk/lib/modules/datafile-manager/nodeDatafileManager.ts +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getLogger } from '../logging'; -import { makeGetRequest } from './nodeRequest'; -import HttpPollingDatafileManager from './httpPollingDatafileManager'; -import { Headers, AbortableRequest } from './http'; -import { NodeDatafileManagerConfig, DatafileManagerConfig } from './datafileManager'; -import { DEFAULT_URL_TEMPLATE, DEFAULT_AUTHENTICATED_URL_TEMPLATE } from './config'; - -const logger = getLogger('NodeDatafileManager'); - -export default class NodeDatafileManager extends HttpPollingDatafileManager { - private accessToken?: string; - - constructor(config: NodeDatafileManagerConfig) { - const defaultUrlTemplate = config.datafileAccessToken ? DEFAULT_AUTHENTICATED_URL_TEMPLATE : DEFAULT_URL_TEMPLATE; - super({ - ...config, - urlTemplate: config.urlTemplate || defaultUrlTemplate, - }); - this.accessToken = config.datafileAccessToken; - } - - protected makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest { - const requestHeaders = Object.assign({}, headers); - if (this.accessToken) { - logger.debug('Adding Authorization header with Bearer Token'); - requestHeaders['Authorization'] = `Bearer ${this.accessToken}`; - } - return makeGetRequest(reqUrl, requestHeaders); - } - - protected getConfigDefaults(): Partial<DatafileManagerConfig> { - return { - autoUpdate: true, - }; - } -} diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/nodeRequest.ts b/packages/optimizely-sdk/lib/modules/datafile-manager/nodeRequest.ts deleted file mode 100644 index 24ceed0e1..000000000 --- a/packages/optimizely-sdk/lib/modules/datafile-manager/nodeRequest.ts +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import http from 'http'; -import https from 'https'; -import url from 'url'; -import { Headers, AbortableRequest, Response } from './http'; -import { REQUEST_TIMEOUT_MS } from './config'; -import decompressResponse from 'decompress-response'; - -// Shared signature between http.request and https.request -type ClientRequestCreator = (options: http.RequestOptions) => http.ClientRequest; - -function getRequestOptionsFromUrl(url: url.UrlWithStringQuery): http.RequestOptions { - return { - hostname: url.hostname, - path: url.path, - port: url.port, - protocol: url.protocol, - }; -} - -/** - * Convert incomingMessage.headers (which has type http.IncomingHttpHeaders) into our Headers type defined in src/http.ts. - * - * Our Headers type is simplified and can't represent mutliple values for the same header name. - * - * We don't currently need multiple values support, and the consumer code becomes simpler if it can assume at-most 1 value - * per header name. - * - */ -function createHeadersFromNodeIncomingMessage(incomingMessage: http.IncomingMessage): Headers { - const headers: Headers = {}; - Object.keys(incomingMessage.headers).forEach(headerName => { - const headerValue = incomingMessage.headers[headerName]; - if (typeof headerValue === 'string') { - headers[headerName] = headerValue; - } else if (typeof headerValue === 'undefined') { - // no value provided for this header - } else { - // array - if (headerValue.length > 0) { - // We don't care about multiple values - just take the first one - headers[headerName] = headerValue[0]; - } - } - }); - return headers; -} - -function getResponseFromRequest(request: http.ClientRequest): Promise<Response> { - // TODO: When we drop support for Node 6, consider using util.promisify instead of - // constructing own Promise - return new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - request.abort(); - reject(new Error('Request timed out')); - }, REQUEST_TIMEOUT_MS); - - request.once('response', (incomingMessage: http.IncomingMessage) => { - if (request.aborted) { - return; - } - - const response = decompressResponse(incomingMessage); - - response.setEncoding('utf8'); - - let responseData = ''; - response.on('data', (chunk: string) => { - if (!request.aborted) { - responseData += chunk; - } - }); - - response.on('end', () => { - if (request.aborted) { - return; - } - - clearTimeout(timeout); - - resolve({ - statusCode: incomingMessage.statusCode, - body: responseData, - headers: createHeadersFromNodeIncomingMessage(incomingMessage), - }); - }); - }); - - request.on('error', (err: any) => { - clearTimeout(timeout); - - if (err instanceof Error) { - reject(err); - } else if (typeof err === 'string') { - reject(new Error(err)); - } else { - reject(new Error('Request error')); - } - }); - }); -} - -export function makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest { - // TODO: Use non-legacy URL parsing when we drop support for Node 6 - const parsedUrl = url.parse(reqUrl); - - let requester: ClientRequestCreator; - if (parsedUrl.protocol === 'http:') { - requester = http.request; - } else if (parsedUrl.protocol === 'https:') { - requester = https.request; - } else { - return { - responsePromise: Promise.reject(new Error(`Unsupported protocol: ${parsedUrl.protocol}`)), - abort(): void {}, - }; - } - - const requestOptions: http.RequestOptions = { - ...getRequestOptionsFromUrl(parsedUrl), - method: 'GET', - headers: { - ...headers, - 'accept-encoding': 'gzip,deflate', - }, - }; - - const request = requester(requestOptions); - const responsePromise = getResponseFromRequest(request); - - request.end(); - - return { - abort(): void { - request.abort(); - }, - responsePromise, - }; -} diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/persistentKeyValueCache.ts b/packages/optimizely-sdk/lib/modules/datafile-manager/persistentKeyValueCache.ts deleted file mode 100644 index c7fdb2660..000000000 --- a/packages/optimizely-sdk/lib/modules/datafile-manager/persistentKeyValueCache.ts +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * An Interface to implement a persistent key value cache which supports strings as keys and values. - */ -export default interface PersistentKeyValueCache { - /** - * Returns value stored against a key or null if not found. - * @param key - * @returns - * Resolves promise with - * 1. string if value found was stored as a string. - * 2. null if the key does not exist in the cache. - * Rejects the promise in case of an error - */ - get(key: string): Promise<string>; - - /** - * Stores string in the persistent cache against a key - * @param key - * @param val - * @returns - * Resolves promise without a value if successful - * Rejects the promise in case of an error - */ - set(key: string, val: string): Promise<void>; - - /** - * Checks if a key exists in the cache - * @param key - * Resolves promise with - * 1. true if the key exists - * 2. false if the key does not exist - * Rejects the promise in case of an error - */ - contains(key: string): Promise<boolean>; - - /** - * Removes the key value pair from cache. - * @param key - * Resolves promise without a value if successful - * Rejects the promise in case of an error - */ - remove(key: string): Promise<void>; -} diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/reactNativeAsyncStorageCache.ts b/packages/optimizely-sdk/lib/modules/datafile-manager/reactNativeAsyncStorageCache.ts deleted file mode 100644 index 612dd1ec2..000000000 --- a/packages/optimizely-sdk/lib/modules/datafile-manager/reactNativeAsyncStorageCache.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import AsyncStorage from '@react-native-async-storage/async-storage'; -import PersistentKeyValueCache from './persistentKeyValueCache'; - -export default class ReactNativeAsyncStorageCache implements PersistentKeyValueCache { - get(key: string): Promise<string> { - return AsyncStorage.getItem(key).then((val: string | null) => { - if (!val) { - return ''; - } - return val; - }); - } - - set(key: string, val: string): Promise<void> { - return AsyncStorage.setItem(key, val); - } - - contains(key: string): Promise<boolean> { - return AsyncStorage.getItem(key).then((val: string | null) => val !== null); - } - - remove(key: string): Promise<void> { - return AsyncStorage.removeItem(key); - } -} diff --git a/packages/optimizely-sdk/lib/modules/datafile-manager/reactNativeDatafileManager.ts b/packages/optimizely-sdk/lib/modules/datafile-manager/reactNativeDatafileManager.ts deleted file mode 100644 index 04b7eca4a..000000000 --- a/packages/optimizely-sdk/lib/modules/datafile-manager/reactNativeDatafileManager.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { makeGetRequest } from './browserRequest'; -import HttpPollingDatafileManager from './httpPollingDatafileManager'; -import { Headers, AbortableRequest } from './http'; -import { DatafileManagerConfig } from './datafileManager'; -import ReactNativeAsyncStorageCache from './reactNativeAsyncStorageCache'; - -export default class ReactNativeDatafileManager extends HttpPollingDatafileManager { - protected makeGetRequest(reqUrl: string, headers: Headers): AbortableRequest { - return makeGetRequest(reqUrl, headers); - } - - protected getConfigDefaults(): Partial<DatafileManagerConfig> { - return { - autoUpdate: true, - cache: new ReactNativeAsyncStorageCache(), - }; - } -} diff --git a/packages/optimizely-sdk/lib/modules/event_processor/eventProcessor.ts b/packages/optimizely-sdk/lib/modules/event_processor/eventProcessor.ts deleted file mode 100644 index 9dd91bdc6..000000000 --- a/packages/optimizely-sdk/lib/modules/event_processor/eventProcessor.ts +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -// TODO change this to use Managed from js-sdk-models when available -import { Managed } from './managed' -import { ConversionEvent, ImpressionEvent } from './events' -import { EventV1Request } from './eventDispatcher' -import { EventQueue, DefaultEventQueue, SingleEventQueue, EventQueueSink } from './eventQueue' -import { getLogger } from '../logging' -import { NOTIFICATION_TYPES } from '../../utils/enums' -import { NotificationSender } from '../../core/notification_center' - -export const DEFAULT_FLUSH_INTERVAL = 30000 // Unit is ms - default flush interval is 30s -export const DEFAULT_BATCH_SIZE = 10 - -const logger = getLogger('EventProcessor') - -export type ProcessableEvent = ConversionEvent | ImpressionEvent - -export type EventDispatchResult = { result: boolean; event: ProcessableEvent } - -export interface EventProcessor extends Managed { - process(event: ProcessableEvent): void -} - -export function validateAndGetFlushInterval(flushInterval: number): number { - if (flushInterval <= 0) { - logger.warn( - `Invalid flushInterval ${flushInterval}, defaulting to ${DEFAULT_FLUSH_INTERVAL}`, - ) - flushInterval = DEFAULT_FLUSH_INTERVAL - } - return flushInterval -} - -export function validateAndGetBatchSize(batchSize: number): number { - batchSize = Math.floor(batchSize) - if (batchSize < 1) { - logger.warn( - `Invalid batchSize ${batchSize}, defaulting to ${DEFAULT_BATCH_SIZE}`, - ) - batchSize = DEFAULT_BATCH_SIZE - } - batchSize = Math.max(1, batchSize) - return batchSize -} - -export function getQueue(batchSize: number, flushInterval: number, sink: EventQueueSink<ProcessableEvent>, batchComparator: (eventA: ProcessableEvent, eventB: ProcessableEvent) => boolean ): EventQueue<ProcessableEvent> { - let queue: EventQueue<ProcessableEvent> - if (batchSize > 1) { - queue = new DefaultEventQueue<ProcessableEvent>({ - flushInterval, - maxQueueSize: batchSize, - sink, - batchComparator, - }) - } else { - queue = new SingleEventQueue({ sink }) - } - return queue -} - -export function sendEventNotification(notificationSender: NotificationSender | undefined, event: EventV1Request): void { - if (notificationSender) { - notificationSender.sendNotifications( - NOTIFICATION_TYPES.LOG_EVENT, - event, - ) - } -} diff --git a/packages/optimizely-sdk/lib/modules/event_processor/eventQueue.ts b/packages/optimizely-sdk/lib/modules/event_processor/eventQueue.ts deleted file mode 100644 index a61d37918..000000000 --- a/packages/optimizely-sdk/lib/modules/event_processor/eventQueue.ts +++ /dev/null @@ -1,159 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { getLogger } from '../logging' -// TODO change this to use Managed from js-sdk-models when available -import { Managed } from './managed' - -const logger = getLogger('EventProcessor') - -export type EventQueueSink<K> = (buffer: K[]) => Promise<any> - -export interface EventQueue<K> extends Managed { - enqueue(event: K): void -} - -export interface EventQueueFactory<K> { - createEventQueue(config: { - sink: EventQueueSink<K> - flushInterval: number - maxQueueSize: number - }): EventQueue<K> -} - -class Timer { - private timeout: number - private callback: () => void - private timeoutId?: number - - constructor({ timeout, callback }: { timeout: number; callback: () => void }) { - this.timeout = Math.max(timeout, 0) - this.callback = callback - } - - start(): void { - this.timeoutId = setTimeout(this.callback, this.timeout) as any - } - - refresh(): void { - this.stop() - this.start() - } - - stop(): void { - if (this.timeoutId) { - clearTimeout(this.timeoutId as any) - } - } -} - -export class SingleEventQueue<K> implements EventQueue<K> { - private sink: EventQueueSink<K> - - constructor({ sink }: { sink: EventQueueSink<K> }) { - this.sink = sink - } - - start(): void { - // no-op - } - - stop(): Promise<any> { - // no-op - return Promise.resolve() - } - - enqueue(event: K): void { - this.sink([event]) - } -} - -export class DefaultEventQueue<K> implements EventQueue<K> { - // expose for testing - public timer: Timer - private buffer: K[] - private maxQueueSize: number - private sink: EventQueueSink<K> - // batchComparator is called to determine whether two events can be included - // together in the same batch - private batchComparator: (eventA: K, eventB: K) => boolean - private started: boolean - - constructor({ - flushInterval, - maxQueueSize, - sink, - batchComparator, - }: { - flushInterval: number - maxQueueSize: number - sink: EventQueueSink<K> - batchComparator: (eventA: K, eventB: K) => boolean - }) { - this.buffer = [] - this.maxQueueSize = Math.max(maxQueueSize, 1) - this.sink = sink - this.batchComparator = batchComparator - this.timer = new Timer({ - callback: this.flush.bind(this), - timeout: flushInterval, - }) - this.started = false - } - - start(): void { - this.started = true - // dont start the timer until the first event is enqueued - } - - stop(): Promise<any> { - this.started = false - const result = this.sink(this.buffer) - this.buffer = [] - this.timer.stop() - return result - } - - enqueue(event: K): void { - if (!this.started) { - logger.warn('Queue is stopped, not accepting event') - return - } - - // If new event cannot be included into the current batch, flush so it can - // be in its own new batch. - const bufferedEvent: K | undefined = this.buffer[0] - if (bufferedEvent && !this.batchComparator(bufferedEvent, event)) { - this.flush() - } - - // start the timer when the first event is put in - if (this.buffer.length === 0) { - this.timer.refresh() - } - this.buffer.push(event) - - if (this.buffer.length >= this.maxQueueSize) { - this.flush() - } - } - - flush() : void { - this.sink(this.buffer) - this.buffer = [] - this.timer.stop() - } -} diff --git a/packages/optimizely-sdk/lib/modules/event_processor/events.ts b/packages/optimizely-sdk/lib/modules/event_processor/events.ts deleted file mode 100644 index 65cce503b..000000000 --- a/packages/optimizely-sdk/lib/modules/event_processor/events.ts +++ /dev/null @@ -1,101 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export type VisitorAttribute = { - entityId: string - key: string - value: string | number | boolean -} - -export interface BaseEvent { - type: 'impression' | 'conversion' - timestamp: number - uuid: string - - // projectConfig stuff - context: { - accountId: string - projectId: string - clientName: string - clientVersion: string - revision: string - anonymizeIP: boolean - botFiltering?: boolean - } -} - -export interface ImpressionEvent extends BaseEvent { - type: 'impression' - - user: { - id: string - attributes: VisitorAttribute[] - } - - layer: { - id: string | null - } | null - - experiment: { - id: string | null - key: string - } | null - - variation: { - id: string | null - key: string - } | null - - ruleKey: string - flagKey: string - ruleType: string - enabled: boolean -} - -export interface ConversionEvent extends BaseEvent { - type: 'conversion' - - user: { - id: string - attributes: VisitorAttribute[] - } - - event: { - id: string | null - key: string - } - - revenue: number | null - value: number | null - tags: EventTags | undefined -} - -export type EventTags = { - [key: string]: string | number | null -} - -export function areEventContextsEqual(eventA: BaseEvent, eventB: BaseEvent): boolean { - const contextA = eventA.context - const contextB = eventB.context - return ( - contextA.accountId === contextB.accountId && - contextA.projectId === contextB.projectId && - contextA.clientName === contextB.clientName && - contextA.clientVersion === contextB.clientVersion && - contextA.revision === contextB.revision && - contextA.anonymizeIP === contextB.anonymizeIP && - contextA.botFiltering === contextB.botFiltering - ) -} diff --git a/packages/optimizely-sdk/lib/modules/event_processor/index.react_native.ts b/packages/optimizely-sdk/lib/modules/event_processor/index.react_native.ts deleted file mode 100644 index 91bb29a58..000000000 --- a/packages/optimizely-sdk/lib/modules/event_processor/index.react_native.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export * from './events' -export * from './eventProcessor' -export * from './eventDispatcher' -export * from './managed' -export * from './pendingEventsDispatcher' -export * from './v1/buildEventV1' -export * from './v1/v1EventProcessor.react_native' diff --git a/packages/optimizely-sdk/lib/modules/event_processor/index.ts b/packages/optimizely-sdk/lib/modules/event_processor/index.ts deleted file mode 100644 index c4eaef01d..000000000 --- a/packages/optimizely-sdk/lib/modules/event_processor/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export * from './events' -export * from './eventProcessor' -export * from './eventDispatcher' -export * from './managed' -export * from './pendingEventsDispatcher' -export * from './v1/buildEventV1' -export * from './v1/v1EventProcessor' diff --git a/packages/optimizely-sdk/lib/modules/event_processor/pendingEventsDispatcher.ts b/packages/optimizely-sdk/lib/modules/event_processor/pendingEventsDispatcher.ts deleted file mode 100644 index 4f4c8c61b..000000000 --- a/packages/optimizely-sdk/lib/modules/event_processor/pendingEventsDispatcher.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { getLogger } from '../logging' -import { EventDispatcher, EventV1Request, EventDispatcherCallback } from './eventDispatcher' -import { PendingEventsStore, LocalStorageStore } from './pendingEventsStore' -import { uuid, getTimestamp } from '../../utils/fns' - -const logger = getLogger('EventProcessor') - -export type DispatcherEntry = { - uuid: string - timestamp: number - request: EventV1Request -} - -export class PendingEventsDispatcher implements EventDispatcher { - protected dispatcher: EventDispatcher - protected store: PendingEventsStore<DispatcherEntry> - - constructor({ - eventDispatcher, - store, - }: { - eventDispatcher: EventDispatcher - store: PendingEventsStore<DispatcherEntry> - }) { - this.dispatcher = eventDispatcher - this.store = store - } - - dispatchEvent(request: EventV1Request, callback: EventDispatcherCallback): void { - this.send( - { - uuid: uuid(), - timestamp: getTimestamp(), - request, - }, - callback, - ) - } - - sendPendingEvents(): void { - const pendingEvents = this.store.values() - - logger.debug('Sending %s pending events from previous page', pendingEvents.length) - - pendingEvents.forEach(item => { - try { - this.send(item, () => {}) - } catch (e) - { - logger.debug(String(e)) - } - }) - } - - protected send(entry: DispatcherEntry, callback: EventDispatcherCallback): void { - this.store.set(entry.uuid, entry) - - this.dispatcher.dispatchEvent(entry.request, response => { - this.store.remove(entry.uuid) - callback(response) - }) - } -} - -export class LocalStoragePendingEventsDispatcher extends PendingEventsDispatcher { - constructor({ eventDispatcher }: { eventDispatcher: EventDispatcher }) { - super({ - eventDispatcher, - store: new LocalStorageStore({ - // TODO make this configurable - maxValues: 100, - key: 'fs_optly_pending_events', - }), - }) - } -} diff --git a/packages/optimizely-sdk/lib/modules/event_processor/pendingEventsStore.ts b/packages/optimizely-sdk/lib/modules/event_processor/pendingEventsStore.ts deleted file mode 100644 index 6b5ee3393..000000000 --- a/packages/optimizely-sdk/lib/modules/event_processor/pendingEventsStore.ts +++ /dev/null @@ -1,117 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { objectValues } from '../../utils/fns' -import { getLogger } from '../logging'; - -const logger = getLogger('EventProcessor') - -export interface PendingEventsStore<K> { - get(key: string): K | null - - set(key: string, value: K): void - - remove(key: string): void - - values(): K[] - - clear(): void - - replace(newMap: { [key: string]: K }): void -} - -interface StoreEntry { - uuid: string - timestamp: number -} - -export class LocalStorageStore<K extends StoreEntry> implements PendingEventsStore<K> { - protected LS_KEY: string - protected maxValues: number - - constructor({ key, maxValues = 1000 }: { key: string; maxValues?: number }) { - this.LS_KEY = key - this.maxValues = maxValues - } - - get(key: string): K | null { - return this.getMap()[key] || null - } - - set(key: string, value: K): void { - const map = this.getMap() - map[key] = value - this.replace(map) - } - - remove(key: string): void { - const map = this.getMap() - delete map[key] - this.replace(map) - } - - values(): K[] { - return objectValues(this.getMap()) - } - - clear(): void { - this.replace({}) - } - - replace(map: { [key: string]: K }): void { - try { - // This is a temporary fix to support React Native which does not have localStorage. - typeof window !== 'undefined' ? window && window.localStorage && localStorage.setItem(this.LS_KEY, JSON.stringify(map)) : localStorage.setItem(this.LS_KEY, JSON.stringify(map)) - this.clean() - } catch (e) { - logger.error(String(e)) - } - } - - private clean() { - const map = this.getMap() - const keys = Object.keys(map) - const toRemove = keys.length - this.maxValues - if (toRemove < 1) { - return - } - - const entries = keys.map(key => ({ - key, - value: map[key] - })) - - entries.sort((a, b) => a.value.timestamp - b.value.timestamp) - - for (let i = 0; i < toRemove; i++) { - delete map[entries[i].key] - } - - this.replace(map) - } - - private getMap(): { [key: string]: K } { - try { - // This is a temporary fix to support React Native which does not have localStorage. - const data = typeof window !== 'undefined' ? window && window.localStorage && localStorage.getItem(this.LS_KEY): localStorage.getItem(this.LS_KEY); - if (data) { - return (JSON.parse(data) as { [key: string]: K }) || {} - } - } catch (e: any) { - logger.error(e) - } - return {} - } -} diff --git a/packages/optimizely-sdk/lib/modules/event_processor/persistentKeyValueCache.ts b/packages/optimizely-sdk/lib/modules/event_processor/persistentKeyValueCache.ts deleted file mode 100644 index 7dd508df9..000000000 --- a/packages/optimizely-sdk/lib/modules/event_processor/persistentKeyValueCache.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * An Interface to implement a persistent key value cache which supports strings as keys - * and JSON Object as value. - */ -export default interface PersistentKeyValueCache { - /** - * Returns value stored against a key or null if not found. - * @param key - * @returns - * Resolves promise with - * 1. Object if value found was stored as a JSON Object. - * 2. null if the key does not exist in the cache. - * Rejects the promise in case of an error - */ - get(key: string): Promise<any | null>; - - /** - * Stores Object in the persistent cache against a key - * @param key - * @param val - * @returns - * Resolves promise without a value if successful - * Rejects the promise in case of an error - */ - set(key: string, val: any): Promise<void>; - - /** - * Checks if a key exists in the cache - * @param key - * Resolves promise with - * 1. true if the key exists - * 2. false if the key does not exist - * Rejects the promise in case of an error - */ - contains(key: string): Promise<boolean>; - - /** - * Removes the key value pair from cache. - * @param key - * Resolves promise without a value if successful - * Rejects the promise in case of an error - */ - remove(key: string): Promise<void>; -} diff --git a/packages/optimizely-sdk/lib/modules/event_processor/reactNativeAsyncStorageCache.ts b/packages/optimizely-sdk/lib/modules/event_processor/reactNativeAsyncStorageCache.ts deleted file mode 100644 index 26b2ac315..000000000 --- a/packages/optimizely-sdk/lib/modules/event_processor/reactNativeAsyncStorageCache.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import AsyncStorage from '@react-native-async-storage/async-storage'; - -import PersistentKeyValueCache from './persistentKeyValueCache'; - -export default class ReactNativeAsyncStorageCache implements PersistentKeyValueCache { - get(key: string): Promise<any | null> { - return AsyncStorage.getItem(key).then((val: string | null) => { - if (!val) { - return null; - } - return JSON.parse(val); - }); - } - - /* eslint-disable */ - set(key: string, val: any): Promise<void> { - try { - return AsyncStorage.setItem(key, JSON.stringify(val)); - } catch (ex) { - return Promise.reject(ex); - } - } - /* eslint-enable */ - - contains(key: string): Promise<boolean> { - return AsyncStorage.getItem(key).then((val: string | null) => val !== null); - } - - remove(key: string): Promise<void> { - return AsyncStorage.removeItem(key); - } -} diff --git a/packages/optimizely-sdk/lib/modules/event_processor/reactNativeEventsStore.ts b/packages/optimizely-sdk/lib/modules/event_processor/reactNativeEventsStore.ts deleted file mode 100644 index d07928afa..000000000 --- a/packages/optimizely-sdk/lib/modules/event_processor/reactNativeEventsStore.ts +++ /dev/null @@ -1,81 +0,0 @@ - -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { getLogger } from '../logging' -import { objectValues } from "../../utils/fns" - -import { Synchronizer } from './synchronizer' -import ReactNativeAsyncStorageCache from './reactNativeAsyncStorageCache' - -const logger = getLogger('ReactNativeEventsStore') - -/** - * A key value store which stores objects of type T with string keys - */ -export class ReactNativeEventsStore<T> { - private maxSize: number - private storeKey: string - private synchronizer: Synchronizer = new Synchronizer() - private cache: ReactNativeAsyncStorageCache = new ReactNativeAsyncStorageCache() - - constructor(maxSize: number, storeKey: string) { - this.maxSize = maxSize - this.storeKey = storeKey - } - - public async set(key: string, event: T): Promise<string> { - await this.synchronizer.getLock() - const eventsMap: {[key: string]: T} = await this.cache.get(this.storeKey) || {} - if (Object.keys(eventsMap).length < this.maxSize) { - eventsMap[key] = event - await this.cache.set(this.storeKey, eventsMap) - } else { - logger.warn('React native events store is full. Store key: %s', this.storeKey) - } - this.synchronizer.releaseLock() - return key - } - - public async get(key: string): Promise<T> { - await this.synchronizer.getLock() - const eventsMap: {[key: string]: T} = await this.cache.get(this.storeKey) || {} - this.synchronizer.releaseLock() - return eventsMap[key] - } - - public async getEventsMap(): Promise<{[key: string]: T}> { - return await this.cache.get(this.storeKey) || {} - } - - public async getEventsList(): Promise<T[]> { - await this.synchronizer.getLock() - const eventsMap: {[key: string]: T} = await this.cache.get(this.storeKey) || {} - this.synchronizer.releaseLock() - return objectValues(eventsMap) - } - - public async remove(key: string): Promise<void> { - await this.synchronizer.getLock() - const eventsMap: {[key: string]: T} = await this.cache.get(this.storeKey) || {} - eventsMap[key] && delete eventsMap[key] - await this.cache.set(this.storeKey, eventsMap) - this.synchronizer.releaseLock() - } - - public async clear(): Promise<void> { - await this.cache.remove(this.storeKey) - } -} diff --git a/packages/optimizely-sdk/lib/modules/event_processor/requestTracker.ts b/packages/optimizely-sdk/lib/modules/event_processor/requestTracker.ts deleted file mode 100644 index ab18b36d1..000000000 --- a/packages/optimizely-sdk/lib/modules/event_processor/requestTracker.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * RequestTracker keeps track of in-flight requests for EventProcessor using - * an internal counter. It exposes methods for adding a new request to be - * tracked, and getting a Promise representing the completion of currently - * tracked requests. - */ -class RequestTracker { - private reqsInFlightCount = 0 - private reqsCompleteResolvers: Array<() => void> = [] - - /** - * Track the argument request (represented by a Promise). reqPromise will feed - * into the state of Promises returned by onRequestsComplete. - * @param {Promise<void>} reqPromise - */ - public trackRequest(reqPromise: Promise<void>): void { - this.reqsInFlightCount++ - const onReqComplete = () => { - this.reqsInFlightCount-- - if (this.reqsInFlightCount === 0) { - this.reqsCompleteResolvers.forEach(resolver => resolver()) - this.reqsCompleteResolvers = [] - } - } - reqPromise.then(onReqComplete, onReqComplete) - } - - /** - * Return a Promise that fulfills after all currently-tracked request promises - * are resolved. - * @return {Promise<void>} - */ - public onRequestsComplete(): Promise<void> { - return new Promise(resolve => { - if (this.reqsInFlightCount === 0) { - resolve() - } else { - this.reqsCompleteResolvers.push(resolve) - } - }) - } -} - -export default RequestTracker diff --git a/packages/optimizely-sdk/lib/modules/event_processor/synchronizer.ts b/packages/optimizely-sdk/lib/modules/event_processor/synchronizer.ts deleted file mode 100644 index d6bf32b7b..000000000 --- a/packages/optimizely-sdk/lib/modules/event_processor/synchronizer.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * This synchronizer makes sure the operations are atomic using promises. - */ -export class Synchronizer { - private lockPromises: Promise<void>[] = [] - private resolvers: any[] = [] - - // Adds a promise to the existing list and returns the promise so that the code block can wait for its turn - public async getLock(): Promise<void> { - this.lockPromises.push(new Promise(resolve => this.resolvers.push(resolve))) - if (this.lockPromises.length === 1) { - return - } - await this.lockPromises[this.lockPromises.length - 2] - } - - // Resolves first promise in the array so that the code block waiting on the first promise can continue execution - public releaseLock(): void { - if (this.lockPromises.length > 0) { - this.lockPromises.shift() - const resolver = this.resolvers.shift() - resolver() - return - } - } -} diff --git a/packages/optimizely-sdk/lib/modules/event_processor/v1/buildEventV1.ts b/packages/optimizely-sdk/lib/modules/event_processor/v1/buildEventV1.ts deleted file mode 100644 index 699498dc4..000000000 --- a/packages/optimizely-sdk/lib/modules/event_processor/v1/buildEventV1.ts +++ /dev/null @@ -1,272 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { EventTags, ConversionEvent, ImpressionEvent, VisitorAttribute } from '../events' -import { ProcessableEvent } from '../eventProcessor' -import { EventV1Request } from '../eventDispatcher' - -const ACTIVATE_EVENT_KEY = 'campaign_activated' -const CUSTOM_ATTRIBUTE_FEATURE_TYPE = 'custom' -const BOT_FILTERING_KEY = '$opt_bot_filtering' - -export type EventV1 = { - account_id: string - project_id: string - revision: string - client_name: string - client_version: string - anonymize_ip: boolean - enrich_decisions: boolean - visitors: Visitor[] -} - -type Visitor = { - snapshots: Visitor.Snapshot[] - visitor_id: string - attributes: Visitor.Attribute[] -} - -// eslint-disable-next-line @typescript-eslint/no-namespace -namespace Visitor { - type AttributeType = 'custom' - - export type Attribute = { - // attribute id - entity_id: string - // attribute key - key: string - type: AttributeType - value: string | number | boolean - } - - export type Snapshot = { - decisions?: Decision[] - events: SnapshotEvent[] - } - - type Decision = { - campaign_id: string | null - experiment_id: string | null - variation_id: string | null - metadata: Metadata - } - - type Metadata = { - flag_key: string; - rule_key: string; - rule_type: string; - variation_key: string; - enabled: boolean; - } - - export type SnapshotEvent = { - entity_id: string | null - timestamp: number - uuid: string - key: string - revenue?: number - value?: number - tags?: EventTags - } -} - - - -type Attributes = { - [key: string]: string | number | boolean -} - -/** - * Given an array of batchable Decision or ConversionEvent events it returns - * a single EventV1 with proper batching - * - * @param {ProcessableEvent[]} events - * @returns {EventV1} - */ -export function makeBatchedEventV1(events: ProcessableEvent[]): EventV1 { - const visitors: Visitor[] = [] - const data = events[0] - - events.forEach(event => { - if (event.type === 'conversion' || event.type === 'impression') { - const visitor = makeVisitor(event) - - if (event.type === 'impression') { - visitor.snapshots.push(makeDecisionSnapshot(event)) - } else if (event.type === 'conversion') { - visitor.snapshots.push(makeConversionSnapshot(event)) - } - - visitors.push(visitor) - } - }) - - return { - client_name: data.context.clientName, - client_version: data.context.clientVersion, - - account_id: data.context.accountId, - project_id: data.context.projectId, - revision: data.context.revision, - anonymize_ip: data.context.anonymizeIP, - enrich_decisions: true, - - visitors, - } -} - -function makeConversionSnapshot(conversion: ConversionEvent): Visitor.Snapshot { - const tags: EventTags = { - ...conversion.tags, - } - - delete tags['revenue'] - delete tags['value'] - - const event: Visitor.SnapshotEvent = { - entity_id: conversion.event.id, - key: conversion.event.key, - timestamp: conversion.timestamp, - uuid: conversion.uuid, - } - - if (conversion.tags) { - event.tags = conversion.tags - } - - if (conversion.value != null) { - event.value = conversion.value - } - - if (conversion.revenue != null) { - event.revenue = conversion.revenue - } - - return { - events: [event], - } -} - -function makeDecisionSnapshot(event: ImpressionEvent): Visitor.Snapshot { - const { layer, experiment, variation, ruleKey, flagKey, ruleType, enabled } = event - const layerId = layer ? layer.id : null - const experimentId = experiment?.id ?? '' - const variationId = variation?.id ?? '' - const variationKey = variation ? variation.key : '' - - return { - decisions: [ - { - campaign_id: layerId, - experiment_id: experimentId, - variation_id: variationId, - metadata: { - flag_key: flagKey, - rule_key: ruleKey, - rule_type: ruleType, - variation_key: variationKey, - enabled: enabled, - }, - }, - ], - events: [ - { - entity_id: layerId, - timestamp: event.timestamp, - key: ACTIVATE_EVENT_KEY, - uuid: event.uuid, - }, - ], - } -} - -function makeVisitor(data: ImpressionEvent | ConversionEvent): Visitor { - const visitor: Visitor = { - snapshots: [], - visitor_id: data.user.id, - attributes: [], - } - - const type = 'custom' - data.user.attributes.forEach(attr => { - visitor.attributes.push({ - entity_id: attr.entityId, - key: attr.key, - type: type as 'custom', // tell the compiler this is always string "custom" - value: attr.value, - }) - }) - - if (typeof data.context.botFiltering === 'boolean') { - visitor.attributes.push({ - entity_id: BOT_FILTERING_KEY, - key: BOT_FILTERING_KEY, - type: CUSTOM_ATTRIBUTE_FEATURE_TYPE, - value: data.context.botFiltering, - }) - } - return visitor -} - -/** - * Event for usage with v1 logtier - * - * @export - * @interface EventBuilderV1 - */ - -export function buildImpressionEventV1(data: ImpressionEvent): EventV1 { - const visitor = makeVisitor(data) - visitor.snapshots.push(makeDecisionSnapshot(data)) - - return { - client_name: data.context.clientName, - client_version: data.context.clientVersion, - - account_id: data.context.accountId, - project_id: data.context.projectId, - revision: data.context.revision, - anonymize_ip: data.context.anonymizeIP, - enrich_decisions: true, - - visitors: [visitor], - } -} - -export function buildConversionEventV1(data: ConversionEvent): EventV1 { - const visitor = makeVisitor(data) - visitor.snapshots.push(makeConversionSnapshot(data)) - - return { - client_name: data.context.clientName, - client_version: data.context.clientVersion, - - account_id: data.context.accountId, - project_id: data.context.projectId, - revision: data.context.revision, - anonymize_ip: data.context.anonymizeIP, - enrich_decisions: true, - - visitors: [visitor], - } -} - -export function formatEvents(events: ProcessableEvent[]): EventV1Request { - return { - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1(events), - } -} diff --git a/packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.react_native.ts b/packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.react_native.ts deleted file mode 100644 index cc690dbee..000000000 --- a/packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.react_native.ts +++ /dev/null @@ -1,237 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { - uuid as id, - objectEntries, -} from '../../../utils/fns' -import { - NetInfoState, - addEventListener as addConnectionListener, -} from "@react-native-community/netinfo" -import { getLogger } from '../../logging' -import { NotificationSender } from '../../../core/notification_center' - -import { - getQueue, - EventProcessor, - ProcessableEvent, - sendEventNotification, - validateAndGetBatchSize, - validateAndGetFlushInterval, - DEFAULT_BATCH_SIZE, - DEFAULT_FLUSH_INTERVAL, -} from "../eventProcessor" -import { ReactNativeEventsStore } from '../reactNativeEventsStore' -import { Synchronizer } from '../synchronizer' -import { EventQueue } from '../eventQueue' -import RequestTracker from '../requestTracker' -import { areEventContextsEqual } from '../events' -import { formatEvents } from './buildEventV1' -import { - EventV1Request, - EventDispatcher, - EventDispatcherResponse, -} from '../eventDispatcher' - -const logger = getLogger('ReactNativeEventProcessor') - -const DEFAULT_MAX_QUEUE_SIZE = 10000 -const PENDING_EVENTS_STORE_KEY = 'fs_optly_pending_events' -const EVENT_BUFFER_STORE_KEY = 'fs_optly_event_buffer' - -/** - * React Native Events Processor with Caching support for events when app is offline. - */ -export class LogTierV1EventProcessor implements EventProcessor { - private dispatcher: EventDispatcher - // expose for testing - public queue: EventQueue<ProcessableEvent> - private notificationSender?: NotificationSender - private requestTracker: RequestTracker - - /* eslint-disable */ - private unsubscribeNetInfo: Function | null = null - /* eslint-enable */ - private isInternetReachable = true - private pendingEventsPromise: Promise<void> | null = null - private synchronizer: Synchronizer = new Synchronizer() - - // If a pending event fails to dispatch, this indicates skipping further events to preserve sequence in the next retry. - private shouldSkipDispatchToPreserveSequence = false - - /** - * This Stores Formatted events before dispatching. The events are removed after they are successfully dispatched. - * Stored events are retried on every new event dispatch, when connection becomes available again or when SDK initializes the next time. - */ - private pendingEventsStore: ReactNativeEventsStore<EventV1Request> - - /** - * This stores individual events generated from the SDK till they are part of the pending buffer. - * The store is cleared right before the event is formatted to be dispatched. - * This is to make sure that individual events are not lost when app closes before the buffer was flushed. - */ - private eventBufferStore: ReactNativeEventsStore<ProcessableEvent> - - constructor({ - dispatcher, - flushInterval = DEFAULT_FLUSH_INTERVAL, - batchSize = DEFAULT_BATCH_SIZE, - maxQueueSize = DEFAULT_MAX_QUEUE_SIZE, - notificationCenter, - }: { - dispatcher: EventDispatcher - flushInterval?: number - batchSize?: number - maxQueueSize?: number - notificationCenter?: NotificationSender - }) { - this.dispatcher = dispatcher - this.notificationSender = notificationCenter - this.requestTracker = new RequestTracker() - - flushInterval = validateAndGetFlushInterval(flushInterval) - batchSize = validateAndGetBatchSize(batchSize) - this.queue = getQueue(batchSize, flushInterval, this.drainQueue.bind(this), areEventContextsEqual) - this.pendingEventsStore = new ReactNativeEventsStore(maxQueueSize, PENDING_EVENTS_STORE_KEY) - this.eventBufferStore = new ReactNativeEventsStore(maxQueueSize, EVENT_BUFFER_STORE_KEY) - } - - private async connectionListener(state: NetInfoState) { - if (this.isInternetReachable && !state.isInternetReachable) { - this.isInternetReachable = false - logger.debug('Internet connection lost') - return - } - if (!this.isInternetReachable && state.isInternetReachable) { - this.isInternetReachable = true - logger.debug('Internet connection is restored, attempting to dispatch pending events') - await this.processPendingEvents() - this.shouldSkipDispatchToPreserveSequence = false - } - } - - private isSuccessResponse(status: number): boolean { - return status >= 200 && status < 400 - } - - private async drainQueue(buffer: ProcessableEvent[]): Promise<void> { - if (buffer.length === 0) { - return - } - - await this.synchronizer.getLock() - - // Retry pending failed events while draining queue - await this.processPendingEvents() - - logger.debug('draining queue with %s events', buffer.length) - - const eventCacheKey = id() - const formattedEvent = formatEvents(buffer) - - // Store formatted event before dispatching to be retried later in case of failure. - await this.pendingEventsStore.set(eventCacheKey, formattedEvent) - - // Clear buffer because the buffer has become a formatted event and is already stored in pending cache. - for (const {uuid} of buffer) { - await this.eventBufferStore.remove(uuid) - } - - if (!this.shouldSkipDispatchToPreserveSequence) { - await this.dispatchEvent(eventCacheKey, formattedEvent) - } - - // Resetting skip flag because current sequence of events have all been processed - this.shouldSkipDispatchToPreserveSequence = false - - this.synchronizer.releaseLock() - } - - private async processPendingEvents(): Promise<void> { - logger.debug('Processing pending events from offline storage') - if (!this.pendingEventsPromise) { - // Only process events if existing promise is not in progress - this.pendingEventsPromise = this.getPendingEventsPromise() - } else { - logger.debug('Already processing pending events, returning the existing promise') - } - await this.pendingEventsPromise - this.pendingEventsPromise = null - } - - private async getPendingEventsPromise(): Promise<void> { - const formattedEvents: {[key: string]: any} = await this.pendingEventsStore.getEventsMap() - const eventEntries = objectEntries(formattedEvents) - logger.debug('Processing %s pending events', eventEntries.length) - // Using for loop to be able to wait for previous dispatch to finish before moving on to the new one - for (const [eventKey, event] of eventEntries) { - // If one event dispatch failed, skip subsequent events to preserve sequence - if (this.shouldSkipDispatchToPreserveSequence) { - return - } - await this.dispatchEvent(eventKey, event) - } - } - - private async dispatchEvent(eventCacheKey: string, event: EventV1Request): Promise<void> { - const requestPromise = new Promise<void>((resolve) => { - this.dispatcher.dispatchEvent(event, async ({ statusCode }: EventDispatcherResponse) => { - if (this.isSuccessResponse(statusCode)) { - await this.pendingEventsStore.remove(eventCacheKey) - } else { - this.shouldSkipDispatchToPreserveSequence = true - logger.warn('Failed to dispatch event, Response status Code: %s', statusCode) - } - resolve() - }) - sendEventNotification(this.notificationSender, event) - }) - // Tracking all the requests to dispatch to make sure request is completed before fulfilling the `stop` promise - this.requestTracker.trackRequest(requestPromise) - return requestPromise - } - - public async start(): Promise<void> { - this.queue.start() - this.unsubscribeNetInfo = addConnectionListener(this.connectionListener.bind(this)) - - await this.processPendingEvents() - this.shouldSkipDispatchToPreserveSequence = false - - // Process individual events pending from the buffer. - const events: ProcessableEvent[] = await this.eventBufferStore.getEventsList() - await this.eventBufferStore.clear() - events.forEach(this.process.bind(this)) - } - - public process(event: ProcessableEvent): void { - // Adding events to buffer store. If app closes before dispatch, we can reprocess next time the app initializes - this.eventBufferStore.set(event.uuid, event).then(() => { - this.queue.enqueue(event) - }) - } - - public async stop(): Promise<void> { - // swallow - an error stopping this queue shouldn't prevent this from stopping - try { - this.unsubscribeNetInfo && this.unsubscribeNetInfo() - await this.queue.stop() - return this.requestTracker.onRequestsComplete() - } catch (e) { - logger.error('Error stopping EventProcessor: "%s"', Object(e).message, String(e)) - } - } -} diff --git a/packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.ts b/packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.ts deleted file mode 100644 index 108bf2e1c..000000000 --- a/packages/optimizely-sdk/lib/modules/event_processor/v1/v1EventProcessor.ts +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { getLogger } from '../../logging' -import { NotificationSender } from '../../../core/notification_center' - -import { EventDispatcher } from '../eventDispatcher' -import { - getQueue, - EventProcessor, - ProcessableEvent, - sendEventNotification, - validateAndGetBatchSize, - validateAndGetFlushInterval, - DEFAULT_BATCH_SIZE, - DEFAULT_FLUSH_INTERVAL, -} from '../eventProcessor' -import { EventQueue } from '../eventQueue' -import RequestTracker from '../requestTracker' -import { areEventContextsEqual } from '../events' -import { formatEvents } from './buildEventV1' - -const logger = getLogger('LogTierV1EventProcessor') - -export class LogTierV1EventProcessor implements EventProcessor { - private dispatcher: EventDispatcher - private queue: EventQueue<ProcessableEvent> - private notificationCenter?: NotificationSender - private requestTracker: RequestTracker - - constructor({ - dispatcher, - flushInterval = DEFAULT_FLUSH_INTERVAL, - batchSize = DEFAULT_BATCH_SIZE, - notificationCenter, - }: { - dispatcher: EventDispatcher - flushInterval?: number - batchSize?: number - notificationCenter?: NotificationSender - }) { - this.dispatcher = dispatcher - this.notificationCenter = notificationCenter - this.requestTracker = new RequestTracker() - - flushInterval = validateAndGetFlushInterval(flushInterval) - batchSize = validateAndGetBatchSize(batchSize) - this.queue = getQueue(batchSize, flushInterval, this.drainQueue.bind(this), areEventContextsEqual) - } - - drainQueue(buffer: ProcessableEvent[]): Promise<void> { - const reqPromise = new Promise<void>(resolve => { - logger.debug('draining queue with %s events', buffer.length) - - if (buffer.length === 0) { - resolve() - return - } - - const formattedEvent = formatEvents(buffer) - this.dispatcher.dispatchEvent(formattedEvent, () => { - resolve() - }) - sendEventNotification(this.notificationCenter, formattedEvent) - }) - this.requestTracker.trackRequest(reqPromise) - return reqPromise - } - - process(event: ProcessableEvent): void { - this.queue.enqueue(event) - } - - // TODO[OASIS-6649]: Don't use any type - // eslint-disable-next-line @typescript-eslint/no-explicit-any - stop(): Promise<any> { - // swallow - an error stopping this queue shouldn't prevent this from stopping - try { - this.queue.stop() - return this.requestTracker.onRequestsComplete() - } catch (e) { - logger.error('Error stopping EventProcessor: "%s"', Object(e).message, String(e)) - } - return Promise.resolve() - } - - async start(): Promise<void> { - this.queue.start() - } -} diff --git a/packages/optimizely-sdk/lib/modules/logging/errorHandler.ts b/packages/optimizely-sdk/lib/modules/logging/errorHandler.ts deleted file mode 100644 index bb659aeae..000000000 --- a/packages/optimizely-sdk/lib/modules/logging/errorHandler.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/** - * @export - * @interface ErrorHandler - */ -export interface ErrorHandler { - /** - * @param {Error} exception - * @memberof ErrorHandler - */ - handleError(exception: Error): void -} - -/** - * @export - * @class NoopErrorHandler - * @implements {ErrorHandler} - */ -export class NoopErrorHandler implements ErrorHandler { - /** - * @param {Error} exception - * @memberof NoopErrorHandler - */ - handleError(exception: Error): void { - // no-op - return - } -} - -let globalErrorHandler: ErrorHandler = new NoopErrorHandler() - -/** - * @export - * @param {ErrorHandler} handler - */ -export function setErrorHandler(handler: ErrorHandler): void { - globalErrorHandler = handler -} - -/** - * @export - * @returns {ErrorHandler} - */ -export function getErrorHandler(): ErrorHandler { - return globalErrorHandler -} - -/** - * @export - */ -export function resetErrorHandler(): void { - globalErrorHandler = new NoopErrorHandler() -} diff --git a/packages/optimizely-sdk/lib/modules/logging/index.ts b/packages/optimizely-sdk/lib/modules/logging/index.ts deleted file mode 100644 index 47a1e99c8..000000000 --- a/packages/optimizely-sdk/lib/modules/logging/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export * from './errorHandler' -export * from './models' -export * from './logger' diff --git a/packages/optimizely-sdk/lib/modules/logging/logger.ts b/packages/optimizely-sdk/lib/modules/logging/logger.ts deleted file mode 100644 index e58664fb1..000000000 --- a/packages/optimizely-sdk/lib/modules/logging/logger.ts +++ /dev/null @@ -1,333 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { getErrorHandler } from './errorHandler' -import { isValidEnum, sprintf } from '../../utils/fns' - -import { LogLevel, LoggerFacade, LogManager, LogHandler } from './models' - -type StringToLogLevel = { - NOTSET: number, - DEBUG: number, - INFO: number, - WARNING: number, - ERROR: number, -} - -const stringToLogLevel: StringToLogLevel = { - NOTSET: 0, - DEBUG: 1, - INFO: 2, - WARNING: 3, - ERROR: 4, -} - -function coerceLogLevel(level: any): LogLevel { - if (typeof level !== 'string') { - return level - } - - level = level.toUpperCase() - if (level === 'WARN') { - level = 'WARNING' - } - - if (!stringToLogLevel[level as keyof StringToLogLevel]) { - return level - } - - return stringToLogLevel[level as keyof StringToLogLevel] -} - -type LogData = { - message: string - splat: any[] - error?: Error -} - -class DefaultLogManager implements LogManager { - private loggers: { - [name: string]: LoggerFacade - } - private defaultLoggerFacade = new OptimizelyLogger() - - constructor() { - this.loggers = {} - } - - getLogger(name?: string): LoggerFacade { - if (!name) { - return this.defaultLoggerFacade - } - - if (!this.loggers[name]) { - this.loggers[name] = new OptimizelyLogger({ messagePrefix: name }) - } - - return this.loggers[name] - } -} - -type ConsoleLogHandlerConfig = { - logLevel?: LogLevel | string - logToConsole?: boolean - prefix?: string -} - -export class ConsoleLogHandler implements LogHandler { - public logLevel: LogLevel - private logToConsole: boolean - private prefix: string - - /** - * Creates an instance of ConsoleLogger. - * @param {ConsoleLogHandlerConfig} config - * @memberof ConsoleLogger - */ - constructor(config: ConsoleLogHandlerConfig = {}) { - this.logLevel = LogLevel.NOTSET - if (config.logLevel !== undefined && isValidEnum(LogLevel, config.logLevel)) { - this.setLogLevel(config.logLevel) - } - - this.logToConsole = config.logToConsole !== undefined ? !!config.logToConsole : true - this.prefix = config.prefix !== undefined ? config.prefix : '[OPTIMIZELY]' - } - - /** - * @param {LogLevel} level - * @param {string} message - * @memberof ConsoleLogger - */ - log(level: LogLevel, message: string) : void { - if (!this.shouldLog(level) || !this.logToConsole) { - return - } - - const logMessage = `${this.prefix} - ${this.getLogLevelName( - level, - )} ${this.getTime()} ${message}` - - this.consoleLog(level, [logMessage]) - } - - /** - * @param {LogLevel} level - * @memberof ConsoleLogger - */ - setLogLevel(level: LogLevel | string) : void { - level = coerceLogLevel(level) - if (!isValidEnum(LogLevel, level) || level === undefined) { - this.logLevel = LogLevel.ERROR - } else { - this.logLevel = level - } - } - - /** - * @returns {string} - * @memberof ConsoleLogger - */ - getTime(): string { - return new Date().toISOString() - } - - /** - * @private - * @param {LogLevel} targetLogLevel - * @returns {boolean} - * @memberof ConsoleLogger - */ - private shouldLog(targetLogLevel: LogLevel): boolean { - return targetLogLevel >= this.logLevel - } - - /** - * @private - * @param {LogLevel} logLevel - * @returns {string} - * @memberof ConsoleLogger - */ - private getLogLevelName(logLevel: LogLevel): string { - switch (logLevel) { - case LogLevel.DEBUG: - return 'DEBUG' - case LogLevel.INFO: - return 'INFO ' - case LogLevel.WARNING: - return 'WARN ' - case LogLevel.ERROR: - return 'ERROR' - default: - return 'NOTSET' - } - } - - /** - * @private - * @param {LogLevel} logLevel - * @param {string[]} logArguments - * @memberof ConsoleLogger - */ - private consoleLog(logLevel: LogLevel, logArguments: [string, ...string[]]) { - switch (logLevel) { - case LogLevel.DEBUG: - console.log(...logArguments) - break - case LogLevel.INFO: - console.info(...logArguments) - break - case LogLevel.WARNING: - console.warn(...logArguments) - break - case LogLevel.ERROR: - console.error(...logArguments) - break - default: - console.log(...logArguments) - } - } -} - -let globalLogLevel: LogLevel = LogLevel.NOTSET -let globalLogHandler: LogHandler | null = null - -class OptimizelyLogger implements LoggerFacade { - private messagePrefix = '' - - constructor(opts: { messagePrefix?: string } = {}) { - if (opts.messagePrefix) { - this.messagePrefix = opts.messagePrefix - } - } - - /** - * @param {(LogLevel | LogInputObject)} levelOrObj - * @param {string} [message] - * @memberof OptimizelyLogger - */ - log(level: LogLevel | string, message: string, ...splat: any[]): void { - this.internalLog(coerceLogLevel(level), { - message, - splat, - }) - } - - info(message: string | Error, ...splat: any[]): void { - this.namedLog(LogLevel.INFO, message, splat) - } - - debug(message: string | Error, ...splat: any[]): void { - this.namedLog(LogLevel.DEBUG, message, splat) - } - - warn(message: string | Error, ...splat: any[]): void { - this.namedLog(LogLevel.WARNING, message, splat) - } - - error(message: string | Error, ...splat: any[]): void { - this.namedLog(LogLevel.ERROR, message, splat) - } - - private format(data: LogData): string { - return `${this.messagePrefix ? this.messagePrefix + ': ' : ''}${sprintf( - data.message, - ...data.splat, - )}` - } - - private internalLog(level: LogLevel, data: LogData): void { - if (!globalLogHandler) { - return - } - - if (level < globalLogLevel) { - return - } - - globalLogHandler.log(level, this.format(data)) - - if (data.error && data.error instanceof Error) { - getErrorHandler().handleError(data.error) - } - } - - private namedLog(level: LogLevel, message: string | Error, splat: any[]): void { - let error: Error | undefined - - if (message instanceof Error) { - error = message - message = error.message - this.internalLog(level, { - error, - message, - splat, - }) - return - } - - if (splat.length === 0) { - this.internalLog(level, { - message, - splat, - }) - return - } - - const last = splat[splat.length - 1] - if (last instanceof Error) { - error = last - splat.splice(-1) - } - - this.internalLog(level, { message, error, splat }) - } -} - -let globalLogManager: LogManager = new DefaultLogManager() - -export function getLogger(name?: string): LoggerFacade { - return globalLogManager.getLogger(name) -} - -export function setLogHandler(logger: LogHandler | null) : void { - globalLogHandler = logger -} - -export function setLogLevel(level: LogLevel | string) : void { - level = coerceLogLevel(level) - if (!isValidEnum(LogLevel, level) || level === undefined) { - globalLogLevel = LogLevel.ERROR - } else { - globalLogLevel = level - } -} - -export function getLogLevel(): LogLevel { - return globalLogLevel -} - -/** - * Resets all global logger state to it's original - */ -export function resetLogger() : void { - globalLogManager = new DefaultLogManager() - globalLogLevel = LogLevel.NOTSET -} - -export default { - setLogLevel: setLogLevel, - setLogHandler: setLogHandler -} diff --git a/packages/optimizely-sdk/lib/modules/logging/models.ts b/packages/optimizely-sdk/lib/modules/logging/models.ts deleted file mode 100644 index cd3223932..000000000 --- a/packages/optimizely-sdk/lib/modules/logging/models.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -export enum LogLevel { - NOTSET = 0, - DEBUG = 1, - INFO = 2, - WARNING = 3, - ERROR = 4, -} - -export interface LoggerFacade { - log(level: LogLevel | string, message: string, ...splat: any[]): void - - info(message: string | Error, ...splat: any[]): void - - debug(message: string | Error, ...splat: any[]): void - - warn(message: string | Error, ...splat: any[]): void - - error(message: string | Error, ...splat: any[]): void -} - -export interface LogManager { - getLogger(name?: string): LoggerFacade -} - -export interface LogHandler { - log(level: LogLevel, message: string, ...splat: any[]): void -} diff --git a/packages/optimizely-sdk/lib/plugins/datafile_manager/browser_http_polling_datafile_manager.ts b/packages/optimizely-sdk/lib/plugins/datafile_manager/browser_http_polling_datafile_manager.ts deleted file mode 100644 index 9bc89aa53..000000000 --- a/packages/optimizely-sdk/lib/plugins/datafile_manager/browser_http_polling_datafile_manager.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright 2021-2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - import { LoggerFacade } from '../../modules/logging'; - import { HttpPollingDatafileManager } from '../../modules/datafile-manager/index.browser'; - import { DatafileOptions, DatafileManagerConfig, DatafileManager } from '../../shared_types'; - import { toDatafile, tryCreatingProjectConfig } from '../../core/project_config'; - import fns from '../../utils/fns'; - - export function createHttpPollingDatafileManager( - sdkKey: string, - logger: LoggerFacade, - // TODO[OASIS-6649]: Don't use object type - // eslint-disable-next-line @typescript-eslint/ban-types - datafile?: string | object, - datafileOptions?: DatafileOptions, - ): DatafileManager { - const datafileManagerConfig: DatafileManagerConfig = { sdkKey }; - if (datafileOptions === undefined || (typeof datafileOptions === 'object' && datafileOptions !== null)) { - fns.assign(datafileManagerConfig, datafileOptions); - } - if (datafile) { - const { configObj, error } = tryCreatingProjectConfig({ - datafile: datafile, - jsonSchemaValidator: undefined, - logger: logger, - }); - - if (error) { - logger.error(error); - } - if (configObj) { - datafileManagerConfig.datafile = toDatafile(configObj); - } - } - return new HttpPollingDatafileManager(datafileManagerConfig); - } diff --git a/packages/optimizely-sdk/lib/plugins/datafile_manager/http_polling_datafile_manager.tests.js b/packages/optimizely-sdk/lib/plugins/datafile_manager/http_polling_datafile_manager.tests.js deleted file mode 100644 index ffd9b369a..000000000 --- a/packages/optimizely-sdk/lib/plugins/datafile_manager/http_polling_datafile_manager.tests.js +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Copyright 2021 Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import sinon from 'sinon'; -import { createHttpPollingDatafileManager } from './http_polling_datafile_manager'; -import * as projectConfig from '../../core/project_config'; -import datafileManager from '../../modules/datafile-manager/index.node'; - -describe('lib/plugins/datafile_manager/http_polling_datafile_manager', function() { - var sandbox = sinon.sandbox.create(); - - beforeEach(() => { - sandbox.stub(datafileManager,'HttpPollingDatafileManager') - }); - - afterEach(() => { - sandbox.restore(); - }); - - describe('when datafile is null', () => { - beforeEach(() => { - sandbox.stub(projectConfig, 'toDatafile'); - sandbox.stub(projectConfig, 'tryCreatingProjectConfig'); - }); - it('should create HttpPollingDatafileManager with correct options and not create project config', () => { - var logger = { - error: () => {}, - } - createHttpPollingDatafileManager('SDK_KEY', logger, undefined, { - autoUpdate: true, - datafileAccessToken: 'THE_TOKEN', - updateInterval: 5000, - urlTemplate: 'http://example.com/' - }); - - sinon.assert.calledWithExactly(datafileManager.HttpPollingDatafileManager, { - autoUpdate: true, - datafileAccessToken: 'THE_TOKEN', - updateInterval: 5000, - urlTemplate: 'http://example.com/', - sdkKey: 'SDK_KEY', - }); - sinon.assert.notCalled(projectConfig.tryCreatingProjectConfig); - sinon.assert.notCalled(projectConfig.toDatafile); - }); - }); - - describe('when initial datafile is provided', () => { - beforeEach(() => { - sandbox.stub(projectConfig, 'tryCreatingProjectConfig').returns({ configObj: { dummy: "Config" }, error: null}); - sandbox.stub(projectConfig, 'toDatafile').returns('{"dummy": "datafile"}'); - }); - it('should create project config and add datafile', () => { - var logger = { - error: () => {}, - } - var dummyDatafile = '{"dummy": "datafile"}'; - createHttpPollingDatafileManager('SDK_KEY', logger, dummyDatafile, { - autoUpdate: true, - datafileAccessToken: 'THE_TOKEN', - updateInterval: 5000, - urlTemplate: 'http://example.com/' - }); - - sinon.assert.calledWithExactly(datafileManager.HttpPollingDatafileManager, { - datafile: dummyDatafile, - autoUpdate: true, - datafileAccessToken: 'THE_TOKEN', - updateInterval: 5000, - urlTemplate: 'http://example.com/', - sdkKey: 'SDK_KEY', - }); - sinon.assert.calledWithExactly(projectConfig.tryCreatingProjectConfig, { - datafile: dummyDatafile, - jsonSchemaValidator: undefined, - logger, - }); - sinon.assert.calledWithExactly(projectConfig.toDatafile, {dummy: "Config"}); - }) - }) - - describe('error logging', () => { - beforeEach(() => { - sandbox.stub(projectConfig, 'tryCreatingProjectConfig').returns({ configObj: null, error: 'Error creating config'}); - sandbox.stub(projectConfig, 'toDatafile'); - }); - it('Should log error when error is thrown while creating project config', () => { - var logger = { - error: () => {}, - } - var errorSpy = sandbox.spy(logger, 'error'); - var dummyDatafile = '{"dummy": "datafile"}'; - createHttpPollingDatafileManager('SDK_KEY', logger, dummyDatafile, { - autoUpdate: true, - datafileAccessToken: 'THE_TOKEN', - updateInterval: 5000, - urlTemplate: 'http://example.com/' - }); - - sinon.assert.calledWithExactly(datafileManager.HttpPollingDatafileManager, { - autoUpdate: true, - datafileAccessToken: 'THE_TOKEN', - updateInterval: 5000, - urlTemplate: 'http://example.com/', - sdkKey: 'SDK_KEY', - }); - sinon.assert.calledWithExactly(projectConfig.tryCreatingProjectConfig, { - datafile: dummyDatafile, - jsonSchemaValidator: undefined, - logger, - }); - sinon.assert.notCalled(projectConfig.toDatafile); - sinon.assert.calledWithExactly(errorSpy, 'Error creating config'); - }) - }); -}); diff --git a/packages/optimizely-sdk/lib/plugins/datafile_manager/http_polling_datafile_manager.ts b/packages/optimizely-sdk/lib/plugins/datafile_manager/http_polling_datafile_manager.ts deleted file mode 100644 index 7bbd19738..000000000 --- a/packages/optimizely-sdk/lib/plugins/datafile_manager/http_polling_datafile_manager.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright 2021-2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { LoggerFacade } from '../../modules/logging'; -import datafileManager from '../../modules/datafile-manager/index.node'; -import { DatafileOptions, DatafileManagerConfig, DatafileManager } from '../../shared_types'; -import { toDatafile, tryCreatingProjectConfig } from '../../core/project_config'; -import fns from '../../utils/fns'; - -export function createHttpPollingDatafileManager( - sdkKey: string, - logger: LoggerFacade, - // TODO[OASIS-6649]: Don't use object type - // eslint-disable-next-line @typescript-eslint/ban-types - datafile?: string | object, - datafileOptions?: DatafileOptions, -): DatafileManager { - const datafileManagerConfig: DatafileManagerConfig = { sdkKey }; - if (datafileOptions === undefined || (typeof datafileOptions === 'object' && datafileOptions !== null)) { - fns.assign(datafileManagerConfig, datafileOptions); - } - if (datafile) { - const { configObj, error } = tryCreatingProjectConfig({ - datafile: datafile, - jsonSchemaValidator: undefined, - logger: logger, - }); - - if (error) { - logger.error(error); - } - if (configObj) { - datafileManagerConfig.datafile = toDatafile(configObj); - } - } - return new datafileManager.HttpPollingDatafileManager(datafileManagerConfig); -} diff --git a/packages/optimizely-sdk/lib/plugins/datafile_manager/no_op_datafile_manager.tests.js b/packages/optimizely-sdk/lib/plugins/datafile_manager/no_op_datafile_manager.tests.js deleted file mode 100644 index 1eacb169b..000000000 --- a/packages/optimizely-sdk/lib/plugins/datafile_manager/no_op_datafile_manager.tests.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright 2021 Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - import { assert } from 'chai'; - import { createNoOpDatafileManager } from './no_op_datafile_manager'; - - describe('lib/plugins/datafile_manager/no_op_datafile_manager', function() { - var dfm = createNoOpDatafileManager(); - - beforeEach(() => { - dfm.start(); - }); - - it('should return empty string when get is called', () => { - assert.equal(dfm.get(), ''); - }); - - it('should return a resolved promise when onReady is called', (done) => { - dfm.onReady().then(done); - }); - - it('should return a resolved promise when stop is called', (done) => { - dfm.stop().then(done); - }); - - it('should return an empty function when event listener is added', () => { - assert.equal(typeof(dfm.on('dummyEvent', () => {}, '')), 'function'); - }); - }); - \ No newline at end of file diff --git a/packages/optimizely-sdk/lib/plugins/datafile_manager/no_op_datafile_manager.ts b/packages/optimizely-sdk/lib/plugins/datafile_manager/no_op_datafile_manager.ts deleted file mode 100644 index eb602c371..000000000 --- a/packages/optimizely-sdk/lib/plugins/datafile_manager/no_op_datafile_manager.ts +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright 2021, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { DatafileManager, DatafileUpdateListener} from '../../shared_types'; - -class NoOpDatafileManager implements DatafileManager { - - /* eslint-disable @typescript-eslint/no-unused-vars */ - on(_eventName: string, _listener: DatafileUpdateListener): () => void { - return (): void => {} - } - - get(): string { - return ''; - } - - onReady(): Promise<void> { - return Promise.resolve(); - } - - start(): void {} - - stop(): Promise<void> { - return Promise.resolve(); - } -} - -export function createNoOpDatafileManager(): DatafileManager { - return new NoOpDatafileManager(); -} diff --git a/packages/optimizely-sdk/lib/plugins/datafile_manager/react_native_http_polling_datafile_manager.ts b/packages/optimizely-sdk/lib/plugins/datafile_manager/react_native_http_polling_datafile_manager.ts deleted file mode 100644 index e5a4eba83..000000000 --- a/packages/optimizely-sdk/lib/plugins/datafile_manager/react_native_http_polling_datafile_manager.ts +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright 2021-2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - import { LoggerFacade } from '../../modules/logging'; - import { HttpPollingDatafileManager } from '../../modules/datafile-manager/index.react_native'; - import { DatafileOptions, DatafileManagerConfig, DatafileManager } from '../../shared_types'; - import { toDatafile, tryCreatingProjectConfig } from '../../core/project_config'; - import fns from '../../utils/fns'; - - export function createHttpPollingDatafileManager( - sdkKey: string, - logger: LoggerFacade, - // TODO[OASIS-6649]: Don't use object type - // eslint-disable-next-line @typescript-eslint/ban-types - datafile?: string | object, - datafileOptions?: DatafileOptions, - ): DatafileManager { - const datafileManagerConfig: DatafileManagerConfig = { sdkKey }; - if (datafileOptions === undefined || (typeof datafileOptions === 'object' && datafileOptions !== null)) { - fns.assign(datafileManagerConfig, datafileOptions); - } - if (datafile) { - const { configObj, error } = tryCreatingProjectConfig({ - datafile: datafile, - jsonSchemaValidator: undefined, - logger: logger, - }); - - if (error) { - logger.error(error); - } - if (configObj) { - datafileManagerConfig.datafile = toDatafile(configObj); - } - } - return new HttpPollingDatafileManager(datafileManagerConfig); - } diff --git a/packages/optimizely-sdk/lib/plugins/event_dispatcher/index.browser.tests.js b/packages/optimizely-sdk/lib/plugins/event_dispatcher/index.browser.tests.js deleted file mode 100644 index b5c548c5b..000000000 --- a/packages/optimizely-sdk/lib/plugins/event_dispatcher/index.browser.tests.js +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright 2016-2017, 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { assert } from 'chai'; -import sinon from 'sinon'; - -import { dispatchEvent } from './index.browser'; - -describe('lib/plugins/event_dispatcher/browser', function() { - describe('APIs', function() { - describe('dispatchEvent', function() { - beforeEach(function() { - this.requests = []; - global.XMLHttpRequest = sinon.useFakeXMLHttpRequest(); - global.XMLHttpRequest.onCreate = (req) => { this.requests.push(req); }; - }); - - afterEach(function() { - delete global.XMLHttpRequest - }); - - it('should send a POST request with the specified params', function(done) { - var eventParams = { testParam: 'testParamValue' }; - var eventObj = { - url: 'https://cdn.com/event', - body: { - id: 123, - }, - httpVerb: 'POST', - params: eventParams, - }; - - var callback = sinon.spy(); - dispatchEvent(eventObj, callback); - assert.strictEqual(1, this.requests.length); - assert.strictEqual(this.requests[0].method, 'POST'); - assert.strictEqual(this.requests[0].requestBody, JSON.stringify(eventParams)); - done(); - }); - - it('should execute the callback passed to event dispatcher with a post', function(done) { - var eventParams = { testParam: 'testParamValue' }; - var eventObj = { - url: 'https://cdn.com/event', - body: { - id: 123, - }, - httpVerb: 'POST', - params: eventParams, - }; - - var callback = sinon.spy(); - dispatchEvent(eventObj, callback); - this.requests[0].respond([ - 200, - {}, - '{"url":"https://cdn.com/event","body":{"id":123},"httpVerb":"POST","params":{"testParam":"testParamValue"}}', - ]); - sinon.assert.calledOnce(callback); - done(); - }); - - it('should execute the callback passed to event dispatcher with a get', function(done) { - var eventObj = { - url: 'https://cdn.com/event', - httpVerb: 'GET', - }; - - var callback = sinon.spy(); - dispatchEvent(eventObj, callback); - this.requests[0].respond([200, {}, '{"url":"https://cdn.com/event","httpVerb":"GET"']); - sinon.assert.calledOnce(callback); - done(); - }); - }); - }); -}); diff --git a/packages/optimizely-sdk/lib/plugins/event_dispatcher/index.browser.ts b/packages/optimizely-sdk/lib/plugins/event_dispatcher/index.browser.ts deleted file mode 100644 index 9f0da6d0a..000000000 --- a/packages/optimizely-sdk/lib/plugins/event_dispatcher/index.browser.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright 2016-2017, 2020-2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -const POST_METHOD = 'POST'; -const GET_METHOD = 'GET'; -const READYSTATE_COMPLETE = 4; - -export interface Event { - url: string; - httpVerb: 'POST' | 'GET'; - // eslint-disable-next-line @typescript-eslint/no-explicit-any - params: any; -} - - -/** - * Sample event dispatcher implementation for tracking impression and conversions - * Users of the SDK can provide their own implementation - * @param {Event} eventObj - * @param {Function} callback - */ -export const dispatchEvent = function( - eventObj: Event, - callback: (response: { statusCode: number; }) => void -): void { - const params = eventObj.params; - let url: string = eventObj.url; - let req: XMLHttpRequest; - if (eventObj.httpVerb === POST_METHOD) { - req = new XMLHttpRequest(); - req.open(POST_METHOD, url, true); - req.setRequestHeader('Content-Type', 'application/json'); - req.onreadystatechange = function() { - if (req.readyState === READYSTATE_COMPLETE && callback && typeof callback === 'function') { - try { - callback({ statusCode: req.status }); - } catch (e) { - // TODO: Log this somehow (consider adding a logger to the EventDispatcher interface) - } - } - }; - req.send(JSON.stringify(params)); - } else { - // add param for cors headers to be sent by the log endpoint - url += '?wxhr=true'; - if (params) { - url += '&' + toQueryString(params); - } - - req = new XMLHttpRequest(); - req.open(GET_METHOD, url, true); - req.onreadystatechange = function() { - if (req.readyState === READYSTATE_COMPLETE && callback && typeof callback === 'function') { - try { - callback({ statusCode: req.status }); - } catch (e) { - // TODO: Log this somehow (consider adding a logger to the EventDispatcher interface) - } - } - }; - req.send(); - } -} - -// eslint-disable-next-line @typescript-eslint/no-explicit-any -const toQueryString = function(obj: any): string { - return Object.keys(obj) - .map(function(k) { - return encodeURIComponent(k) + '=' + encodeURIComponent(obj[k]); - }) - .join('&'); -}; - -export default { - dispatchEvent, -}; diff --git a/packages/optimizely-sdk/lib/plugins/event_dispatcher/index.node.tests.js b/packages/optimizely-sdk/lib/plugins/event_dispatcher/index.node.tests.js deleted file mode 100644 index 2afeccc7b..000000000 --- a/packages/optimizely-sdk/lib/plugins/event_dispatcher/index.node.tests.js +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Copyright 2016-2018, 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import nock from 'nock'; -import sinon from 'sinon'; -import { assert } from 'chai'; - -import { dispatchEvent } from './index.node'; - -describe('lib/plugins/event_dispatcher/node', function() { - describe('APIs', function() { - describe('dispatchEvent', function() { - var stubCallback = { - callback: function() {}, - }; - - beforeEach(function() { - sinon.stub(stubCallback, 'callback'); - nock('https://cdn.com/') - .post('/event') - .reply(200, { - ok: true, - }); - }); - - afterEach(function() { - stubCallback.callback.restore(); - nock.cleanAll(); - }); - - it('should send a POST request with the specified params', function(done) { - var eventObj = { - url: 'https://cdn.com/event', - params: { - id: 123, - }, - httpVerb: 'POST', - }; - - dispatchEvent(eventObj, function(resp) { - assert.equal(200, resp.statusCode); - done(); - }); - }); - - it('should execute the callback passed to event dispatcher', function(done) { - var eventObj = { - url: 'https://cdn.com/event', - params: { - id: 123, - }, - httpVerb: 'POST', - }; - - dispatchEvent(eventObj, stubCallback.callback) - .on('response', function(response) { - sinon.assert.calledOnce(stubCallback.callback); - done(); - }) - .on('error', function(error) { - assert.fail('status code okay', 'status code not okay', ''); - }); - }); - - it('rejects GET httpVerb', function() { - var eventObj = { - url: 'https://cdn.com/event', - params: { - id: 123, - }, - httpVerb: 'GET', - }; - - var callback = sinon.spy(); - dispatchEvent(eventObj, callback); - sinon.assert.notCalled(callback); - }); - - describe('in the event of an error', function() { - beforeEach(function() { - nock('https://example/') - .post('/event') - .replyWithError('Connection error') - }); - - it('does not throw', function() { - var eventObj = { - url: 'https://example/event', - params: {}, - httpVerb: 'POST', - }; - - var callback = sinon.spy(); - dispatchEvent(eventObj, callback); - sinon.assert.notCalled(callback); - }); - }); - }); - }); -}); diff --git a/packages/optimizely-sdk/lib/plugins/event_dispatcher/index.node.ts b/packages/optimizely-sdk/lib/plugins/event_dispatcher/index.node.ts deleted file mode 100644 index f9f716caf..000000000 --- a/packages/optimizely-sdk/lib/plugins/event_dispatcher/index.node.ts +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright 2016-2018, 2020-2021, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import http from 'http'; -import https from 'https'; -import url from 'url'; - -import { Event } from '../../shared_types'; - -/** - * Dispatch an HTTP request to the given url and the specified options - * @param {Event} eventObj Event object containing - * @param {string} eventObj.url the url to make the request to - * @param {Object} eventObj.params parameters to pass to the request (i.e. in the POST body) - * @param {string} eventObj.httpVerb the HTTP request method type. only POST is supported. - * @param {function} callback callback to execute - * @return {ClientRequest|undefined} ClientRequest object which made the request, or undefined if no request was made (error) - */ -export const dispatchEvent = function( - eventObj: Event, - callback: (response: { statusCode: number }) => void -): http.ClientRequest | void { - // Non-POST requests not supported - if (eventObj.httpVerb !== 'POST') { - return; - } - - const parsedUrl = url.parse(eventObj.url); - - const dataString = JSON.stringify(eventObj.params); - - const requestOptions = { - host: parsedUrl.host, - path: parsedUrl.path, - method: 'POST', - headers: { - 'content-type': 'application/json', - 'content-length': dataString.length.toString(), - }, - }; - - const requestCallback = function(response?: { statusCode: number }): void { - if (response && response.statusCode && response.statusCode >= 200 && response.statusCode < 400) { - callback(response); - } - }; - - const req = (parsedUrl.protocol === 'http:' ? http : https) - .request(requestOptions, requestCallback as (res: http.IncomingMessage) => void); - // Add no-op error listener to prevent this from throwing - req.on('error', function() {}); - req.write(dataString); - req.end(); - return req; -}; - -export default { - dispatchEvent, -}; diff --git a/packages/optimizely-sdk/lib/plugins/event_dispatcher/no_op.ts b/packages/optimizely-sdk/lib/plugins/event_dispatcher/no_op.ts deleted file mode 100644 index 32353bae9..000000000 --- a/packages/optimizely-sdk/lib/plugins/event_dispatcher/no_op.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2021, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { Event } from '../../shared_types'; - -/** - * No Op Event dispatcher for non standard platforms like edge workers etc - * @param {Event} eventObj - * @param {Function} callback - */ -/* eslint-disable @typescript-eslint/no-unused-vars */ -export const dispatchEvent = function( - eventObj: Event, - callback: (response: { statusCode: number; }) => void -): void { - // NoOp Event dispatcher. It does nothing really. -} - -export default { - dispatchEvent, -}; diff --git a/packages/optimizely-sdk/lib/plugins/event_processor/forwarding_event_processor.tests.js b/packages/optimizely-sdk/lib/plugins/event_processor/forwarding_event_processor.tests.js deleted file mode 100644 index 9a4670adc..000000000 --- a/packages/optimizely-sdk/lib/plugins/event_processor/forwarding_event_processor.tests.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * Copyright 2021 Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - import sinon from 'sinon'; - import { createForwardingEventProcessor } from './forwarding_event_processor'; - import * as buildEventV1 from '../../core/event_builder/build_event_v1'; - - describe('lib/plugins/event_processor/forwarding_event_processor', function() { - var sandbox = sinon.sandbox.create(); - var ep; - var dispatcherSpy; - var sendNotificationsSpy; - - beforeEach(() => { - var dispatcher = { - dispatchEvent: () => {}, - }; - var notificationCenter = { - sendNotifications: () => {}, - } - dispatcherSpy = sandbox.spy(dispatcher, 'dispatchEvent'); - sendNotificationsSpy = sandbox.spy(notificationCenter, 'sendNotifications'); - sandbox.stub(buildEventV1, 'formatEvents').returns({ dummy: "event" }); - ep = createForwardingEventProcessor(dispatcher, notificationCenter); - ep.start(); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('should dispatch event immediately when process is called', () => { - ep.process({ dummy: 'event' }); - sinon.assert.calledWithExactly(dispatcherSpy, { dummy: 'event' }, sinon.match.func); - sinon.assert.calledOnce(sendNotificationsSpy); - }); - - it('should return a resolved promise when stop is called', (done) => { - ep.stop().then(done); - }); - }); diff --git a/packages/optimizely-sdk/lib/plugins/event_processor/forwarding_event_processor.ts b/packages/optimizely-sdk/lib/plugins/event_processor/forwarding_event_processor.ts deleted file mode 100644 index dd0473ac1..000000000 --- a/packages/optimizely-sdk/lib/plugins/event_processor/forwarding_event_processor.ts +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright 2021-2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { - EventProcessor, - ProcessableEvent, -} from '../../../lib/modules/event_processor'; -import { NotificationSender } from '../../core/notification_center'; - -import { EventDispatcher } from '../../shared_types'; -import { NOTIFICATION_TYPES } from '../../utils/enums'; -import { formatEvents } from '../../core/event_builder/build_event_v1'; - -class ForwardingEventProcessor implements EventProcessor { - private dispatcher: EventDispatcher; - private NotificationSender?: NotificationSender; - - constructor(dispatcher: EventDispatcher, notificationSender?: NotificationSender) { - this.dispatcher = dispatcher; - this.NotificationSender = notificationSender; - } - - process(event: ProcessableEvent): void { - const formattedEvent = formatEvents([event]); - this.dispatcher.dispatchEvent(formattedEvent, () => {}); - if (this.NotificationSender) { - this.NotificationSender.sendNotifications( - NOTIFICATION_TYPES.LOG_EVENT, - formattedEvent, - ) - } - } - - start(): void {} - - stop(): Promise<unknown> { - return Promise.resolve(); - } -} - -export function createForwardingEventProcessor(dispatcher: EventDispatcher, notificationSender?: NotificationSender): EventProcessor { - return new ForwardingEventProcessor(dispatcher, notificationSender); -} diff --git a/packages/optimizely-sdk/lib/plugins/key_value_cache/browserAsyncStorageCache.ts b/packages/optimizely-sdk/lib/plugins/key_value_cache/browserAsyncStorageCache.ts deleted file mode 100644 index d65a9b4a8..000000000 --- a/packages/optimizely-sdk/lib/plugins/key_value_cache/browserAsyncStorageCache.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import PersistentKeyValueCache from './persistentKeyValueCache'; - -export default class BrowserAsyncStorageCache implements PersistentKeyValueCache { - async contains(key: string): Promise<boolean> { - return localStorage.getItem(key) !== null; - } - - async get(key: string): Promise<string | null> { - return localStorage.getItem(key); - } - - async remove(key: string): Promise<boolean> { - if (await this.contains(key)) { - localStorage.removeItem(key); - return true; - } else { - return false; - } - } - - async set(key: string, val: string): Promise<void> { - return localStorage.setItem(key, val); - } -} diff --git a/packages/optimizely-sdk/lib/plugins/key_value_cache/persistentKeyValueCache.ts b/packages/optimizely-sdk/lib/plugins/key_value_cache/persistentKeyValueCache.ts deleted file mode 100644 index 268456d9f..000000000 --- a/packages/optimizely-sdk/lib/plugins/key_value_cache/persistentKeyValueCache.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * An Interface to implement a persistent key value cache which supports strings as keys and value objects. - */ -export default interface PersistentKeyValueCache { - /** - * Checks if a key exists in the cache - * @param key - * Resolves promise with - * 1. true if the key exists - * 2. false if the key does not exist - * Rejects the promise in case of an error - */ - contains(key: string): Promise<boolean>; - - /** - * Returns value stored against a key or undefined if not found. - * @param key - * @returns - * Resolves promise with - * 1. object as value if found. - * 2. undefined if the key does not exist in the cache. - * Rejects the promise in case of an error - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - get(key: string): Promise<any>; - - /** - * Removes the key value pair from cache. - * @param key * - * @returns - * Resolves promise with - * 1. true if key-value was removed or - * 2. false if key not found - * Rejects the promise in case of an error - */ - remove(key: string): Promise<boolean>; - - /** - * Stores any object in the persistent cache against a key - * @param key - * @param val - * @returns - * Resolves promise without a value if successful - * Rejects the promise in case of an error - */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - set(key: string, val: any): Promise<void>; -} diff --git a/packages/optimizely-sdk/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts b/packages/optimizely-sdk/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts deleted file mode 100644 index 3aced8bdc..000000000 --- a/packages/optimizely-sdk/lib/plugins/key_value_cache/reactNativeAsyncStorageCache.ts +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright 2020, 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import AsyncStorage from '@react-native-async-storage/async-storage'; -import PersistentKeyValueCache from './persistentKeyValueCache'; - -export default class ReactNativeAsyncStorageCache implements PersistentKeyValueCache { - async contains(key: string): Promise<boolean> { - return await AsyncStorage.getItem(key) !== null; - } - - async get(key: string): Promise<string | null> { - return await AsyncStorage.getItem(key); - } - - async remove(key: string): Promise<boolean> { - if (await this.contains(key)) { - await AsyncStorage.removeItem(key); - return true; - } - return false; - } - - set(key: string, val: string): Promise<void> { - return AsyncStorage.setItem(key, val); - } -} diff --git a/packages/optimizely-sdk/lib/plugins/logger/index.react_native.tests.js b/packages/optimizely-sdk/lib/plugins/logger/index.react_native.tests.js deleted file mode 100644 index 7ea19e98a..000000000 --- a/packages/optimizely-sdk/lib/plugins/logger/index.react_native.tests.js +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright 2019-2020 Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import sinon from 'sinon'; -import { assert } from 'chai'; - -import { createLogger } from './index.react_native'; -import { LOG_LEVEL } from '../../utils/enums'; - -describe('lib/plugins/logger/react_native', function() { - describe('APIs', function() { - var defaultLogger; - describe('createLogger', function() { - it('should return an instance of the default logger', function() { - defaultLogger = createLogger(); - assert.isObject(defaultLogger); - }); - }); - - describe('log', function() { - beforeEach(function() { - defaultLogger = createLogger(); - - sinon.stub(console, 'log'); - sinon.stub(console, 'info'); - sinon.stub(console, 'warn'); - sinon.stub(console, 'error'); - }); - - afterEach(function() { - console.log.restore(); - console.info.restore(); - console.warn.restore(); - console.error.restore(); - }); - - it('should use console.info when log level is info', function() { - defaultLogger.log(LOG_LEVEL.INFO, 'message'); - sinon.assert.calledWithExactly(console.info, sinon.match(/.*INFO.*message.*/)); - sinon.assert.notCalled(console.log); - sinon.assert.notCalled(console.warn); - sinon.assert.notCalled(console.error); - }); - - it('should use console.log when log level is debug', function() { - defaultLogger.log(LOG_LEVEL.DEBUG, 'message'); - sinon.assert.calledWithExactly(console.log, sinon.match(/.*DEBUG.*message.*/)); - sinon.assert.notCalled(console.info); - sinon.assert.notCalled(console.warn); - sinon.assert.notCalled(console.error); - }); - - it('should use console.warn when log level is warn', function() { - defaultLogger.log(LOG_LEVEL.WARNING, 'message'); - sinon.assert.calledWithExactly(console.warn, sinon.match(/.*WARNING.*message.*/)); - sinon.assert.notCalled(console.log); - sinon.assert.notCalled(console.info); - sinon.assert.notCalled(console.error); - }); - - it('should use console.warn when log level is error', function() { - defaultLogger.log(LOG_LEVEL.ERROR, 'message'); - sinon.assert.calledWithExactly(console.warn, sinon.match(/.*ERROR.*message.*/)); - sinon.assert.notCalled(console.log); - sinon.assert.notCalled(console.info); - sinon.assert.notCalled(console.error); - }); - }); - }); -}); diff --git a/packages/optimizely-sdk/lib/plugins/logger/index.react_native.ts b/packages/optimizely-sdk/lib/plugins/logger/index.react_native.ts deleted file mode 100644 index 5d5ee8ae7..000000000 --- a/packages/optimizely-sdk/lib/plugins/logger/index.react_native.ts +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright 2019-2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { LogLevel } from '../../modules/logging'; -import { sprintf } from '../../utils/fns'; -import { NoOpLogger } from './index'; - -function getLogLevelName(level: number): string { - switch (level) { - case LogLevel.INFO: - return 'INFO'; - case LogLevel.ERROR: - return 'ERROR'; - case LogLevel.WARNING: - return 'WARNING'; - case LogLevel.DEBUG: - return 'DEBUG'; - default: - return 'NOTSET'; - } -} - -class ReactNativeLogger { - log(level: number, message: string): void { - const formattedMessage = sprintf('[OPTIMIZELY] - %s %s %s', getLogLevelName(level), new Date().toISOString(), message); - switch (level) { - case LogLevel.INFO: - console.info(formattedMessage); - break; - case LogLevel.ERROR: - case LogLevel.WARNING: - console.warn(formattedMessage); - break; - case LogLevel.DEBUG: - case LogLevel.NOTSET: - console.log(formattedMessage); - break; - } - } -} - -export function createLogger(): ReactNativeLogger { - return new ReactNativeLogger(); -} - -export function createNoOpLogger(): NoOpLogger { - return new NoOpLogger(); -} diff --git a/packages/optimizely-sdk/lib/plugins/logger/index.tests.js b/packages/optimizely-sdk/lib/plugins/logger/index.tests.js deleted file mode 100644 index 0e3eaac56..000000000 --- a/packages/optimizely-sdk/lib/plugins/logger/index.tests.js +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Copyright 2016, 2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { assert, expect } from 'chai'; -import sinon from 'sinon'; - -import { createLogger } from './'; -import { LOG_LEVEL } from '../../utils/enums';; - -describe('lib/plugins/logger', function() { - describe('APIs', function() { - var defaultLogger; - describe('createLogger', function() { - it('should return an instance of the default logger', function() { - defaultLogger = createLogger({ logLevel: LOG_LEVEL.NOTSET }); - assert.isObject(defaultLogger); - expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.NOTSET); - }); - }); - - describe('log', function() { - beforeEach(function() { - defaultLogger = createLogger({ logLevel: LOG_LEVEL.INFO }); - - sinon.stub(console, 'log'); - sinon.stub(console, 'info'); - sinon.stub(console, 'warn'); - sinon.stub(console, 'error'); - }); - - afterEach(function() { - console.log.restore(); - console.info.restore(); - console.warn.restore(); - console.error.restore(); - }); - - it('should log a message at the threshold log level', function() { - defaultLogger.log(LOG_LEVEL.INFO, 'message'); - - sinon.assert.notCalled(console.log); - sinon.assert.calledOnce(console.info); - sinon.assert.calledWithExactly(console.info, sinon.match(/.*INFO.*message.*/)); - sinon.assert.notCalled(console.warn); - sinon.assert.notCalled(console.error); - }); - - it('should log a message if its log level is higher than the threshold log level', function() { - defaultLogger.log(LOG_LEVEL.WARNING, 'message'); - - sinon.assert.notCalled(console.log); - sinon.assert.notCalled(console.info); - sinon.assert.calledOnce(console.warn); - sinon.assert.calledWithExactly(console.warn, sinon.match(/.*WARN.*message.*/)); - sinon.assert.notCalled(console.error); - }); - - it('should not log a message if its log level is lower than the threshold log level', function() { - defaultLogger.log(LOG_LEVEL.DEBUG, 'message'); - - sinon.assert.notCalled(console.log); - sinon.assert.notCalled(console.info); - sinon.assert.notCalled(console.warn); - sinon.assert.notCalled(console.error); - }); - }); - - describe('setLogLevel', function() { - beforeEach(function() { - defaultLogger = createLogger({ logLevel: LOG_LEVEL.NOTSET }); - }); - - it('should set the log level to the specified log level', function() { - expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.NOTSET); - - defaultLogger.setLogLevel(LOG_LEVEL.DEBUG); - expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.DEBUG); - - defaultLogger.setLogLevel(LOG_LEVEL.INFO); - expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.INFO); - }); - - it('should set the log level to the ERROR when log level is not specified', function() { - defaultLogger.setLogLevel(); - expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.ERROR); - }); - - it('should set the log level to the ERROR when log level is not valid', function() { - defaultLogger.setLogLevel(-123); - expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.ERROR); - - defaultLogger.setLogLevel(undefined); - expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.ERROR); - - defaultLogger.setLogLevel('abc'); - expect(defaultLogger.logLevel).to.equal(LOG_LEVEL.ERROR); - }); - }); - }); -}); diff --git a/packages/optimizely-sdk/lib/plugins/logger/index.ts b/packages/optimizely-sdk/lib/plugins/logger/index.ts deleted file mode 100644 index a9a24e7bb..000000000 --- a/packages/optimizely-sdk/lib/plugins/logger/index.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright 2016-2017, 2020-2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { ConsoleLogHandler, LogLevel } from '../../modules/logging'; - -type ConsoleLogHandlerConfig = { - logLevel?: LogLevel | string; - logToConsole?: boolean; - prefix?: string; -} - -export class NoOpLogger { - log(): void { } -} - -export function createLogger(opts?: ConsoleLogHandlerConfig): ConsoleLogHandler { - return new ConsoleLogHandler(opts); -} - -export function createNoOpLogger(): NoOpLogger { - return new NoOpLogger(); -} diff --git a/packages/optimizely-sdk/lib/plugins/odp/odp_config.ts b/packages/optimizely-sdk/lib/plugins/odp/odp_config.ts deleted file mode 100644 index 215d3655c..000000000 --- a/packages/optimizely-sdk/lib/plugins/odp/odp_config.ts +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export class OdpConfig { - /** - * Host of ODP audience segments API. - * @private - */ - private _apiHost: string; - - /** - * Getter to retrieve the ODP server host - * @public - */ - public get apiHost(): string { - return this._apiHost; - } - - /** - * Public API key for the ODP account from which the audience segments will be fetched (optional). - * @private - */ - private _apiKey: string; - - /** - * Getter to retrieve the ODP API key - * @public - */ - public get apiKey(): string { - return this._apiKey; - } - - /** - * All ODP segments used in the current datafile (associated with apiHost/apiKey). - * @private - */ - private _segmentsToCheck: string[]; - - /** - * Getter for ODP segments to check - * @public - */ - public get segmentsToCheck(): string[] { - return this._segmentsToCheck; - } - - constructor(apiKey: string, apiHost: string, segmentsToCheck?: string[]) { - this._apiKey = apiKey; - this._apiHost = apiHost; - this._segmentsToCheck = segmentsToCheck ?? []; - } - - /** - * Update the ODP configuration details - * @param apiKey Public API key for the ODP account - * @param apiHost Host of ODP audience segments API - * @param segmentsToCheck Audience segments - * @returns true if configuration was updated successfully - */ - public update(apiKey: string, apiHost: string, segmentsToCheck: string[]): boolean { - if (this._apiKey === apiKey && this._apiHost === apiHost && this._segmentsToCheck === segmentsToCheck) { - return false; - } else { - this._apiKey = apiKey; - this._apiHost = apiHost; - this._segmentsToCheck = segmentsToCheck; - - return true; - } - } - - /** - * Determines if ODP configuration has the minimum amount of information - */ - public isReady(): boolean { - return !!this._apiKey && !!this._apiHost; - } -} diff --git a/packages/optimizely-sdk/lib/plugins/odp/odp_event_manager.ts b/packages/optimizely-sdk/lib/plugins/odp/odp_event_manager.ts deleted file mode 100644 index 766d5fa0e..000000000 --- a/packages/optimizely-sdk/lib/plugins/odp/odp_event_manager.ts +++ /dev/null @@ -1,406 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { LogHandler, LogLevel } from '../../modules/logging'; -import { OdpEvent } from './odp_event'; -import { uuid } from '../../utils/fns'; -import { ODP_USER_KEY } from '../../utils/enums'; -import { OdpConfig } from './odp_config'; -import { RestApiManager } from './rest_api_manager'; - -const MAX_RETRIES = 3; -const DEFAULT_BATCH_SIZE = 10; -const DEFAULT_FLUSH_INTERVAL_MSECS = 1000; -const DEFAULT_BROWSER_QUEUE_SIZE = 100; -const DEFAULT_SERVER_QUEUE_SIZE = 10000; - -/** - * Event dispatcher's execution states - */ -export enum STATE { - STOPPED, - RUNNING, - PROCESSING, -} - -/** - * Manager for persisting events to the Optimizely Data Platform (ODP) - */ -export interface IOdpEventManager { - updateSettings(odpConfig: OdpConfig): void; - - start(): void; - - stop(): Promise<void>; - - registerVuid(vuid: string): void; - - identifyUser(userId: string, vuid?: string): void; - - sendEvent(event: OdpEvent): void; -} - -/** - * Concrete implementation of a manager for persisting events to the Optimizely Data Platform - */ -export class OdpEventManager implements IOdpEventManager { - /** - * Current state of the event processor - */ - public state: STATE = STATE.STOPPED; - /** - * Queue for holding all events to be eventually dispatched - * @private - */ - private queue = new Array<OdpEvent>(); - /** - * Identifier of the currently running timeout so clearCurrentTimeout() can be called - * @private - */ - private timeoutId?: NodeJS.Timeout | number; - /** - * ODP configuration settings in used - * @private - */ - private odpConfig: OdpConfig; - /** - * REST API Manager used to send the events - * @private - */ - private readonly apiManager: RestApiManager; - /** - * Handler for recording execution logs - * @private - */ - private readonly logger: LogHandler; - /** - * Maximum queue size - * @private - */ - private readonly queueSize: number; - /** - * Maximum number of events to process at once - * @private - */ - private readonly batchSize: number; - /** - * Milliseconds between setTimeout() to process new batches - * @private - */ - private readonly flushInterval: number; - /** - * Type of execution context eg node, js, react - * @private - */ - private readonly clientEngine: string; - /** - * Version of the client being used - * @private - */ - private readonly clientVersion: string; - - public constructor({ - odpConfig, - apiManager, - logger, - clientEngine, - clientVersion, - queueSize, - batchSize, - flushInterval, - }: { - odpConfig: OdpConfig, - apiManager: RestApiManager, - logger: LogHandler, - clientEngine: string, - clientVersion: string, - queueSize?: number, - batchSize?: number, - flushInterval?: number - }) { - this.odpConfig = odpConfig; - this.apiManager = apiManager; - this.logger = logger; - this.clientEngine = clientEngine; - this.clientVersion = clientVersion; - - this.queueSize = queueSize || (process ? DEFAULT_SERVER_QUEUE_SIZE : DEFAULT_BROWSER_QUEUE_SIZE); - this.batchSize = batchSize || DEFAULT_BATCH_SIZE; - this.flushInterval = flushInterval || DEFAULT_FLUSH_INTERVAL_MSECS; - - this.state = STATE.STOPPED; - } - - /** - * Update ODP configuration settings - * @param odpConfig New configuration to apply - */ - public updateSettings(odpConfig: OdpConfig): void { - this.odpConfig = odpConfig; - } - - /** - * Start processing events in the queue - */ - public start(): void { - this.state = STATE.RUNNING; - - this.setNewTimeout(); - } - - /** - * Drain the queue sending all remaining events in batches then stop processing - */ - public async stop(): Promise<void> { - this.logger.log(LogLevel.DEBUG, 'Stop requested.'); - - await this.processQueue(true); - - this.state = STATE.STOPPED; - this.logger.log(LogLevel.DEBUG, 'Stopped. Queue Count: %s', this.queue.length); - } - - /** - * Register a new visitor user id (VUID) in ODP - * @param vuid Visitor User ID to send - */ - public registerVuid(vuid: string): void { - const identifiers = new Map<string, string>(); - identifiers.set(ODP_USER_KEY.VUID, vuid); - - const event = new OdpEvent('fullstack', 'client_initialized', identifiers); - this.sendEvent(event); - } - - /** - * Associate a full-stack userid with an established VUID - * @param userId Full-stack User ID - * @param vuid Visitor User ID - */ - public identifyUser(userId: string, vuid?: string): void { - const identifiers = new Map<string, string>(); - if (vuid) { - identifiers.set(ODP_USER_KEY.VUID, vuid); - } - identifiers.set(ODP_USER_KEY.FS_USER_ID, userId); - - const event = new OdpEvent('fullstack', 'identified', identifiers); - this.sendEvent(event); - } - - /** - * Send an event to ODP via dispatch queue - * @param event ODP Event to forward - */ - public sendEvent(event: OdpEvent): void { - if (this.invalidDataFound(event.data)) { - this.logger.log(LogLevel.ERROR, 'Event data found to be invalid.'); - } else { - event.data = this.augmentCommonData(event.data); - this.enqueue(event); - } - } - - /** - * Add a new event to the main queue - * @param event ODP Event to be queued - * @private - */ - private enqueue(event: OdpEvent): void { - if (this.state === STATE.STOPPED) { - this.logger.log(LogLevel.WARNING, 'Failed to Process ODP Event. ODPEventManager is not running.'); - return; - } - - if (!this.odpConfig.isReady()) { - this.logger.log(LogLevel.DEBUG, 'Unable to Process ODP Event. ODPConfig is not ready.'); - return; - } - - if (this.queue.length >= this.queueSize) { - this.logger.log(LogLevel.WARNING, 'Failed to Process ODP Event. Event Queue full. queueSize = %s.', this.queue.length); - return; - } - - this.queue.push(event); - - this.processQueue(); - } - - /** - * Process events in the main queue - * @param shouldFlush Flush all events regardless of available queue event count - * @private - */ - private processQueue(shouldFlush = false): void { - if (this.state !== STATE.RUNNING) { - return; - } - - if (!this.isOdpConfigurationReady()) { - return; - } - - // Flush interval occurred & queue has items - if (shouldFlush) { - // clear the queue completely - this.clearCurrentTimeout(); - - this.state = STATE.PROCESSING; - - while (this.queueContainsItems()) { - this.makeAndSend1Batch(); - } - } - // Check if queue has a full batch available - else if (this.queueHasBatches()) { - this.clearCurrentTimeout(); - - this.state = STATE.PROCESSING; - - while (this.queueHasBatches()) { - this.makeAndSend1Batch(); - } - } - - this.state = STATE.RUNNING; - this.setNewTimeout(); - } - - /** - * Clear the currently running timout - * @private - */ - private clearCurrentTimeout(): void { - clearTimeout(this.timeoutId); - this.timeoutId = undefined; - } - - /** - * Start a new timeout - * @private - */ - private setNewTimeout(): void { - if (this.timeoutId !== undefined) { - return; - } - this.timeoutId = setTimeout(() => this.processQueue(true), this.flushInterval); - } - - /** - * Make a batch and send it to ODP - * @private - */ - private makeAndSend1Batch(): void { - const batch = new Array<OdpEvent>(); - - // remove a batch from the queue - for (let count = 0; count < this.batchSize; count += 1) { - const event = this.queue.shift(); - if (event) { - batch.push(event); - } else { - break; - } - } - - if (batch.length > 0) { - // put sending the event on another event loop - setTimeout(async () => { - let shouldRetry: boolean; - let attemptNumber = 0; - do { - shouldRetry = await this.apiManager.sendEvents(this.odpConfig.apiKey, this.odpConfig.apiHost, batch); - attemptNumber += 1; - } while (shouldRetry && attemptNumber < MAX_RETRIES); - }); - } - } - - /** - * Check if main queue has any full/even batches available - * @returns True if there are event batches available in the queue otherwise False - * @private - */ - private queueHasBatches(): boolean { - return this.queueContainsItems() && this.queue.length % this.batchSize === 0; - } - - /** - * Check if main queue has any items - * @returns True if there are any events in the queue otherwise False - * @private - */ - private queueContainsItems(): boolean { - return this.queue.length > 0; - } - - /** - * Check if the ODP Configuration is ready and log if not. - * Potentially clear queue if server-side - * @returns True if the ODP configuration is ready otherwise False - * @private - */ - private isOdpConfigurationReady(): boolean { - if (this.odpConfig.isReady()) { - return true; - } - - if (process) { - // if Node/server-side context, empty queue items before ready state - this.logger.log(LogLevel.WARNING, 'ODPConfig not ready. Discarding events in queue.'); - this.queue = new Array<OdpEvent>(); - } else { - // in Browser/client-side context, give debug message but leave events in queue - this.logger.log(LogLevel.DEBUG, 'ODPConfig not ready. Leaving events in queue.'); - } - return false; - } - - /** - * Validate event data value types - * @param data Event data to be validated - * @returns True if an invalid type was found in the data otherwise False - * @private - */ - private invalidDataFound(data: Map<string, unknown>): boolean { - const validTypes: string[] = ['string', 'number', 'boolean']; - let foundInvalidValue = false; - data.forEach((value) => { - if (!validTypes.includes(typeof value) && value !== null) { - foundInvalidValue = true; - } - }); - return foundInvalidValue; - } - - /** - * Add additional common data including an idempotent ID and execution context to event data - * @param sourceData Existing event data to augment - * @returns Augmented event data - * @private - */ - private augmentCommonData(sourceData: Map<string, unknown>): Map<string, unknown> { - const data = new Map<string, unknown>(); - data.set('idempotence_id', uuid()); - data.set('data_source_type', 'sdk'); - data.set('data_source', this.clientEngine); - data.set('data_source_version', this.clientVersion); - - sourceData.forEach((value, key) => data.set(key, value)); - return data; - } -} diff --git a/packages/optimizely-sdk/lib/plugins/odp/rest_api_manager.ts b/packages/optimizely-sdk/lib/plugins/odp/rest_api_manager.ts deleted file mode 100644 index 8a58de202..000000000 --- a/packages/optimizely-sdk/lib/plugins/odp/rest_api_manager.ts +++ /dev/null @@ -1,108 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { LogHandler, LogLevel } from '../../modules/logging'; -import { OdpEvent } from './odp_event'; -import { RequestHandler } from '../../utils/http_request_handler/http'; - -const EVENT_SENDING_FAILURE_MESSAGE = 'ODP event send failed'; - -/** - * Manager for communicating with the Optimizely Data Platform REST API - */ -export interface IRestApiManager { - sendEvents(apiKey: string, apiHost: string, events: OdpEvent[]): Promise<boolean>; -} - -/** - * Concrete implementation for accessing the ODP REST API - */ -export class RestApiManager implements IRestApiManager { - private readonly logger: LogHandler; - private readonly requestHandler: RequestHandler; - - /** - * Creates instance to access Optimizely Data Platform (ODP) REST API - * @param requestHandler Desired request handler for testing - * @param logger Collect and record events/errors for this GraphQL implementation - */ - constructor(requestHandler: RequestHandler, logger: LogHandler) { - this.requestHandler = requestHandler; - this.logger = logger; - } - - /** - * Service for sending ODP events to REST API - * @param apiKey ODP public key - * @param apiHost Host of ODP endpoint - * @param events ODP events to send - * @returns Retry is true - if network or server error (5xx), otherwise false - */ - public async sendEvents(apiKey: string, apiHost: string, events: OdpEvent[]): Promise<boolean> { - let shouldRetry = false; - - if (!apiKey || !apiHost) { - this.logger.log(LogLevel.ERROR, `${EVENT_SENDING_FAILURE_MESSAGE} (Parameters apiKey or apiHost invalid)`); - return shouldRetry; - } - - if (events.length === 0) { - this.logger.log(LogLevel.ERROR, `${EVENT_SENDING_FAILURE_MESSAGE} (no events)`); - return shouldRetry; - } - - const endpoint = `${apiHost}/v3/events`; - const data = JSON.stringify(events, this.replacer); - - const method = 'POST'; - const headers = { - 'Content-Type': 'application/json', - 'x-api-key': apiKey, - }; - - let statusCode = 0; - try { - const request = this.requestHandler.makeRequest(endpoint, headers, method, data); - const response = await request.responsePromise; - statusCode = response.statusCode ?? statusCode; - } catch (err) { - let message = 'network error'; - if (err instanceof Error) { - message = (err as Error).message; - } - this.logger.log(LogLevel.ERROR, `${EVENT_SENDING_FAILURE_MESSAGE} (${message})`); - shouldRetry = true; - } - - if (statusCode >= 400) { - this.logger.log(LogLevel.ERROR, `${EVENT_SENDING_FAILURE_MESSAGE} (${statusCode})`); - } - - if (statusCode >= 500) { - shouldRetry = true; - } - - return shouldRetry; - } - - private replacer(_: unknown, value: unknown) { - if (value instanceof Map) { - return Object.fromEntries(value); - } else { - return value; - } - } -} diff --git a/packages/optimizely-sdk/lib/plugins/vuid_manager/index.ts b/packages/optimizely-sdk/lib/plugins/vuid_manager/index.ts deleted file mode 100644 index 81b5cf4bd..000000000 --- a/packages/optimizely-sdk/lib/plugins/vuid_manager/index.ts +++ /dev/null @@ -1,136 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { uuid } from '../../utils/fns'; -import PersistentKeyValueCache from '../key_value_cache/persistentKeyValueCache'; - -export interface IVuidManager { - readonly vuid: string; -} - -/** - * Manager for creating, persisting, and retrieving a Visitor Unique Identifier - */ -export class VuidManager implements IVuidManager { - /** - * Unique key used within the persistent value cache against which to - * store the VUID - * @private - */ - private _keyForVuid = 'optimizely-vuid'; - - /** - * Prefix used as part of the VUID format - * @private - */ - private readonly _prefix: string = `vuid_`; - - /** - * Current VUID value being used - * @private - */ - private _vuid: string; - - /** - * Get the current VUID value being used - */ - public get vuid(): string { - return this._vuid; - } - - private constructor() { - this._vuid = ''; - } - - /** - * Instance of the VUID Manager - * @private - */ - private static _instance: VuidManager; - - /** - * Gets the current instance of the VUID Manager, initializing if needed - * @param cache Caching mechanism to use for persisting the VUID outside working memory * - * @returns An instance of VuidManager - */ - public static async instance(cache: PersistentKeyValueCache): Promise<VuidManager> { - if (!this._instance) { - this._instance = new VuidManager(); - } - - if (!this._instance._vuid) { - await this._instance.load(cache); - } - - return this._instance; - } - - /** - * Attempts to load a VUID from persistent cache or generates a new VUID - * @param cache Caching mechanism to use for persisting the VUID outside working memory - * @returns Current VUID stored in the VuidManager - */ - private async load(cache: PersistentKeyValueCache): Promise<string> { - const cachedValue = await cache.get(this._keyForVuid); - if (cachedValue && this.isVuid(cachedValue)) { - this._vuid = cachedValue; - } else { - this._vuid = this.makeVuid(); - await this.save(this._vuid, cache); - } - - return this._vuid; - } - - /** - * Creates a new VUID - * @returns A new visitor unique identifier - */ - private makeVuid(): string { - const maxLength = 32; // required by ODP server - - // make sure UUIDv4 is used (not UUIDv1 or UUIDv6) since the trailing 5 chars will be truncated. See TDD for details. - const uuidV4 = uuid(); - const formatted = uuidV4.replace(/-/g, '').toLowerCase(); - const vuidFull = `${(this._prefix)}${formatted}`; - - return (vuidFull.length <= maxLength) ? vuidFull : vuidFull.substring(0, maxLength); - } - - /** - * Saves a VUID to a persistent cache - * @param vuid VUID to be stored - * @param cache Caching mechanism to use for persisting the VUID outside working memory - */ - private async save(vuid: string, cache: PersistentKeyValueCache): Promise<void> { - await cache.set(this._keyForVuid, vuid); - } - - /** - * Validates the format of a Visitor Unique Identifier - * @param vuid VistorId to check - * @returns *true* if the VisitorId is valid otherwise *false* for invalid - */ - private isVuid = (vuid: string): boolean => vuid.startsWith(this._prefix); - - /** - * Function used in unit testing to reset the VuidManager - * **Important**: This should not to be used in production code - */ - private static _reset(): void { - this._instance._vuid = ''; - } -} diff --git a/packages/optimizely-sdk/lib/utils/config_validator/index.ts b/packages/optimizely-sdk/lib/utils/config_validator/index.ts deleted file mode 100644 index a273121cc..000000000 --- a/packages/optimizely-sdk/lib/utils/config_validator/index.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Copyright 2016, 2018-2020, 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { sprintf } from '../../utils/fns'; -import { ObjectWithUnknownProperties } from '../../shared_types'; - -import { - ERROR_MESSAGES, - DATAFILE_VERSIONS, -} from '../enums'; - -const MODULE_NAME = 'CONFIG_VALIDATOR'; -const SUPPORTED_VERSIONS = [DATAFILE_VERSIONS.V2, DATAFILE_VERSIONS.V3, DATAFILE_VERSIONS.V4]; - -/** - * Validates the given config options - * @param {unknown} config - * @param {object} config.errorHandler - * @param {object} config.eventDispatcher - * @param {object} config.logger - * @return {boolean} true if the config options are valid - * @throws If any of the config options are not valid - */ -export const validate = function(config: unknown): boolean { - if (typeof config === 'object' && config !== null) { - const configObj = config as ObjectWithUnknownProperties; - const errorHandler = configObj['errorHandler']; - const eventDispatcher = configObj['eventDispatcher']; - const logger = configObj['logger']; - if (errorHandler && typeof (errorHandler as ObjectWithUnknownProperties)['handleError'] !== 'function') { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_ERROR_HANDLER, MODULE_NAME)); - } - if (eventDispatcher && typeof (eventDispatcher as ObjectWithUnknownProperties)['dispatchEvent'] !== 'function') { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_EVENT_DISPATCHER, MODULE_NAME)); - } - if (logger && typeof (logger as ObjectWithUnknownProperties)['log'] !== 'function') { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_LOGGER, MODULE_NAME)); - } - return true; - } - throw new Error(sprintf(ERROR_MESSAGES.INVALID_CONFIG, MODULE_NAME)); -} - -/** - * Validates the datafile - * @param {Object|string} datafile - * @return {Object} The datafile object if the datafile is valid - * @throws If the datafile is not valid for any of the following reasons: - - The datafile string is undefined - - The datafile string cannot be parsed as a JSON object - - The datafile version is not supported - */ -// eslint-disable-next-line -export const validateDatafile = function(datafile: unknown): any { - if (!datafile) { - throw new Error(sprintf(ERROR_MESSAGES.NO_DATAFILE_SPECIFIED, MODULE_NAME)); - } - if (typeof datafile === 'string') { - // Attempt to parse the datafile string - try { - datafile = JSON.parse(datafile); - } catch (ex) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_DATAFILE_MALFORMED, MODULE_NAME)); - } - } - if (typeof datafile === 'object' && !Array.isArray(datafile) && datafile !== null) { - if (SUPPORTED_VERSIONS.indexOf(datafile['version' as keyof unknown]) === -1) { - throw new Error(sprintf(ERROR_MESSAGES.INVALID_DATAFILE_VERSION, MODULE_NAME, datafile['version' as keyof unknown])); - } - } - - return datafile; -}; - -/** - * Provides utility methods for validating that the configuration options are valid - */ -export default { - validate: validate, - validateDatafile: validateDatafile, -} diff --git a/packages/optimizely-sdk/lib/utils/enums/index.ts b/packages/optimizely-sdk/lib/utils/enums/index.ts deleted file mode 100644 index d00e65b66..000000000 --- a/packages/optimizely-sdk/lib/utils/enums/index.ts +++ /dev/null @@ -1,306 +0,0 @@ -/**************************************************************************** - * Copyright 2016-2022, Optimizely, Inc. and contributors * - * * - * Licensed under the Apache License, Version 2.0 (the "License"); * - * you may not use this file except in compliance with the License. * - * You may obtain a copy of the License at * - * * - * https://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, software * - * distributed under the License is distributed on an "AS IS" BASIS, * - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * - * See the License for the specific language governing permissions and * - * limitations under the License. * - ***************************************************************************/ - -/** - * Contains global enums used throughout the library - */ -export const LOG_LEVEL = { - NOTSET: 0, - DEBUG: 1, - INFO: 2, - WARNING: 3, - ERROR: 4, -}; - -export const ERROR_MESSAGES = { - CONDITION_EVALUATOR_ERROR: '%s: Error evaluating audience condition of type %s: %s', - DATAFILE_AND_SDK_KEY_MISSING: '%s: You must provide at least one of sdkKey or datafile. Cannot start Optimizely', - EXPERIMENT_KEY_NOT_IN_DATAFILE: '%s: Experiment key %s is not in datafile.', - FEATURE_NOT_IN_DATAFILE: '%s: Feature key %s is not in datafile.', - IMPROPERLY_FORMATTED_EXPERIMENT: '%s: Experiment key %s is improperly formatted.', - INVALID_ATTRIBUTES: '%s: Provided attributes are in an invalid format.', - INVALID_BUCKETING_ID: '%s: Unable to generate hash for bucketing ID %s: %s', - INVALID_DATAFILE: '%s: Datafile is invalid - property %s: %s', - INVALID_DATAFILE_MALFORMED: '%s: Datafile is invalid because it is malformed.', - INVALID_CONFIG: '%s: Provided Optimizely config is in an invalid format.', - INVALID_JSON: '%s: JSON object is not valid.', - INVALID_ERROR_HANDLER: '%s: Provided "errorHandler" is in an invalid format.', - INVALID_EVENT_DISPATCHER: '%s: Provided "eventDispatcher" is in an invalid format.', - INVALID_EVENT_TAGS: '%s: Provided event tags are in an invalid format.', - INVALID_EXPERIMENT_KEY: '%s: Experiment key %s is not in datafile. It is either invalid, paused, or archived.', - INVALID_EXPERIMENT_ID: '%s: Experiment ID %s is not in datafile.', - INVALID_GROUP_ID: '%s: Group ID %s is not in datafile.', - INVALID_LOGGER: '%s: Provided "logger" is in an invalid format.', - INVALID_ROLLOUT_ID: '%s: Invalid rollout ID %s attached to feature %s', - INVALID_USER_ID: '%s: Provided user ID is in an invalid format.', - INVALID_USER_PROFILE_SERVICE: '%s: Provided user profile service instance is in an invalid format: %s.', - NO_DATAFILE_SPECIFIED: '%s: No datafile specified. Cannot start optimizely.', - NO_JSON_PROVIDED: '%s: No JSON object to validate against schema.', - NO_VARIATION_FOR_EXPERIMENT_KEY: '%s: No variation key %s defined in datafile for experiment %s.', - UNDEFINED_ATTRIBUTE: '%s: Provided attribute: %s has an undefined value.', - UNRECOGNIZED_ATTRIBUTE: '%s: Unrecognized attribute %s provided. Pruning before sending event to Optimizely.', - UNABLE_TO_CAST_VALUE: '%s: Unable to cast value %s to type %s, returning null.', - USER_NOT_IN_FORCED_VARIATION: '%s: User %s is not in the forced variation map. Cannot remove their forced variation.', - USER_PROFILE_LOOKUP_ERROR: '%s: Error while looking up user profile for user ID "%s": %s.', - USER_PROFILE_SAVE_ERROR: '%s: Error while saving user profile for user ID "%s": %s.', - VARIABLE_KEY_NOT_IN_DATAFILE: '%s: Variable with key "%s" associated with feature with key "%s" is not in datafile.', - VARIATION_ID_NOT_IN_DATAFILE: '%s: No variation ID %s defined in datafile for experiment %s.', - VARIATION_ID_NOT_IN_DATAFILE_NO_EXPERIMENT: '%s: Variation ID %s is not in the datafile.', - INVALID_INPUT_FORMAT: '%s: Provided %s is in an invalid format.', - INVALID_DATAFILE_VERSION: '%s: This version of the JavaScript SDK does not support the given datafile version: %s', - INVALID_VARIATION_KEY: '%s: Provided variation key is in an invalid format.', -}; - -export const LOG_MESSAGES = { - ACTIVATE_USER: '%s: Activating user %s in experiment %s.', - DISPATCH_CONVERSION_EVENT: '%s: Dispatching conversion event to URL %s with params %s.', - DISPATCH_IMPRESSION_EVENT: '%s: Dispatching impression event to URL %s with params %s.', - DEPRECATED_EVENT_VALUE: '%s: Event value is deprecated in %s call.', - EVENT_KEY_NOT_FOUND: '%s: Event key %s is not in datafile.', - EXPERIMENT_NOT_RUNNING: '%s: Experiment %s is not running.', - FEATURE_ENABLED_FOR_USER: '%s: Feature %s is enabled for user %s.', - FEATURE_NOT_ENABLED_FOR_USER: '%s: Feature %s is not enabled for user %s.', - FEATURE_HAS_NO_EXPERIMENTS: '%s: Feature %s is not attached to any experiments.', - FAILED_TO_PARSE_VALUE: '%s: Failed to parse event value "%s" from event tags.', - FAILED_TO_PARSE_REVENUE: '%s: Failed to parse revenue value "%s" from event tags.', - FORCED_BUCKETING_FAILED: '%s: Variation key %s is not in datafile. Not activating user %s.', - INVALID_OBJECT: '%s: Optimizely object is not valid. Failing %s.', - INVALID_CLIENT_ENGINE: '%s: Invalid client engine passed: %s. Defaulting to node-sdk.', - INVALID_DEFAULT_DECIDE_OPTIONS: '%s: Provided default decide options is not an array.', - INVALID_DECIDE_OPTIONS: '%s: Provided decide options is not an array. Using default decide options.', - INVALID_VARIATION_ID: '%s: Bucketed into an invalid variation ID. Returning null.', - NOTIFICATION_LISTENER_EXCEPTION: '%s: Notification listener for (%s) threw exception: %s', - NO_ROLLOUT_EXISTS: '%s: There is no rollout of feature %s.', - NOT_ACTIVATING_USER: '%s: Not activating user %s for experiment %s.', - NOT_TRACKING_USER: '%s: Not tracking user %s.', - PARSED_REVENUE_VALUE: '%s: Parsed revenue value "%s" from event tags.', - PARSED_NUMERIC_VALUE: '%s: Parsed event value "%s" from event tags.', - RETURNING_STORED_VARIATION: - '%s: Returning previously activated variation "%s" of experiment "%s" for user "%s" from user profile.', - ROLLOUT_HAS_NO_EXPERIMENTS: '%s: Rollout of feature %s has no experiments', - SAVED_VARIATION: '%s: Saved variation "%s" of experiment "%s" for user "%s".', - SAVED_VARIATION_NOT_FOUND: - '%s: User %s was previously bucketed into variation with ID %s for experiment %s, but no matching variation was found.', - SHOULD_NOT_DISPATCH_ACTIVATE: '%s: Experiment %s is not in "Running" state. Not activating user.', - SKIPPING_JSON_VALIDATION: '%s: Skipping JSON schema validation.', - TRACK_EVENT: '%s: Tracking event %s for user %s.', - UNRECOGNIZED_DECIDE_OPTION: '%s: Unrecognized decide option %s provided.', - USER_ASSIGNED_TO_EXPERIMENT_BUCKET: '%s: Assigned bucket %s to user with bucketing ID %s.', - USER_BUCKETED_INTO_EXPERIMENT_IN_GROUP: '%s: User %s is in experiment %s of group %s.', - USER_BUCKETED_INTO_TARGETING_RULE: '%s: User %s bucketed into targeting rule %s.', - USER_IN_FEATURE_EXPERIMENT: '%s: User %s is in variation %s of experiment %s on the feature %s.', - USER_IN_ROLLOUT: '%s: User %s is in rollout of feature %s.', - USER_NOT_BUCKETED_INTO_EVERYONE_TARGETING_RULE: - '%s: User %s not bucketed into everyone targeting rule due to traffic allocation.', - USER_NOT_BUCKETED_INTO_EXPERIMENT_IN_GROUP: '%s: User %s is not in experiment %s of group %s.', - USER_NOT_BUCKETED_INTO_ANY_EXPERIMENT_IN_GROUP: '%s: User %s is not in any experiment of group %s.', - USER_NOT_BUCKETED_INTO_TARGETING_RULE: - '%s User %s not bucketed into targeting rule %s due to traffic allocation. Trying everyone rule.', - USER_NOT_IN_FEATURE_EXPERIMENT: '%s: User %s is not in any experiment on the feature %s.', - USER_NOT_IN_ROLLOUT: '%s: User %s is not in rollout of feature %s.', - USER_FORCED_IN_VARIATION: '%s: User %s is forced in variation %s.', - USER_MAPPED_TO_FORCED_VARIATION: '%s: Set variation %s for experiment %s and user %s in the forced variation map.', - USER_DOESNT_MEET_CONDITIONS_FOR_TARGETING_RULE: '%s: User %s does not meet conditions for targeting rule %s.', - USER_MEETS_CONDITIONS_FOR_TARGETING_RULE: '%s: User %s meets conditions for targeting rule %s.', - USER_HAS_VARIATION: '%s: User %s is in variation %s of experiment %s.', - USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED: 'Variation (%s) is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.', - USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED: 'Variation (%s) is mapped to flag (%s) and user (%s) in the forced decision map.', - USER_HAS_FORCED_DECISION_WITH_RULE_SPECIFIED_BUT_INVALID: 'Invalid variation is mapped to flag (%s), rule (%s) and user (%s) in the forced decision map.', - USER_HAS_FORCED_DECISION_WITH_NO_RULE_SPECIFIED_BUT_INVALID: 'Invalid variation is mapped to flag (%s) and user (%s) in the forced decision map.', - USER_HAS_FORCED_VARIATION: '%s: Variation %s is mapped to experiment %s and user %s in the forced variation map.', - USER_HAS_NO_VARIATION: '%s: User %s is in no variation of experiment %s.', - USER_HAS_NO_FORCED_VARIATION: '%s: User %s is not in the forced variation map.', - USER_HAS_NO_FORCED_VARIATION_FOR_EXPERIMENT: '%s: No experiment %s mapped to user %s in the forced variation map.', - USER_NOT_IN_ANY_EXPERIMENT: '%s: User %s is not in any experiment of group %s.', - USER_NOT_IN_EXPERIMENT: '%s: User %s does not meet conditions to be in experiment %s.', - USER_RECEIVED_DEFAULT_VARIABLE_VALUE: - '%s: User "%s" is not in any variation or rollout rule. Returning default value for variable "%s" of feature flag "%s".', - FEATURE_NOT_ENABLED_RETURN_DEFAULT_VARIABLE_VALUE: - '%s: Feature "%s" is not enabled for user %s. Returning the default variable value "%s".', - VARIABLE_NOT_USED_RETURN_DEFAULT_VARIABLE_VALUE: - '%s: Variable "%s" is not used in variation "%s". Returning default value.', - USER_RECEIVED_VARIABLE_VALUE: '%s: Got variable value "%s" for variable "%s" of feature flag "%s"', - VALID_DATAFILE: '%s: Datafile is valid.', - VALID_USER_PROFILE_SERVICE: '%s: Valid user profile service provided.', - VARIATION_REMOVED_FOR_USER: '%s: Variation mapped to experiment %s has been removed for user %s.', - VARIABLE_REQUESTED_WITH_WRONG_TYPE: - '%s: Requested variable type "%s", but variable is of type "%s". Use correct API to retrieve value. Returning None.', - VALID_BUCKETING_ID: '%s: BucketingId is valid: "%s"', - BUCKETING_ID_NOT_STRING: '%s: BucketingID attribute is not a string. Defaulted to userId', - EVALUATING_AUDIENCE: '%s: Starting to evaluate audience "%s" with conditions: %s.', - EVALUATING_AUDIENCES_COMBINED: '%s: Evaluating audiences for %s "%s": %s.', - AUDIENCE_EVALUATION_RESULT: '%s: Audience "%s" evaluated to %s.', - AUDIENCE_EVALUATION_RESULT_COMBINED: '%s: Audiences for %s %s collectively evaluated to %s.', - MISSING_ATTRIBUTE_VALUE: - '%s: Audience condition %s evaluated to UNKNOWN because no value was passed for user attribute "%s".', - UNEXPECTED_CONDITION_VALUE: - '%s: Audience condition %s evaluated to UNKNOWN because the condition value is not supported.', - UNEXPECTED_TYPE: - '%s: Audience condition %s evaluated to UNKNOWN because a value of type "%s" was passed for user attribute "%s".', - UNEXPECTED_TYPE_NULL: - '%s: Audience condition %s evaluated to UNKNOWN because a null value was passed for user attribute "%s".', - UNKNOWN_CONDITION_TYPE: - '%s: Audience condition %s has an unknown condition type. You may need to upgrade to a newer release of the Optimizely SDK.', - UNKNOWN_MATCH_TYPE: - '%s: Audience condition %s uses an unknown match type. You may need to upgrade to a newer release of the Optimizely SDK.', - UPDATED_OPTIMIZELY_CONFIG: '%s: Updated Optimizely config to revision %s (project id %s)', - OUT_OF_BOUNDS: - '%s: Audience condition %s evaluated to UNKNOWN because the number value for user attribute "%s" is not in the range [-2^53, +2^53].', - UNABLE_TO_ATTACH_UNLOAD: '%s: unable to bind optimizely.close() to page unload event: "%s"', -}; - -export const enum RESERVED_EVENT_KEYWORDS { - REVENUE = 'revenue', - VALUE = 'value', -} - -export const CONTROL_ATTRIBUTES = { - BOT_FILTERING: '$opt_bot_filtering', - BUCKETING_ID: '$opt_bucketing_id', - STICKY_BUCKETING_KEY: '$opt_experiment_bucket_map', - USER_AGENT: '$opt_user_agent', - FORCED_DECISION_NULL_RULE_KEY: '$opt_null_rule_key' -}; - -export const JAVASCRIPT_CLIENT_ENGINE = 'javascript-sdk'; -export const NODE_CLIENT_ENGINE = 'node-sdk'; -export const REACT_CLIENT_ENGINE = 'react-sdk'; -export const REACT_NATIVE_CLIENT_ENGINE = 'react-native-sdk'; -export const REACT_NATIVE_JS_CLIENT_ENGINE = 'react-native-js-sdk'; -export const NODE_CLIENT_VERSION = '4.9.2'; - -export const DECISION_NOTIFICATION_TYPES = { - AB_TEST: 'ab-test', - FEATURE: 'feature', - FEATURE_TEST: 'feature-test', - FEATURE_VARIABLE: 'feature-variable', - ALL_FEATURE_VARIABLES: 'all-feature-variables', - FLAG: 'flag', -}; - -/* - * Represents the source of a decision for feature management. When a feature - * is accessed through isFeatureEnabled or getVariableValue APIs, the decision - * source is used to decide whether to dispatch an impression event to - * Optimizely. - */ -export const DECISION_SOURCES = { - FEATURE_TEST: 'feature-test', - ROLLOUT: 'rollout', - EXPERIMENT: 'experiment', -}; - -export const AUDIENCE_EVALUATION_TYPES = { - RULE: 'rule', - EXPERIMENT: 'experiment', -}; - -/* - * Possible types of variables attached to features - */ -export const FEATURE_VARIABLE_TYPES = { - BOOLEAN: 'boolean', - DOUBLE: 'double', - INTEGER: 'integer', - STRING: 'string', - JSON: 'json', -}; - -/* - * Supported datafile versions - */ -export const DATAFILE_VERSIONS = { - V2: '2', - V3: '3', - V4: '4', -}; - -/* - * Pre-Release and Build symbols - */ -export const enum VERSION_TYPE { - PRE_RELEASE_VERSION_DELIMITER = '-', - BUILD_VERSION_DELIMITER = '+' -} - -export const DECISION_MESSAGES = { - SDK_NOT_READY: 'Optimizely SDK not configured properly yet.', - FLAG_KEY_INVALID: 'No flag was found for key "%s".', - VARIABLE_VALUE_INVALID: 'Variable value for key "%s" is invalid or wrong type.', -} - -/* -* Notification types for use with NotificationCenter -* Format is EVENT: <list of parameters to callback> -* -* SDK consumers can use these to register callbacks with the notification center. -* -* @deprecated since 3.1.0 -* ACTIVATE: An impression event will be sent to Optimizely -* Callbacks will receive an object argument with the following properties: -* - experiment {Object} -* - userId {string} -* - attributes {Object|undefined} -* - variation {Object} -* - logEvent {Object} -* -* DECISION: A decision is made in the system. i.e. user activation, -* feature access or feature-variable value retrieval -* Callbacks will receive an object argument with the following properties: -* - type {string} -* - userId {string} -* - attributes {Object|undefined} -* - decisionInfo {Object|undefined} -* -* LOG_EVENT: A batch of events, which could contain impressions and/or conversions, -* will be sent to Optimizely -* Callbacks will receive an object argument with the following properties: -* - url {string} -* - httpVerb {string} -* - params {Object} -* -* OPTIMIZELY_CONFIG_UPDATE: This Optimizely instance has been updated with a new -* config -* -* TRACK: A conversion event will be sent to Optimizely -* Callbacks will receive the an object argument with the following properties: -* - eventKey {string} -* - userId {string} -* - attributes {Object|undefined} -* - eventTags {Object|undefined} -* - logEvent {Object} -* -*/ -export enum NOTIFICATION_TYPES { - ACTIVATE = 'ACTIVATE:experiment, user_id,attributes, variation, event', - DECISION = 'DECISION:type, userId, attributes, decisionInfo', - LOG_EVENT = 'LOG_EVENT:logEvent', - OPTIMIZELY_CONFIG_UPDATE = 'OPTIMIZELY_CONFIG_UPDATE', - TRACK = 'TRACK:event_key, user_id, attributes, event_tags, event', -} - -/** - * Default milliseconds before request timeout - */ -export const REQUEST_TIMEOUT_MS = 60 * 1000; // 1 minute - -/** - * ODP User Key - */ -export enum ODP_USER_KEY { - VUID = 'vuid', - FS_USER_ID = 'fs_user_id', -} diff --git a/packages/optimizely-sdk/lib/utils/event_processor_config_validator/index.tests.js b/packages/optimizely-sdk/lib/utils/event_processor_config_validator/index.tests.js deleted file mode 100644 index 6ecc6a134..000000000 --- a/packages/optimizely-sdk/lib/utils/event_processor_config_validator/index.tests.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { assert } from 'chai'; - -import eventProcessorConfigValidator from './index'; - -describe('utils/event_processor_config_validator', function() { - describe('validateEventFlushInterval', function() { - it('returns false for null & undefined', function() { - assert.isFalse(eventProcessorConfigValidator.validateEventFlushInterval(null)); - assert.isFalse(eventProcessorConfigValidator.validateEventFlushInterval(undefined)); - }); - - it('returns false for a string', function() { - assert.isFalse(eventProcessorConfigValidator.validateEventFlushInterval('not a number')); - }); - - it('returns false for an object', function() { - assert.isFalse(eventProcessorConfigValidator.validateEventFlushInterval({ value: 'not a number' })); - }); - - it('returns false for a negative integer', function() { - assert.isFalse(eventProcessorConfigValidator.validateEventFlushInterval(-1000)); - }); - - it('returns false for 0', function() { - assert.isFalse(eventProcessorConfigValidator.validateEventFlushInterval(0)); - }); - - it('returns true for a positive integer', function() { - assert.isTrue(eventProcessorConfigValidator.validateEventFlushInterval(30000)); - }); - }); - - describe('validateEventBatchSize', function() { - it('returns false for null & undefined', function() { - assert.isFalse(eventProcessorConfigValidator.validateEventBatchSize(null)); - assert.isFalse(eventProcessorConfigValidator.validateEventBatchSize(undefined)); - }); - - it('returns false for a string', function() { - assert.isFalse(eventProcessorConfigValidator.validateEventBatchSize('not a number')); - }); - - it('returns false for an object', function() { - assert.isFalse(eventProcessorConfigValidator.validateEventBatchSize({ value: 'not a number' })); - }); - - it('returns false for a negative integer', function() { - assert.isFalse(eventProcessorConfigValidator.validateEventBatchSize(-1000)); - }); - - it('returns false for 0', function() { - assert.isFalse(eventProcessorConfigValidator.validateEventBatchSize(0)); - }); - - it('returns true for a positive integer', function() { - assert.isTrue(eventProcessorConfigValidator.validateEventBatchSize(10)); - }); - }); -}); diff --git a/packages/optimizely-sdk/lib/utils/event_processor_config_validator/index.ts b/packages/optimizely-sdk/lib/utils/event_processor_config_validator/index.ts deleted file mode 100644 index e6bd304bb..000000000 --- a/packages/optimizely-sdk/lib/utils/event_processor_config_validator/index.ts +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright 2019-2020, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import fns from '../fns'; - -/** - * Return true if the argument is a valid event batch size, false otherwise - * @param {unknown} eventBatchSize - * @returns {boolean} - */ -const validateEventBatchSize = function(eventBatchSize: unknown): boolean { - if (typeof eventBatchSize === 'number' && fns.isSafeInteger(eventBatchSize)) { - return eventBatchSize >= 1; - } - return false; -} - -/** - * Return true if the argument is a valid event flush interval, false otherwise - * @param {unknown} eventFlushInterval - * @returns {boolean} - */ -const validateEventFlushInterval = function(eventFlushInterval: unknown): boolean { - if (typeof eventFlushInterval === 'number' && fns.isSafeInteger(eventFlushInterval)) { - return eventFlushInterval > 0; - } - return false; -} - -export default { - validateEventBatchSize: validateEventBatchSize, - validateEventFlushInterval: validateEventFlushInterval, -} diff --git a/packages/optimizely-sdk/lib/utils/event_tag_utils/index.ts b/packages/optimizely-sdk/lib/utils/event_tag_utils/index.ts deleted file mode 100644 index 4fbc60597..000000000 --- a/packages/optimizely-sdk/lib/utils/event_tag_utils/index.ts +++ /dev/null @@ -1,88 +0,0 @@ -/** - * Copyright 2017, 2019-2020, 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { EventTags } from '../../../lib/modules/event_processor'; -import { LoggerFacade } from '../../modules/logging'; - -import { - LOG_LEVEL, - LOG_MESSAGES, - RESERVED_EVENT_KEYWORDS, -} from '../enums'; - -/** - * Provides utility method for parsing event tag values - */ -const MODULE_NAME = 'EVENT_TAG_UTILS'; -const REVENUE_EVENT_METRIC_NAME = RESERVED_EVENT_KEYWORDS.REVENUE; -const VALUE_EVENT_METRIC_NAME = RESERVED_EVENT_KEYWORDS.VALUE; - -/** - * Grab the revenue value from the event tags. "revenue" is a reserved keyword. - * @param {EventTags} eventTags - * @param {LoggerFacade} logger - * @return {number|null} - */ -export function getRevenueValue(eventTags: EventTags, logger: LoggerFacade): number | null { - if (eventTags.hasOwnProperty(REVENUE_EVENT_METRIC_NAME)) { - const rawValue = eventTags[REVENUE_EVENT_METRIC_NAME]; - let parsedRevenueValue; - if (typeof rawValue === 'string') { - parsedRevenueValue = parseInt(rawValue); - if (isNaN(parsedRevenueValue)) { - logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.FAILED_TO_PARSE_REVENUE, MODULE_NAME, rawValue); - return null; - } - logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.PARSED_REVENUE_VALUE, MODULE_NAME, parsedRevenueValue); - return parsedRevenueValue; - } - if (typeof rawValue === 'number') { - parsedRevenueValue = rawValue; - logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.PARSED_REVENUE_VALUE, MODULE_NAME, parsedRevenueValue); - return parsedRevenueValue; - } - return null; - } - return null; -} - -/** - * Grab the event value from the event tags. "value" is a reserved keyword. - * @param {EventTags} eventTags - * @param {LoggerFacade} logger - * @return {number|null} - */ -export function getEventValue(eventTags: EventTags, logger: LoggerFacade): number | null { - if (eventTags.hasOwnProperty(VALUE_EVENT_METRIC_NAME)) { - const rawValue = eventTags[VALUE_EVENT_METRIC_NAME]; - let parsedEventValue; - if (typeof rawValue === 'string') { - parsedEventValue = parseFloat(rawValue); - if (isNaN(parsedEventValue)) { - logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.FAILED_TO_PARSE_VALUE, MODULE_NAME, rawValue); - return null; - } - logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.PARSED_NUMERIC_VALUE, MODULE_NAME, parsedEventValue); - return parsedEventValue; - } - if (typeof rawValue === 'number') { - parsedEventValue = rawValue; - logger.log(LOG_LEVEL.INFO, LOG_MESSAGES.PARSED_NUMERIC_VALUE, MODULE_NAME, parsedEventValue); - return parsedEventValue; - } - return null; - } - return null; -} diff --git a/packages/optimizely-sdk/lib/utils/fns/index.ts b/packages/optimizely-sdk/lib/utils/fns/index.ts deleted file mode 100644 index c769f147b..000000000 --- a/packages/optimizely-sdk/lib/utils/fns/index.ts +++ /dev/null @@ -1,171 +0,0 @@ -/** - * Copyright 2017, 2019-2020, 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { v4 } from 'uuid'; - -const MAX_SAFE_INTEGER_LIMIT = Math.pow(2, 53); - -// eslint-disable-next-line -export function assign(target: any, ...sources: any[]): any { - if (!target) { - return {}; - } - if (typeof Object.assign === 'function') { - return Object.assign(target, ...sources); - } else { - const to = Object(target); - for (let index = 0; index < sources.length; index++) { - const nextSource = sources[index]; - if (nextSource !== null && nextSource !== undefined) { - for (const nextKey in nextSource) { - // Avoid bugs when hasOwnProperty is shadowed - if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { - to[nextKey] = nextSource[nextKey]; - } - } - } - } - return to; - } -} - -function currentTimestamp(): number { - return Math.round(new Date().getTime()); -} - -function isSafeInteger(number: unknown): boolean { - return typeof number == 'number' && Math.abs(number) <= MAX_SAFE_INTEGER_LIMIT; -} - -export function keyBy<K>(arr: K[], key: string): { [key: string]: K } { - if (!arr) return {}; - return keyByUtil(arr, function (item) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return (item as any)[key]; - }); -} - -function isNumber(value: unknown): boolean { - return typeof value === 'number'; -} - -export function uuid(): string { - return v4() -} - -export type Omit<T, K> = Pick<T, Exclude<keyof T, K>> - -export function getTimestamp(): number { - return new Date().getTime() -} - -/** -* Validates a value is a valid TypeScript enum -* -* @export -* @param {object} enumToCheck -* @param {*} value -* @returns {boolean} -*/ -// TODO[OASIS-6649]: Don't use any type -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function isValidEnum(enumToCheck: { [key: string]: any }, value: number | string): boolean { - let found = false - - const keys = Object.keys(enumToCheck) - for (let index = 0; index < keys.length; index++) { - if (value === enumToCheck[keys[index]]) { - found = true - break - } - } - return found -} - -export function groupBy<K>(arr: K[], grouperFn: (item: K) => string): Array<K[]> { - const grouper: { [key: string]: K[] } = {} - - arr.forEach(item => { - const key = grouperFn(item) - grouper[key] = grouper[key] || [] - grouper[key].push(item) - }) - - return objectValues(grouper) -} - -export function objectValues<K>(obj: { [key: string]: K }): K[] { - return Object.keys(obj).map(key => obj[key]) -} - -export function objectEntries<K>(obj: { [key: string]: K }): [string, K][] { - return Object.keys(obj).map(key => [key, obj[key]]) -} - -export function find<K>(arr: K[], cond: (arg: K) => boolean): K | undefined { - let found - - for (const item of arr) { - if (cond(item)) { - found = item - break - } - } - - return found -} - -export function keyByUtil<K>(arr: K[], keyByFn: (item: K) => string): { [key: string]: K } { - const map: { [key: string]: K } = {} - arr.forEach(item => { - const key = keyByFn(item) - map[key] = item - }) - return map -} - -// TODO[OASIS-6649]: Don't use any type -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function sprintf(format: string, ...args: any[]): string { - let i = 0 - return format.replace(/%s/g, function() { - const arg = args[i++]; - const type = typeof arg; - if (type === 'function') { - return arg(); - } else if (type === 'string') { - return arg; - } else { - return String(arg); - } - }) -} - -export default { - assign, - currentTimestamp, - isSafeInteger, - keyBy, - uuid, - isNumber, - getTimestamp, - isValidEnum, - groupBy, - objectValues, - objectEntries, - find, - keyByUtil, - sprintf, -}; diff --git a/packages/optimizely-sdk/package-lock.json b/packages/optimizely-sdk/package-lock.json deleted file mode 100644 index 276e04757..000000000 --- a/packages/optimizely-sdk/package-lock.json +++ /dev/null @@ -1,36755 +0,0 @@ -{ - "name": "@optimizely/optimizely-sdk", - "version": "4.9.2", - "lockfileVersion": 2, - "requires": true, - "packages": { - "": { - "name": "@optimizely/optimizely-sdk", - "version": "4.9.2", - "license": "Apache-2.0", - "dependencies": { - "@optimizely/js-sdk-datafile-manager": "^0.9.5", - "decompress-response": "^4.2.1", - "json-schema": "^0.4.0", - "murmurhash": "^2.0.1", - "uuid": "^8.3.2" - }, - "devDependencies": { - "@react-native-async-storage/async-storage": "^1.2.0", - "@react-native-community/netinfo": "^5.9.10", - "@rollup/plugin-commonjs": "^11.0.2", - "@rollup/plugin-node-resolve": "^7.1.1", - "@types/chai": "^4.2.11", - "@types/jest": "^23.3.14", - "@types/mocha": "^5.2.7", - "@types/nise": "^1.4.0", - "@types/node": "^18.7.18", - "@types/uuid": "^3.4.4", - "@typescript-eslint/eslint-plugin": "^5.33.0", - "@typescript-eslint/parser": "^5.33.0", - "bluebird": "^3.4.6", - "chai": "^4.2.0", - "coveralls": "^3.0.2", - "eslint": "^8.21.0", - "eslint-config-prettier": "^6.10.0", - "eslint-plugin-prettier": "^3.1.2", - "jest": "^28.1.3", - "jest-environment-jsdom": "^29.0.0", - "jest-localstorage-mock": "^2.4.22", - "json-loader": "^0.5.4", - "karma": "^6.4.0", - "karma-browserstack-launcher": "^1.5.1", - "karma-chai": "^0.1.0", - "karma-chrome-launcher": "^2.1.1", - "karma-mocha": "^1.3.0", - "karma-webpack": "^5.0.0", - "lodash": "^4.17.11", - "mocha": "^5.2.0", - "mocha-lcov-reporter": "^1.3.0", - "nise": "^1.4.10", - "nock": "11.9.1", - "nyc": "^15.0.1", - "prettier": "^1.19.1", - "promise-polyfill": "8.1.0", - "rollup": "2.2.0", - "rollup-plugin-terser": "^5.3.0", - "rollup-plugin-typescript2": "^0.27.1", - "sinon": "^2.3.1", - "ts-jest": "^28.0.8", - "ts-loader": "^9.3.1", - "ts-mockito": "^2.6.1", - "ts-node": "^8.10.2", - "tslib": "^2.4.0", - "typescript": "^4.7.4", - "webpack": "^5.74.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "@babel/runtime": "^7.0.0", - "@react-native-async-storage/async-storage": "^1.2.0", - "@react-native-community/netinfo": "5.9.4" - }, - "peerDependenciesMeta": { - "@react-native-async-storage/async-storage": { - "optional": true - }, - "@react-native-community/netinfo": { - "optional": true - } - } - }, - "node_modules/@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "devOptional": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "devOptional": true, - "dependencies": { - "@babel/highlight": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.1.tgz", - "integrity": "sha512-72a9ghR0gnESIa7jBN53U32FOVCEoztyIlKaNoU05zRhEecduGK9L9c3ww7Mp06JiR+0ls0GBPFJQwwtjn9ksg==", - "devOptional": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.13.tgz", - "integrity": "sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==", - "devOptional": true, - "dependencies": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.13", - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-module-transforms": "^7.18.9", - "@babel/helpers": "^7.18.9", - "@babel/parser": "^7.18.13", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.18.13", - "@babel/types": "^7.18.13", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/core/node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", - "devOptional": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "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==", - "devOptional": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/generator": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.0.tgz", - "integrity": "sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==", - "devOptional": true, - "dependencies": { - "@babel/types": "^7.19.0", - "@jridgewell/gen-mapping": "^0.3.2", - "jsesc": "^2.5.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "devOptional": 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/@babel/generator/node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "devOptional": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", - "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.1.tgz", - "integrity": "sha512-LlLkkqhCMyz2lkQPvJNdIYU7O5YjWRgC2R4omjCTpZd8u8KMQzZvX4qce+/BluN1rcQiV7BoGUpmQ0LeHerbhg==", - "devOptional": true, - "dependencies": { - "@babel/compat-data": "^7.19.1", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "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==", - "devOptional": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz", - "integrity": "sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz", - "integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", - "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0-0" - } - }, - "node_modules/@babel/helper-define-polyfill-provider/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", - "devOptional": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", - "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", - "devOptional": true, - "dependencies": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" - }, - "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==", - "devOptional": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", - "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "devOptional": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz", - "integrity": "sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==", - "devOptional": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.18.6", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", - "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==", - "devOptional": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", - "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-wrap-function": "^7.18.9", - "@babel/types": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", - "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/traverse": "^7.19.1", - "@babel/types": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", - "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", - "devOptional": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz", - "integrity": "sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/types": "^7.18.9" - }, - "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==", - "devOptional": true, - "dependencies": { - "@babel/types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", - "devOptional": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", - "devOptional": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", - "devOptional": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz", - "integrity": "sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-function-name": "^7.19.0", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz", - "integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==", - "devOptional": true, - "dependencies": { - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.9", - "@babel/types": "^7.18.9" - }, - "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==", - "devOptional": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.1.tgz", - "integrity": "sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==", - "devOptional": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", - "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", - "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-proposal-optional-chaining": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" - } - }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.1.tgz", - "integrity": "sha512-0yu8vNATgLy4ivqMNBIwb1HebCelqN7YX8SL3FDXORv/RqT0zEEWUCH4GH44JsSrvCu6GqnAdR5EBFAPeNBB4Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-class-static-block": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", - "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" - } - }, - "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-export-default-from": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.18.10.tgz", - "integrity": "sha512-5H2N3R2aQFxkV4PIBUR/i7PUSwgTZjouJKzI8eKswfIjT0PhvzkPn0t0wIS5zn6maQuvtT0t1oHtMUz61LOuow==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-default-from": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", - "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz", - "integrity": "sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/compat-data": "^7.18.8", - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.18.8" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", - "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", - "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=4" - }, - "peerDependencies": { - "@babel/core": "^7.0.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==", - "devOptional": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-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==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "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==", - "devOptional": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.12.13" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-default-from": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.18.6.tgz", - "integrity": "sha512-Kr//z3ujSVNx6E9z9ih5xXXMqK07VVTuqPmqGe6Mss/zW5XPeLZeSDZoP9ab/hT4wPKqAgjl2PnhPrcpk8Seew==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-flow": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz", - "integrity": "sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz", - "integrity": "sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "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==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "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==", - "devOptional": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "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==", - "devOptional": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "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==", - "devOptional": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "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==", - "devOptional": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "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==", - "devOptional": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "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==", - "devOptional": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "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==", - "devOptional": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "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==", - "devOptional": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", - "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", - "devOptional": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", - "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", - "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-remap-async-to-generator": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", - "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz", - "integrity": "sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz", - "integrity": "sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-compilation-targets": "^7.19.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-classes/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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", - "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.13.tgz", - "integrity": "sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", - "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", - "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", - "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-flow-strip-types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.19.0.tgz", - "integrity": "sha512-sgeMlNaQVbCSpgLSKP4ZZKfsJVnFnNQlUSk6gPYzR/q7tzCgQF2t8RBKAP6cKJeZdveei7Q7Jm527xepI8lNLg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-flow": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", - "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", - "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", - "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz", - "integrity": "sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz", - "integrity": "sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.0.tgz", - "integrity": "sha512-x9aiR0WXAWmOWsqcsnrzGR+ieaTMVyGyffPVA7F8cXAGt/UxefYv6uSHZLkAFChN5M5Iy1+wjE+xJuPt22H39A==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-module-transforms": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-validator-identifier": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz", - "integrity": "sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", - "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz", - "integrity": "sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", - "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-display-name": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", - "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz", - "integrity": "sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-jsx": "^7.18.6", - "@babel/types": "^7.19.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.18.6.tgz", - "integrity": "sha512-A0LQGx4+4Jv7u/tWzoJF7alZwnBDQd6cGLh9P+Ttk4dpiL+J5p7NSNv/9tlEFFJDq3kjxOavWmbm6t0Gk+A3Ig==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.18.6.tgz", - "integrity": "sha512-utZmlASneDfdaMh0m/WausbjUjEdGrQJz0vFK93d7wD3xf5wBtX219+q6IlCNZeguIcxS2f/CvLZrlLSvSHQXw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", - "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "regenerator-transform": "^0.15.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", - "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.1.tgz", - "integrity": "sha512-2nJjTUFIzBMP/f/miLxEK9vxwW/KUXsdvN4sR//TmuDhe6yU2h57WmIOE12Gng3MDP/xpjUV/ToZRdcf8Yj4fA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-runtime/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", - "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", - "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", - "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", - "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", - "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.19.1.tgz", - "integrity": "sha512-+ILcOU+6mWLlvCwnL920m2Ow3wWx3Wo8n2t5aROQmV55GZt+hOiLvBaa3DNzRjSEHa1aauRs4/YLmkCfFkhhRQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-typescript": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", - "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.9" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", - "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.19.1.tgz", - "integrity": "sha512-c8B2c6D16Lp+Nt6HcD+nHl0VbPKVnNPTpszahuxJJnurfMtKeZ80A+qUv48Y7wqvS+dTFuLuaM9oYxyNHbCLWA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/compat-data": "^7.19.1", - "@babel/helper-compilation-targets": "^7.19.1", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.19.1", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", - "@babel/plugin-proposal-dynamic-import": "^7.18.6", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.18.9", - "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", - "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.18.9", - "@babel/plugin-transform-classes": "^7.19.0", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.18.13", - "@babel/plugin-transform-dotall-regex": "^7.18.6", - "@babel/plugin-transform-duplicate-keys": "^7.18.9", - "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", - "@babel/plugin-transform-function-name": "^7.18.9", - "@babel/plugin-transform-literals": "^7.18.9", - "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.18.6", - "@babel/plugin-transform-modules-commonjs": "^7.18.6", - "@babel/plugin-transform-modules-systemjs": "^7.19.0", - "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", - "@babel/plugin-transform-new-target": "^7.18.6", - "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.18.8", - "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", - "@babel/plugin-transform-reserved-words": "^7.18.6", - "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.19.0", - "@babel/plugin-transform-sticky-regex": "^7.18.6", - "@babel/plugin-transform-template-literals": "^7.18.9", - "@babel/plugin-transform-typeof-symbol": "^7.18.9", - "@babel/plugin-transform-unicode-escapes": "^7.18.10", - "@babel/plugin-transform-unicode-regex": "^7.18.6", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.19.0", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "core-js-compat": "^3.25.1", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-env/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@babel/preset-flow": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.18.6.tgz", - "integrity": "sha512-E7BDhL64W6OUqpuyHnSroLnqyRTcG6ZdOBl1OKI/QK/HJfplqK/S3sq1Cckx7oTodJ5yOXyfw7rEADJ6UjoQDQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-flow-strip-types": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz", - "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-typescript": "^7.18.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/register": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.18.9.tgz", - "integrity": "sha512-ZlbnXDcNYHMR25ITwwNKT88JiaukkdVj/nG7r3wnuXkOTHc60Uy05PwMCPre0hSkY68E6zK3xz+vUJSP2jWmcw==", - "devOptional": true, - "peer": true, - "dependencies": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.5", - "source-map-support": "^0.5.16" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/register/node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/register/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "devOptional": true, - "peer": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/register/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/register/node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "devOptional": true, - "peer": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/register/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@babel/register/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/register/node_modules/pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "devOptional": true, - "peer": true, - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@babel/register/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/@babel/register/node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "devOptional": true, - "peer": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz", - "integrity": "sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==", - "peer": true, - "dependencies": { - "regenerator-runtime": "^0.13.4" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", - "devOptional": true, - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.1.tgz", - "integrity": "sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA==", - "devOptional": true, - "dependencies": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.19.1", - "@babel/types": "^7.19.0", - "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==", - "devOptional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", - "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", - "devOptional": true, - "dependencies": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types/node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "devOptional": true, - "engines": { - "node": ">=4" - } - }, - "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/@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/@eslint/eslintrc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", - "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.3.2", - "globals": "^13.15.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@eslint/eslintrc/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "devOptional": true, - "peer": true - }, - "node_modules/@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", - "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", - "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/@humanwhocodes/gitignore-to-minimatch": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", - "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "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/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "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==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "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==", - "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/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "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": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", - "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/console/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/console/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/console/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/@jest/console/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/@jest/console/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", - "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", - "dev": true, - "dependencies": { - "@jest/console": "^28.1.3", - "@jest/reporters": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@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": "^28.1.3", - "jest-config": "^28.1.3", - "jest-haste-map": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-resolve-dependencies": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "jest-watcher": "^28.1.3", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/@jest/core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/core/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/core/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/@jest/core/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/@jest/core/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/core/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/create-cache-key-function": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-27.5.1.tgz", - "integrity": "sha512-dmH1yW+makpTSURTy8VzdUwFnfQh1G8R+DxO2Ho2FFmBbKFEVm+3jWdvFhE2VqB/LATCTokkP0dotjyQyw5/AQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@jest/types": "^27.5.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/create-cache-key-function/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/create-cache-key-function/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@jest/create-cache-key-function/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/create-cache-key-function/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/create-cache-key-function/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/create-cache-key-function/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==", - "devOptional": true, - "peer": true - }, - "node_modules/@jest/create-cache-key-function/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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/create-cache-key-function/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/environment": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.0.1.tgz", - "integrity": "sha512-iLcFfoq2K6DAB+Mc+2VNLzZVmHdwQFeSqvoM/X8SMON6s/+yEi1iuRX3snx/JfwSnvmiMXjSr0lktxNxOcqXYA==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^29.0.1", - "@jest/types": "^29.0.1", - "@types/node": "*", - "jest-mock": "^29.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/environment/node_modules/@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/environment/node_modules/@jest/types": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", - "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "@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/@jest/environment/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/environment/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/environment/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/@jest/environment/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/@jest/environment/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/environment/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", - "dev": true, - "dependencies": { - "expect": "^28.1.3", - "jest-snapshot": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/expect-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", - "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", - "dev": true, - "dependencies": { - "jest-get-type": "^28.0.2" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/fake-timers": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.0.1.tgz", - "integrity": "sha512-XZ+kAhLChVQ+KJNa5034p7O1Mz3vtWrelxDcMoxhZkgqmWDaEQAW9qJeutaeCfPvwaEwKYVyKDYfWpcyT8RiMw==", - "dev": true, - "dependencies": { - "@jest/types": "^29.0.1", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^29.0.1", - "jest-mock": "^29.0.1", - "jest-util": "^29.0.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/@jest/types": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", - "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "@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/@jest/fake-timers/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/fake-timers/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/fake-timers/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/@jest/fake-timers/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/@jest/fake-timers/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/fake-timers/node_modules/jest-message-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.0.1.tgz", - "integrity": "sha512-wRMAQt3HrLpxSubdnzOo68QoTfQ+NLXFzU0Heb18ZUzO2S9GgaXNEdQ4rpd0fI9dq2NXkpCk1IUWSqzYKji64A==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.0.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.0.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers/node_modules/jest-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.0.1.tgz", - "integrity": "sha512-GIWkgNfkeA9d84rORDHPGGTFBrRD13A38QVSKE0bVrGSnoR1KDn8Kqz+0yI5kezMgbT/7zrWaruWP1Kbghlb2A==", - "dev": true, - "dependencies": { - "@jest/types": "^29.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "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" - } - }, - "node_modules/@jest/fake-timers/node_modules/pretty-format": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.0.1.tgz", - "integrity": "sha512-iTHy3QZMzuL484mSTYbQIM1AHhEQsH8mXWS2/vd2yFBYnG3EBqGiMONo28PlPgrW7P/8s/1ISv+y7WH306l8cw==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/@jest/fake-timers/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==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/fake-timers/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/globals": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", - "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", - "dev": true, - "dependencies": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/types": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/globals/node_modules/@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/globals/node_modules/@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/globals/node_modules/jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/reporters": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", - "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", - "@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": "^28.1.3", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.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/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/reporters/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/reporters/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/@jest/reporters/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/@jest/reporters/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/reporters/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "28.1.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", - "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.13", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/test-result": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", - "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", - "dev": true, - "dependencies": { - "@jest/console": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", - "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", - "dev": true, - "dependencies": { - "@jest/test-result": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "slash": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/transform": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", - "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.3", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/transform/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/transform/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/transform/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/@jest/transform/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/@jest/transform/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/transform/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", - "dev": true, - "dependencies": { - "@jest/schemas": "^28.1.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": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/types/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/@jest/types/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/@jest/types/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "devOptional": true, - "dependencies": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - }, - "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==", - "devOptional": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "devOptional": true, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "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/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "devOptional": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", - "devOptional": true, - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@optimizely/js-sdk-datafile-manager": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-datafile-manager/-/js-sdk-datafile-manager-0.9.5.tgz", - "integrity": "sha512-O4ujr1nBBAQBtx8YoKNpzzaEZgsE+aU4dxubT17ePqv/YVUWE+JOY21tSRrqZy/BlbbyzL+ElT8hrGB5ZzVoIQ==", - "dependencies": { - "@optimizely/js-sdk-logging": "^0.3.1", - "@optimizely/js-sdk-utils": "^0.4.0", - "decompress-response": "^4.2.1" - }, - "engines": { - "node": ">=8.0.0" - }, - "peerDependencies": { - "@react-native-async-storage/async-storage": "^1.2.0" - }, - "peerDependenciesMeta": { - "@react-native-async-storage/async-storage": { - "optional": true - } - } - }, - "node_modules/@optimizely/js-sdk-logging": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-logging/-/js-sdk-logging-0.3.1.tgz", - "integrity": "sha512-K71Jf283FP0E4oXehcXTTM3gvgHZHr7FUrIsw//0mdJlotHJT4Nss4hE0CWPbBxO7LJAtwNnO+VIA/YOcO4vHg==", - "dependencies": { - "@optimizely/js-sdk-utils": "^0.4.0" - } - }, - "node_modules/@optimizely/js-sdk-utils": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-utils/-/js-sdk-utils-0.4.0.tgz", - "integrity": "sha512-QG2oytnITW+VKTJK+l0RxjaS5VrA6W+AZMzpeg4LCB4Rn4BEKtF+EcW/5S1fBDLAviGq/0TLpkjM3DlFkJ9/Gw==", - "dependencies": { - "uuid": "^3.3.2" - } - }, - "node_modules/@optimizely/js-sdk-utils/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/@react-native-async-storage/async-storage": { - "version": "1.17.9", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.17.9.tgz", - "integrity": "sha512-HKhMvjpA5/YzNMkcY3qeWLdTtUrtJe243knHNNYe1c0IplX69hZyiw7DjFwAgxPG9+YvzHDHliqPV+mBNOv+cQ==", - "devOptional": true, - "dependencies": { - "merge-options": "^3.0.4" - }, - "peerDependencies": { - "react-native": "^0.0.0-0 || 0.60 - 0.69 || 1000.0.0" - } - }, - "node_modules/@react-native-community/cli": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-8.0.6.tgz", - "integrity": "sha512-E36hU/if3quQCfJHGWVkpsCnwtByRCwORuAX0r6yr1ebKktpKeEO49zY9PAu/Z1gfyxCtgluXY0HfRxjKRFXTg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-clean": "^8.0.4", - "@react-native-community/cli-config": "^8.0.6", - "@react-native-community/cli-debugger-ui": "^8.0.0", - "@react-native-community/cli-doctor": "^8.0.6", - "@react-native-community/cli-hermes": "^8.0.5", - "@react-native-community/cli-plugin-metro": "^8.0.4", - "@react-native-community/cli-server-api": "^8.0.4", - "@react-native-community/cli-tools": "^8.0.4", - "@react-native-community/cli-types": "^8.0.0", - "chalk": "^4.1.2", - "commander": "^2.19.0", - "execa": "^1.0.0", - "find-up": "^4.1.0", - "fs-extra": "^8.1.0", - "graceful-fs": "^4.1.3", - "leven": "^3.1.0", - "lodash": "^4.17.15", - "minimist": "^1.2.0", - "prompts": "^2.4.0", - "semver": "^6.3.0" - }, - "bin": { - "react-native": "build/bin.js" - }, - "engines": { - "node": ">=12" - }, - "peerDependencies": { - "react-native": "*" - } - }, - "node_modules/@react-native-community/cli-clean": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-8.0.4.tgz", - "integrity": "sha512-IwS1M1NHg6+qL8PThZYMSIMYbZ6Zbx+lIck9PLBskbosFo24M3lCOflOl++Bggjakp6mR+sRXxLMexid/GeOsQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "execa": "^1.0.0", - "prompts": "^2.4.0" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-clean/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@react-native-community/cli-clean/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==", - "devOptional": true, - "peer": true - }, - "node_modules/@react-native-community/cli-clean/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "devOptional": true, - "peer": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "devOptional": true, - "peer": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-clean/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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "devOptional": true, - "peer": true, - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "devOptional": true, - "peer": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@react-native-community/cli-clean/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-config": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-8.0.6.tgz", - "integrity": "sha512-mjVpVvdh8AviiO8xtqeX+BkjqE//NMDnISwsLWSJUfNCwTAPmdR8PGbhgP5O4hWHyJ3WkepTopl0ya7Tfi3ifw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-tools": "^8.0.4", - "cosmiconfig": "^5.1.0", - "deepmerge": "^3.2.0", - "glob": "^7.1.3", - "joi": "^17.2.1" - } - }, - "node_modules/@react-native-community/cli-config/node_modules/deepmerge": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", - "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@react-native-community/cli-debugger-ui": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-8.0.0.tgz", - "integrity": "sha512-u2jq06GZwZ9sRERzd9FIgpW6yv4YOW4zz7Ym/B8eSzviLmy3yI/8mxJtvlGW+J8lBsfMcQoqJpqI6Rl1nZy9yQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "serve-static": "^1.13.1" - } - }, - "node_modules/@react-native-community/cli-doctor": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-8.0.6.tgz", - "integrity": "sha512-ZQqyT9mJMVeFEVIwj8rbDYGCA2xXjJfsQjWk2iTRZ1CFHfhPSUuUiG8r6mJmTinAP9t+wYcbbIYzNgdSUKnDMw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-config": "^8.0.6", - "@react-native-community/cli-platform-ios": "^8.0.6", - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "command-exists": "^1.2.8", - "envinfo": "^7.7.2", - "execa": "^1.0.0", - "hermes-profile-transformer": "^0.0.6", - "ip": "^1.1.5", - "node-stream-zip": "^1.9.1", - "ora": "^5.4.1", - "prompts": "^2.4.0", - "semver": "^6.3.0", - "strip-ansi": "^5.2.0", - "sudo-prompt": "^9.0.0", - "wcwidth": "^1.0.1" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-doctor/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@react-native-community/cli-doctor/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==", - "devOptional": true, - "peer": true - }, - "node_modules/@react-native-community/cli-doctor/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/cross-spawn/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "devOptional": true, - "peer": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "devOptional": true, - "peer": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-doctor/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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "devOptional": true, - "peer": true, - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "devOptional": true, - "peer": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-doctor/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-hermes": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-8.0.5.tgz", - "integrity": "sha512-Zm0wM6SfgYAEX1kfJ1QBvTayabvh79GzmjHyuSnEROVNPbl4PeCG4WFbwy489tGwOP9Qx9fMT5tRIFCD8bp6/g==", - "devOptional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-platform-android": "^8.0.5", - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "hermes-profile-transformer": "^0.0.6", - "ip": "^1.1.5" - } - }, - "node_modules/@react-native-community/cli-hermes/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-hermes/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-hermes/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@react-native-community/cli-hermes/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==", - "devOptional": true, - "peer": true - }, - "node_modules/@react-native-community/cli-hermes/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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-hermes/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-platform-android": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-8.0.5.tgz", - "integrity": "sha512-z1YNE4T1lG5o9acoQR1GBvf7mq6Tzayqo/za5sHVSOJAC9SZOuVN/gg/nkBa9a8n5U7qOMFXfwhTMNqA474gXA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "execa": "^1.0.0", - "fs-extra": "^8.1.0", - "glob": "^7.1.3", - "jetifier": "^1.6.2", - "lodash": "^4.17.15", - "logkitty": "^0.7.1", - "slash": "^3.0.0" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-platform-android/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@react-native-community/cli-platform-android/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==", - "devOptional": true, - "peer": true - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "devOptional": true, - "peer": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "devOptional": true, - "peer": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-platform-android/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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "devOptional": true, - "peer": true, - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "devOptional": true, - "peer": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@react-native-community/cli-platform-android/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-platform-ios": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-8.0.6.tgz", - "integrity": "sha512-CMR6mu/LVx6JVfQRDL9uULsMirJT633bODn+IrYmrwSz250pnhON16We8eLPzxOZHyDjm7JPuSgHG3a/BPiRuQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "execa": "^1.0.0", - "glob": "^7.1.3", - "js-yaml": "^3.13.1", - "lodash": "^4.17.15", - "ora": "^5.4.1", - "plist": "^3.0.2" - } - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-platform-ios/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@react-native-community/cli-platform-ios/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==", - "devOptional": true, - "peer": true - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "devOptional": true, - "peer": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "devOptional": true, - "peer": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli-platform-ios/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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "devOptional": true, - "peer": true, - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "devOptional": true, - "peer": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@react-native-community/cli-platform-ios/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-plugin-metro": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-8.0.4.tgz", - "integrity": "sha512-UWzY1eMcEr/6262R2+d0Is5M3L/7Y/xXSDIFMoc5Rv5Wucl3hJM/TxHXmByvHpuJf6fJAfqOskyt4bZCvbI+wQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-server-api": "^8.0.4", - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "metro": "^0.70.1", - "metro-config": "^0.70.1", - "metro-core": "^0.70.1", - "metro-react-native-babel-transformer": "^0.70.1", - "metro-resolver": "^0.70.1", - "metro-runtime": "^0.70.1", - "readline": "^1.3.0" - } - }, - "node_modules/@react-native-community/cli-plugin-metro/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-plugin-metro/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-plugin-metro/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@react-native-community/cli-plugin-metro/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==", - "devOptional": true, - "peer": true - }, - "node_modules/@react-native-community/cli-plugin-metro/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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-plugin-metro/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-server-api": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-8.0.4.tgz", - "integrity": "sha512-Orr14njx1E70CVrUA8bFdl+mrnbuXUjf1Rhhm0RxUadFpvkHuOi5dh8Bryj2MKtf8eZrpEwZ7tuQPhJEULW16A==", - "devOptional": true, - "peer": true, - "dependencies": { - "@react-native-community/cli-debugger-ui": "^8.0.0", - "@react-native-community/cli-tools": "^8.0.4", - "compression": "^1.7.1", - "connect": "^3.6.5", - "errorhandler": "^1.5.0", - "nocache": "^3.0.1", - "pretty-format": "^26.6.2", - "serve-static": "^1.13.1", - "ws": "^7.5.1" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/@types/yargs": { - "version": "15.0.14", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", - "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-server-api/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@react-native-community/cli-server-api/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==", - "devOptional": true, - "peer": true - }, - "node_modules/@react-native-community/cli-server-api/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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "devOptional": true, - "peer": true - }, - "node_modules/@react-native-community/cli-server-api/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-server-api/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/@react-native-community/cli-tools": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-8.0.4.tgz", - "integrity": "sha512-ePN9lGxh6LRFiotyddEkSmuqpQhnq2iw9oiXYr4EFWpIEy0yCigTuSTiDF68+c8M9B+7bTwkRpz/rMPC4ViO5Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "appdirsjs": "^1.2.4", - "chalk": "^4.1.2", - "find-up": "^5.0.0", - "lodash": "^4.17.15", - "mime": "^2.4.1", - "node-fetch": "^2.6.0", - "open": "^6.2.0", - "ora": "^5.4.1", - "semver": "^6.3.0", - "shell-quote": "^1.7.3" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@react-native-community/cli-tools/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@react-native-community/cli-tools/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==", - "devOptional": true, - "peer": true - }, - "node_modules/@react-native-community/cli-tools/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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@react-native-community/cli-tools/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli-types": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-8.0.0.tgz", - "integrity": "sha512-1lZS1PEvMlFaN3Se1ksyoFWzMjk+YfKi490GgsqKJln9gvFm8tqVPdnXttI5Uf2DQf3BMse8Bk8dNH4oV6Ewow==", - "devOptional": true, - "peer": true, - "dependencies": { - "joi": "^17.2.1" - } - }, - "node_modules/@react-native-community/cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@react-native-community/cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@react-native-community/cli/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@react-native-community/cli/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==", - "devOptional": true, - "peer": true - }, - "node_modules/@react-native-community/cli/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==", - "devOptional": true, - "peer": true - }, - "node_modules/@react-native-community/cli/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/@react-native-community/cli/node_modules/cross-spawn/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/@react-native-community/cli/node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "devOptional": true, - "peer": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "devOptional": true, - "peer": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/@react-native-community/cli/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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@react-native-community/cli/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "devOptional": true, - "peer": true, - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@react-native-community/cli/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@react-native-community/cli/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/cli/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@react-native-community/cli/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/@react-native-community/cli/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "devOptional": true, - "peer": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@react-native-community/cli/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@react-native-community/cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@react-native-community/netinfo": { - "version": "5.9.10", - "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-5.9.10.tgz", - "integrity": "sha512-1NPlBA2Hu/KWc3EnQcDRPRX0x8Dg9tuQlQQVWVQjlg+u+PjCq7ANEtbikOFKp5yQqfF8tqzU5+84/IfDO8zpiA==", - "dev": true, - "peerDependencies": { - "react-native": ">=0.59" - } - }, - "node_modules/@react-native/assets": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@react-native/assets/-/assets-1.0.0.tgz", - "integrity": "sha512-KrwSpS1tKI70wuKl68DwJZYEvXktDHdZMG0k2AXD/rJVSlB23/X2CB2cutVR0HwNMJIal9HOUOBB2rVfa6UGtQ==", - "devOptional": true, - "peer": true - }, - "node_modules/@react-native/normalize-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@react-native/normalize-color/-/normalize-color-2.0.0.tgz", - "integrity": "sha512-Wip/xsc5lw8vsBlmY2MO/gFLp3MvuZ2baBZjDeTjjndMgM0h5sxz7AZR62RDPGgstp8Np7JzjvVqVT7tpFZqsw==", - "devOptional": true, - "peer": true - }, - "node_modules/@react-native/polyfills": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@react-native/polyfills/-/polyfills-2.0.0.tgz", - "integrity": "sha512-K0aGNn1TjalKj+65D7ycc1//H9roAQ51GJVk5ZJQFb2teECGmzd86bYDC0aYdbRf7gtovescq4Zt6FR0tgXiHQ==", - "devOptional": true, - "peer": true - }, - "node_modules/@rollup/plugin-commonjs": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.1.0.tgz", - "integrity": "sha512-Ycr12N3ZPN96Fw2STurD21jMqzKwL9QuFhms3SD7KKRK7oaXUsBU9Zt0jL/rOPHiPYisI21/rXGO3jr9BnLHUA==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^3.0.8", - "commondir": "^1.0.1", - "estree-walker": "^1.0.1", - "glob": "^7.1.2", - "is-reference": "^1.1.2", - "magic-string": "^0.25.2", - "resolve": "^1.11.0" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/plugin-node-resolve": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz", - "integrity": "sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^3.0.8", - "@types/resolve": "0.0.8", - "builtin-modules": "^3.1.0", - "is-module": "^1.0.0", - "resolve": "^1.14.2" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dev": true, - "dependencies": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - }, - "engines": { - "node": ">= 8.0.0" - }, - "peerDependencies": { - "rollup": "^1.20.0||^2.0.0" - } - }, - "node_modules/@sideway/address": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", - "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@hapi/hoek": "^9.0.0" - } - }, - "node_modules/@sideway/formula": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", - "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==", - "devOptional": true, - "peer": true - }, - "node_modules/@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "devOptional": true, - "peer": true - }, - "node_modules/@sinclair/typebox": { - "version": "0.24.34", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.34.tgz", - "integrity": "sha512-x3ejWKw7rpy30Bvm6U0AQMOHdjqe2E3YJrBHlTxH0KFsp77bBa+MH324nJxtXZFpnTy/JW2h5HPYVm0vG2WPnw==", - "dev": true - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/@sinonjs/formatio": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", - "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" - } - }, - "node_modules/@sinonjs/samsam": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", - "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.3.0", - "array-from": "^2.1.1", - "lodash": "^4.17.15" - } - }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", - "dev": true - }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "engines": { - "node": ">= 10" - } - }, - "node_modules/@types/babel__core": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", - "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "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/@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/@types/babel__traverse": { - "version": "7.18.1", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.1.tgz", - "integrity": "sha512-FSdLaZh2UxaMuLp9lixWaHq/golWTRWOnRsAXzDTDSDOQLuZb1nsdCt6pJSPWSEQt2eFZ2YVk3oYhn+1kLMeMA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.3.0" - } - }, - "node_modules/@types/chai": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.3.tgz", - "integrity": "sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==", - "dev": true - }, - "node_modules/@types/component-emitter": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", - "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==", - "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/cors": { - "version": "2.8.12", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", - "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", - "dev": true - }, - "node_modules/@types/eslint": { - "version": "8.4.5", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz", - "integrity": "sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ==", - "dev": true, - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "node_modules/@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "node_modules/@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "devOptional": 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", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "devOptional": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "devOptional": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "devOptional": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/jest": { - "version": "23.3.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.3.14.tgz", - "integrity": "sha512-Q5hTcfdudEL2yOmluA1zaSyPbzWPmJ3XfSWeP3RyoYvS9hnje1ZyagrZOuQ6+1nQC1Gw+7gap3pLNL3xL6UBug==", - "dev": true - }, - "node_modules/@types/jsdom": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.0.tgz", - "integrity": "sha512-YfAchFs0yM1QPDrLm2VHe+WHGtqms3NXnXAMolrgrVP6fgBHHXy1ozAbo/dFtPNtZC/m66bPiCTWYmqp1F14gA==", - "dev": true, - "dependencies": { - "@types/node": "*", - "@types/tough-cookie": "*", - "parse5": "^7.0.0" - } - }, - "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==", - "dev": true - }, - "node_modules/@types/mocha": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", - "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", - "dev": true - }, - "node_modules/@types/nise": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@types/nise/-/nise-1.4.0.tgz", - "integrity": "sha512-DPxmjiDwubsNmguG5X4fEJ+XCyzWM3GXWsqQlvUcjJKa91IOoJUy51meDr0GkzK64qqNcq85ymLlyjoct9tInw==", - "dev": true - }, - "node_modules/@types/node": { - "version": "18.7.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", - "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==", - "devOptional": true - }, - "node_modules/@types/prettier": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz", - "integrity": "sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==", - "dev": true - }, - "node_modules/@types/resolve": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", - "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "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/tough-cookie": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", - "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", - "dev": true - }, - "node_modules/@types/uuid": { - "version": "3.4.10", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.10.tgz", - "integrity": "sha512-BgeaZuElf7DEYZhWYDTc/XcLZXdVgFkVSTa13BqKvbnmUrxr3TJFKofUxCtDO9UQOdhnV+HPOESdHiHKZOJV1A==", - "dev": true - }, - "node_modules/@types/yargs": { - "version": "17.0.12", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz", - "integrity": "sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "devOptional": true - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.0.tgz", - "integrity": "sha512-jHvZNSW2WZ31OPJ3enhLrEKvAZNyAFWZ6rx9tUwaessTc4sx9KmgMNhVcqVAl1ETnT5rU5fpXTLmY9YvC1DCNg==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.33.0", - "@typescript-eslint/type-utils": "5.33.0", - "@typescript-eslint/utils": "5.33.0", - "debug": "^4.3.4", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.2.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.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" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.33.0.tgz", - "integrity": "sha512-cgM5cJrWmrDV2KpvlcSkelTBASAs1mgqq+IUGKJvFxWrapHpaRy5EXPQz9YaKF3nZ8KY18ILTiVpUtbIac86/w==", - "dev": true, - "dependencies": { - "@typescript-eslint/scope-manager": "5.33.0", - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/typescript-estree": "5.33.0", - "debug": "^4.3.4" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.33.0.tgz", - "integrity": "sha512-/Jta8yMNpXYpRDl8EwF/M8It2A9sFJTubDo0ATZefGXmOqlaBffEw0ZbkbQ7TNDK6q55NPHFshGBPAZvZkE8Pw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/visitor-keys": "5.33.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.33.0.tgz", - "integrity": "sha512-2zB8uEn7hEH2pBeyk3NpzX1p3lF9dKrEbnXq1F7YkpZ6hlyqb2yZujqgRGqXgRBTHWIUG3NGx/WeZk224UKlIA==", - "dev": true, - "dependencies": { - "@typescript-eslint/utils": "5.33.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/types": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.33.0.tgz", - "integrity": "sha512-nIMt96JngB4MYFYXpZ/3ZNU4GWPNdBbcB5w2rDOCpXOVUkhtNlG2mmm8uXhubhidRZdwMaMBap7Uk8SZMU/ppw==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.0.tgz", - "integrity": "sha512-tqq3MRLlggkJKJUrzM6wltk8NckKyyorCSGMq4eVkyL5sDYzJJcMgZATqmF8fLdsWrW7OjjIZ1m9v81vKcaqwQ==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/visitor-keys": "5.33.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.33.0.tgz", - "integrity": "sha512-JxOAnXt9oZjXLIiXb5ZIcZXiwVHCkqZgof0O8KPgz7C7y0HS42gi75PdPlqh1Tf109M0fyUw45Ao6JLo7S5AHw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.33.0", - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/typescript-estree": "5.33.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.0.tgz", - "integrity": "sha512-/XsqCzD4t+Y9p5wd9HZiptuGKBlaZO5showwqODii5C0nZawxWLF+Q6k5wYHBrQv96h6GYKyqqMHCSTqta8Kiw==", - "dev": true, - "dependencies": { - "@typescript-eslint/types": "5.33.0", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, - "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "node_modules/abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "dev": true - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "devOptional": true, - "peer": true, - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/absolute-path": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/absolute-path/-/absolute-path-0.0.0.tgz", - "integrity": "sha512-HQiug4c+/s3WOvEnDRxXVmNtSG5s2gJM9r19BTcqjp7BWcE48PB+Y2G6jE65kqI0LpsQeMZygt/b60Gi4KxGyA==", - "devOptional": true, - "peer": 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==", - "devOptional": true, - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-globals/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "peerDependencies": { - "acorn": "^8" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "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", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/anser": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", - "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", - "devOptional": true, - "peer": true - }, - "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==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "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==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-fragments": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", - "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", - "devOptional": true, - "peer": true, - "dependencies": { - "colorette": "^1.0.7", - "slice-ansi": "^2.0.0", - "strip-ansi": "^5.0.0" - } - }, - "node_modules/ansi-fragments/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-fragments/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "devOptional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "devOptional": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "devOptional": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/appdirsjs": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", - "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", - "devOptional": true, - "peer": true - }, - "node_modules/archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", - "dev": true - }, - "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": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "devOptional": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha512-GQTc6Uupx1FCavi5mPzBvVT7nEOeWMmUA9P95wpfpW1XwMSKs+KaymD5C2Up7KAUKg/mYwbsUYzdZWcoajlNZg==", - "dev": true - }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "devOptional": true, - "peer": true - }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ast-types": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz", - "integrity": "sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==", - "devOptional": true, - "peer": true, - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", - "devOptional": true, - "peer": true - }, - "node_modules/async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "devOptional": true, - "peer": true - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "devOptional": true, - "peer": true, - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", - "dev": true - }, - "node_modules/babel-core": { - "version": "7.0.0-bridge.0", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", - "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", - "devOptional": true, - "peer": true, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", - "integrity": "sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==", - "dev": true, - "dependencies": { - "@jest/transform": "^28.1.3", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^28.1.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.8.0" - } - }, - "node_modules/babel-jest/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/babel-jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/babel-jest/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/babel-jest/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/babel-jest/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "object.assign": "^4.1.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==", - "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" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz", - "integrity": "sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==", - "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" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", - "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.3.3", - "semver": "^6.1.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", - "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.3", - "core-js-compat": "^3.25.1" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", - "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.3.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/babel-plugin-syntax-trailing-function-commas": { - "version": "7.0.0-beta.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz", - "integrity": "sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==", - "devOptional": true, - "peer": true - }, - "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/babel-preset-fbjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-3.4.0.tgz", - "integrity": "sha512-9ywCsCvo1ojrw0b+XYk7aFvTH6D9064t0RIL1rtMf3nsa02Xw41MS7sZw216Im35xj/UY0PDBQsa1brUDDF1Ow==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0", - "@babel/plugin-syntax-class-properties": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.0.0", - "@babel/plugin-syntax-object-rest-spread": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-block-scoped-functions": "^7.0.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.0.0", - "@babel/plugin-transform-flow-strip-types": "^7.0.0", - "@babel/plugin-transform-for-of": "^7.0.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-member-expression-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-object-super": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-property-literals": "^7.0.0", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-template-literals": "^7.0.0", - "babel-plugin-syntax-trailing-function-commas": "^7.0.0-beta.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz", - "integrity": "sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==", - "dev": true, - "dependencies": { - "babel-plugin-jest-hoist": "^28.1.3", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "devOptional": true - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "devOptional": true, - "peer": true, - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "devOptional": 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" - } - ], - "peer": true - }, - "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/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "devOptional": true, - "peer": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "node_modules/body-parser": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", - "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", - "dev": true, - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "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.10.3", - "raw-body": "2.5.1", - "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/body-parser/node_modules/qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "devOptional": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "devOptional": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", - "devOptional": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/browserstack": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", - "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", - "dev": true, - "dependencies": { - "https-proxy-agent": "^2.2.1" - } - }, - "node_modules/browserstack-local": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.1.tgz", - "integrity": "sha512-T/wxyWDzvBHbDvl7fZKpFU7mYze6nrUkBhNy+d+8bXBqgQX10HTYvajIGO0wb49oGSLCPM0CMZTV/s7e6LF0sA==", - "dev": true, - "dependencies": { - "agent-base": "^6.0.2", - "https-proxy-agent": "^5.0.1", - "is-running": "^2.1.0", - "ps-tree": "=1.2.0", - "temp-fs": "^0.9.9" - } - }, - "node_modules/browserstack-local/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/browserstack-local/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "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==", - "devOptional": true, - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "devOptional": 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" - } - ], - "peer": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "devOptional": true - }, - "node_modules/builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/caching-transform": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", - "dev": true, - "dependencies": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/caching-transform/node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "devOptional": true, - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "callsites": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/caller-callsite/node_modules/callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", - "devOptional": true, - "peer": true, - "dependencies": { - "caller-callsite": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "devOptional": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001407", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001407.tgz", - "integrity": "sha512-4ydV+t4P7X3zH83fQWNDX/mQEzYomossfpViCOx9zHBSMV+rIe3LFqglHHtVyvNl1FhTNxPxs3jei82iqOW04w==", - "devOptional": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - } - ] - }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true - }, - "node_modules/chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", - "dev": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "devOptional": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "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/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/ci-info": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", - "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", - "devOptional": true - }, - "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/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "devOptional": true, - "peer": true, - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "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==", - "devOptional": true, - "peer": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-spinners": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", - "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "devOptional": true, - "peer": 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==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "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/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", - "devOptional": true, - "peer": true, - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "devOptional": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "devOptional": true - }, - "node_modules/colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "devOptional": true, - "peer": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/command-exists": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", - "devOptional": true, - "peer": true - }, - "node_modules/commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "devOptional": true - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "devOptional": true - }, - "node_modules/compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "devOptional": true, - "peer": true, - "dependencies": { - "mime-db": ">= 1.43.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/compression/node_modules/bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/compression/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/compression/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - }, - "node_modules/compression/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "devOptional": true, - "peer": 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==", - "devOptional": true - }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "devOptional": 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==", - "devOptional": 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==", - "devOptional": true - }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "devOptional": true, - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/convert-source-map/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "devOptional": 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/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/core-js-compat": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.25.2.tgz", - "integrity": "sha512-TxfyECD4smdn3/CjWxczVtJqVLEEC2up7/82t7vC0AzNogr+4nQ8vyF7abxAuTXWvjTClSbvGhU0RgqA4ToQaQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "browserslist": "^4.21.4" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "devOptional": 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": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "devOptional": true, - "peer": true, - "dependencies": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cosmiconfig/node_modules/import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", - "devOptional": true, - "peer": true, - "dependencies": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cosmiconfig/node_modules/parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "devOptional": true, - "peer": true, - "dependencies": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/cosmiconfig/node_modules/resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/coveralls": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", - "integrity": "sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==", - "dev": true, - "dependencies": { - "js-yaml": "^3.13.1", - "lcov-parse": "^1.0.0", - "log-driver": "^1.2.7", - "minimist": "^1.2.5", - "request": "^2.88.2" - }, - "bin": { - "coveralls": "bin/coveralls.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cross-spawn/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/cssom": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", - "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", - "dev": true - }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - }, - "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/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/data-urls": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", - "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", - "dev": true, - "dependencies": { - "abab": "^2.0.6", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/date-format": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.13.tgz", - "integrity": "sha512-bnYCwf8Emc3pTD8pXnre+wfnjGtfi5ncMDKy7+cWZXbmRAsdWkOQHrfC1yz/KiwP5thDp2kCHWYWKBX4HP1hoQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/dayjs": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.5.tgz", - "integrity": "sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==", - "devOptional": true, - "peer": true - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "devOptional": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "devOptional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decimal.js": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.0.tgz", - "integrity": "sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg==", - "dev": true - }, - "node_modules/decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/decompress-response": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", - "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", - "dependencies": { - "mimic-response": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "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-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=0.12" - } - }, - "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.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==", - "devOptional": true, - "peer": true, - "dependencies": { - "clone": "^1.0.2" - } - }, - "node_modules/define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/denodeify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", - "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==", - "devOptional": true, - "peer": true - }, - "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==", - "devOptional": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "devOptional": true, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "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": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/diff-sequences": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", - "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/dir-glob/node_modules/path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "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/domexception": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", - "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", - "dev": true, - "dependencies": { - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "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==", - "devOptional": true - }, - "node_modules/electron-to-chromium": { - "version": "1.4.256", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.256.tgz", - "integrity": "sha512-x+JnqyluoJv8I0U9gVe+Sk2st8vF0CzMt78SXxuoWCooLLY2k5VerIBdpvG7ql6GKI4dzNnPjmqgDJ76EdaAKw==", - "devOptional": true - }, - "node_modules/emittery": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", - "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "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==", - "devOptional": true - }, - "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==", - "devOptional": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/engine.io": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.0.tgz", - "integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==", - "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.2.3" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/engine.io-parser": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", - "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/engine.io/node_modules/ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, - "engines": { - "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/entities": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", - "dev": true, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", - "devOptional": true, - "peer": true, - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "devOptional": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/error-stack-parser": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", - "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "stackframe": "^1.3.4" - } - }, - "node_modules/errorhandler": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", - "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", - "devOptional": true, - "peer": true, - "dependencies": { - "accepts": "~1.3.7", - "escape-html": "~1.0.3" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true - }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true - }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "node_modules/es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", - "dev": true, - "dependencies": { - "es6-promise": "^4.0.3" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "devOptional": true, - "engines": { - "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==", - "devOptional": true - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "devOptional": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/escodegen/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/escodegen/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.21.0.tgz", - "integrity": "sha512-/XJ1+Qurf1T9G2M5IHrsjp+xrGT73RZf23xA1z5wB1ZzzEAWSZKvRwhWxTFp1rvkvCfwcvAUNAP31bhKTTGfDA==", - "dev": true, - "dependencies": { - "@eslint/eslintrc": "^1.3.0", - "@humanwhocodes/config-array": "^0.10.4", - "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", - "ajv": "^6.10.0", - "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.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.3", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.15.0", - "globby": "^11.1.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "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", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-prettier": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz", - "integrity": "sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==", - "dev": true, - "dependencies": { - "get-stdin": "^6.0.0" - }, - "bin": { - "eslint-config-prettier-check": "bin/cli.js" - }, - "peerDependencies": { - "eslint": ">=3.14.1" - } - }, - "node_modules/eslint-plugin-prettier": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", - "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", - "dev": true, - "dependencies": { - "prettier-linter-helpers": "^1.0.0" - }, - "engines": { - "node": ">=6.0.0" - }, - "peerDependencies": { - "eslint": ">=5.0.0", - "prettier": ">=1.13.0" - }, - "peerDependenciesMeta": { - "eslint-config-prettier": { - "optional": true - } - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "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, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/eslint/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/eslint/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/eslint/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/eslint/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/eslint/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/eslint/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/eslint/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/espree": { - "version": "9.3.3", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.3.tgz", - "integrity": "sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==", - "dev": true, - "dependencies": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "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==", - "devOptional": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "devOptional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", - "dev": true, - "dependencies": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - } - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "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", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "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/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", - "devOptional": true, - "peer": true, - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - }, - "node_modules/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", - "dev": true, - "dependencies": { - "@jest/expect-utils": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.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/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "devOptional": true, - "peer": true, - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true, - "engines": [ - "node >=0.6.0" - ] - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "node_modules/fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "node_modules/fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, - "node_modules/fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "devOptional": 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", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "dependencies": { - "flat-cache": "^3.0.4" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "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==", - "devOptional": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fill-range/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "devOptional": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "devOptional": true, - "dependencies": { - "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": ">= 0.8" - } - }, - "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==", - "devOptional": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "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==", - "devOptional": 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==", - "devOptional": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/finalhandler/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==", - "devOptional": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/avajs/find-cache-dir?sponsor=1" - } - }, - "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==", - "devOptional": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/find-up/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true, - "engines": { - "node": ">=8" - } - }, - "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/flat-cache/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/flatted": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", - "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", - "dev": true - }, - "node_modules/flow-parser": { - "version": "0.121.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.121.0.tgz", - "integrity": "sha512-1gIBiWJNR0tKUNv8gZuk7l9rVX06OuLzY9AoGio7y/JT4V1IZErEMEq2TJS+PFcw/y0RshZ1J/27VfK1UQzYVg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/foreground-child/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/foreground-child/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/foreground-child/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/foreground-child/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/foreground-child/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/formatio": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", - "integrity": "sha512-YAF05v8+XCxAyHOdiiAmHdgCVPrWO8X744fYIPtBciIorh5LndWfi1gjeJ16sTbJhzek9kd+j3YByhohtz5Wmg==", - "deprecated": "This package is unmaintained. Use @sinonjs/formatio instead", - "dev": true, - "dependencies": { - "samsam": "1.x" - } - }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", - "devOptional": true, - "peer": true, - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", - "dev": true - }, - "node_modules/fromentries": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", - "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", - "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/fs-access": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", - "integrity": "sha512-05cXDIwNbFaoFWaz5gNHlUTbH5whiss/hr/ibzPd4MH3cR4w0ZKeIPiVdbyJurg3O5r/Bjpvn9KOb1/rPMf3nA==", - "dev": true, - "dependencies": { - "null-check": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "devOptional": true, - "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - }, - "engines": { - "node": ">=6 <7 || >=8" - } - }, - "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==", - "devOptional": 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==", - "devOptional": true - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true - }, - "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==", - "devOptional": 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==", - "devOptional": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", - "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", - "devOptional": true, - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "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-stdin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", - "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "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-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - } - }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "devOptional": 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": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "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/globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globals/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "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==", - "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": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "devOptional": 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/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true, - "engines": { - "node": ">=4.x" - } - }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", - "dev": true, - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "devOptional": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "devOptional": true, - "engines": { - "node": ">=4" - } - }, - "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==", - "devOptional": true, - "peer": true, - "dependencies": { - "get-intrinsic": "^1.1.1" - }, - "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==", - "devOptional": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", - "devOptional": true, - "peer": true, - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hasha": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", - "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", - "dev": true, - "dependencies": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/hasha/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": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha512-z/GDPjlRMNOa2XJiB4em8wJpuuBfrFOlYKTZxtpkdr1uPdibHI8rYA3MY0KDObpVyaes0e/aunid/t88ZI2EKA==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/hermes-engine": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/hermes-engine/-/hermes-engine-0.11.0.tgz", - "integrity": "sha512-7aMUlZja2IyLYAcZ69NBnwJAR5ZOYlSllj0oMpx08a8HzxHOys0eKCzfphrf6D0vX1JGO1QQvVsQKe6TkYherw==", - "devOptional": true, - "peer": true - }, - "node_modules/hermes-estree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.6.0.tgz", - "integrity": "sha512-2YTGzJCkhdmT6VuNprWjXnvTvw/3iPNw804oc7yknvQpNKo+vJGZmtvLLCghOZf0OwzKaNAzeIMp71zQbNl09w==", - "devOptional": true, - "peer": true - }, - "node_modules/hermes-parser": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.6.0.tgz", - "integrity": "sha512-Vf58jBZca2+QBLR9h7B7mdg8oFz2g5ILz1iVouZ5DOrOrAfBmPfJjdjDT8jrO0f+iJ4/hSRrQHqHIjSnTaLUDQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "hermes-estree": "0.6.0" - } - }, - "node_modules/hermes-profile-transformer": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/hermes-profile-transformer/-/hermes-profile-transformer-0.0.6.tgz", - "integrity": "sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "source-map": "^0.7.3" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/hermes-profile-transformer/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/html-encoding-sniffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", - "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", - "dev": true, - "dependencies": { - "whatwg-encoding": "^2.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "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/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==", - "devOptional": 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.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": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/http-proxy-agent/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, - "node_modules/https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "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": ">=10.17.0" - } - }, - "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": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "devOptional": 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" - } - ], - "peer": true - }, - "node_modules/ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/image-size": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz", - "integrity": "sha512-47xSUiQioGaB96nqtp5/q55m0aBQSQdyIloMOc/x+QVTDZLNmXE892IIDrJ0hM1A5vcNUDD5tDffkSP5lCaIIA==", - "devOptional": true, - "peer": true, - "bin": { - "image-size": "bin/image-size.js" - }, - "engines": { - "node": ">=4.0" - } - }, - "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": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "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==", - "devOptional": true, - "engines": { - "node": ">=0.8.19" - } - }, - "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, - "engines": { - "node": ">=8" - } - }, - "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==", - "devOptional": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "devOptional": true - }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "devOptional": true, - "peer": true, - "dependencies": { - "loose-envify": "^1.0.0" - } - }, - "node_modules/ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", - "devOptional": true, - "peer": true - }, - "node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.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==", - "devOptional": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "devOptional": true, - "peer": true - }, - "node_modules/is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", - "devOptional": true, - "dependencies": { - "has": "^1.0.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "devOptional": true, - "engines": { - "node": ">=8" - } - }, - "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==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "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": ">=0.10.0" - } - }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true - }, - "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==", - "devOptional": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "devOptional": true, - "engines": { - "node": ">=8" - } - }, - "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==", - "devOptional": true, - "peer": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "node_modules/is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "dev": true, - "dependencies": { - "@types/estree": "*" - } - }, - "node_modules/is-running": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", - "integrity": "sha512-mjJd3PujZMl7j+D395WTIO5tU5RIDBfVSRtRR4VOJou3H66E38UjbjvDGh3slJzPuolsb+yQFqwHNNdyp5jg3w==", - "dev": true - }, - "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": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "devOptional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "devOptional": true, - "peer": 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, - "engines": { - "node": ">= 8.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/gjtorikian/" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "devOptional": 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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true - }, - "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/istanbul-lib-instrument": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", - "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", - "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/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_modules/istanbul-lib-processinfo": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", - "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", - "dev": true, - "dependencies": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.3", - "istanbul-lib-coverage": "^3.2.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "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": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "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": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", - "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", - "dev": true, - "dependencies": { - "@jest/core": "^28.1.3", - "@jest/types": "^28.1.3", - "import-local": "^3.0.2", - "jest-cli": "^28.1.3" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest-changed-files": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", - "integrity": "sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==", - "dev": true, - "dependencies": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz", - "integrity": "sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==", - "dev": true, - "dependencies": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "p-limit": "^3.1.0", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-circus/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-circus/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/jest-circus/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/jest-circus/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-circus/node_modules/jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-circus/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", - "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^28.1.3", - "@jest/types": "^28.1.3", - "babel-jest": "^28.1.3", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^28.1.3", - "jest-environment-node": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "@types/node": "*", - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-config/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-config/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-config/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/jest-config/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/jest-config/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-config/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", - "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-diff/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/jest-diff/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/jest-diff/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-docblock": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz", - "integrity": "sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==", - "dev": true, - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-each": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", - "integrity": "sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", - "jest-util": "^28.1.3", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-each/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-each/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-each/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/jest-each/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/jest-each/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-each/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-jsdom": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.0.1.tgz", - "integrity": "sha512-rMF501kfui+bw4AmwowLA2bNaYb633A3ejFMN5pVU0AeOqLv2NbMAY5XzzlMr/+lM1itEf+3ZdCr9dGGrUfoxg==", - "dev": true, - "dependencies": { - "@jest/environment": "^29.0.1", - "@jest/fake-timers": "^29.0.1", - "@jest/types": "^29.0.1", - "@types/jsdom": "^20.0.0", - "@types/node": "*", - "jest-mock": "^29.0.1", - "jest-util": "^29.0.1", - "jsdom": "^20.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-environment-jsdom/node_modules/@jest/types": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", - "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "@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/jest-environment-jsdom/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-environment-jsdom/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-environment-jsdom/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/jest-environment-jsdom/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/jest-environment-jsdom/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-jsdom/node_modules/jest-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.0.1.tgz", - "integrity": "sha512-GIWkgNfkeA9d84rORDHPGGTFBrRD13A38QVSKE0bVrGSnoR1KDn8Kqz+0yI5kezMgbT/7zrWaruWP1Kbghlb2A==", - "dev": true, - "dependencies": { - "@jest/types": "^29.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "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" - } - }, - "node_modules/jest-environment-jsdom/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-environment-node": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", - "integrity": "sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==", - "dev": true, - "dependencies": { - "@jest/environment": "^28.1.3", - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-environment-node/node_modules/jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-haste-map": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", - "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@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": "^28.0.2", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", - "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", - "dev": true, - "dependencies": { - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-localstorage-mock": { - "version": "2.4.22", - "resolved": "https://registry.npmjs.org/jest-localstorage-mock/-/jest-localstorage-mock-2.4.22.tgz", - "integrity": "sha512-60PWSDFQOS5v7JzSmYLM3dPLg0JLl+2Vc4lIEz/rj2yrXJzegsFLn7anwc5IL0WzJbBa/Las064CHbFg491/DQ==", - "dev": true, - "engines": { - "node": ">=6.16.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", - "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/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/jest-matcher-utils/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/jest-matcher-utils/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-message-util/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/jest-message-util/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/jest-message-util/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-mock": { - "version": "29.0.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.0.3.tgz", - "integrity": "sha512-ort9pYowltbcrCVR43wdlqfAiFJXBx8l4uJDsD8U72LgBcetvEp+Qxj1W9ZYgMRoeAo+ov5cnAGF2B6+Oth+ww==", - "dev": true, - "dependencies": { - "@jest/types": "^29.0.3", - "@types/node": "*" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock/node_modules/@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "dependencies": { - "@sinclair/typebox": "^0.24.1" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-mock/node_modules/@jest/types": { - "version": "29.0.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.3.tgz", - "integrity": "sha512-coBJmOQvurXjN1Hh5PzF7cmsod0zLIOXpP8KD161mqNlroMhLcwpODiEzi7ZsRl5Z/AIuxpeNm8DCl43F4kz8A==", - "dev": true, - "dependencies": { - "@jest/schemas": "^29.0.0", - "@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/jest-mock/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-mock/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-mock/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/jest-mock/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/jest-mock/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-mock/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", - "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", - "dev": true, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-resolve": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz", - "integrity": "sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz", - "integrity": "sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==", - "dev": true, - "dependencies": { - "jest-regex-util": "^28.0.2", - "jest-snapshot": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-resolve/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-resolve/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-resolve/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/jest-resolve/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/jest-resolve/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-resolve/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz", - "integrity": "sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==", - "dev": true, - "dependencies": { - "@jest/console": "^28.1.3", - "@jest/environment": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "graceful-fs": "^4.2.9", - "jest-docblock": "^28.1.1", - "jest-environment-node": "^28.1.3", - "jest-haste-map": "^28.1.3", - "jest-leak-detector": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-resolve": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-util": "^28.1.3", - "jest-watcher": "^28.1.3", - "jest-worker": "^28.1.3", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runner/node_modules/@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runner/node_modules/@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runner/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runner/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runner/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/jest-runner/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/jest-runner/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runner/node_modules/jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runner/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", - "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", - "dev": true, - "dependencies": { - "@jest/environment": "^28.1.3", - "@jest/fake-timers": "^28.1.3", - "@jest/globals": "^28.1.3", - "@jest/source-map": "^28.1.2", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime/node_modules/@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime/node_modules/@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-runtime/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-runtime/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/jest-runtime/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/jest-runtime/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-runtime/node_modules/jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-runtime/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-serializer": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", - "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/node": "*", - "graceful-fs": "^4.2.9" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-snapshot": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", - "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-haste-map": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "natural-compare": "^1.4.0", - "pretty-format": "^28.1.3", - "semver": "^7.3.5" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-snapshot/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-snapshot/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-snapshot/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/jest-snapshot/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/jest-snapshot/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-snapshot/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-util/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-util/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-util/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/jest-util/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/jest-util/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-util/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", - "integrity": "sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==", - "dev": true, - "dependencies": { - "@jest/types": "^28.1.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", - "leven": "^3.1.0", - "pretty-format": "^28.1.3" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-validate/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-validate/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-validate/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/jest-validate/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/jest-validate/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-validate/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", - "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", - "dev": true, - "dependencies": { - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "jest-util": "^28.1.3", - "string-length": "^4.0.1" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-watcher/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-watcher/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-watcher/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/jest-watcher/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/jest-watcher/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-watcher/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", - "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - } - }, - "node_modules/jest-worker/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-worker/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/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest/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/jest/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/jest/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest/node_modules/jest-cli": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", - "integrity": "sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==", - "dev": true, - "dependencies": { - "@jest/core": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" - }, - "peerDependenciesMeta": { - "node-notifier": { - "optional": true - } - } - }, - "node_modules/jest/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest/node_modules/yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", - "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.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/jest/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/jetifier": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/jetifier/-/jetifier-1.6.8.tgz", - "integrity": "sha512-3Zi16h6L5tXDRQJTb221cnRoVG9/9OvreLdLU2/ZjRv/GILL+2Cemt0IKvkowwkDpvouAU1DQPOJ7qaiHeIdrw==", - "devOptional": true, - "peer": true, - "bin": { - "jetifier": "bin/jetify", - "jetifier-standalone": "bin/jetifier-standalone", - "jetify": "bin/jetify" - } - }, - "node_modules/joi": { - "version": "17.6.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.0.tgz", - "integrity": "sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@hapi/hoek": "^9.0.0", - "@hapi/topo": "^5.0.0", - "@sideway/address": "^4.1.3", - "@sideway/formula": "^3.0.0", - "@sideway/pinpoint": "^2.0.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "devOptional": true - }, - "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==", - "devOptional": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true - }, - "node_modules/jsc-android": { - "version": "250230.2.1", - "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-250230.2.1.tgz", - "integrity": "sha512-KmxeBlRjwoqCnBBKGsihFtvsBHyUFlBxJPK4FzeYcIuBfdjv6jFys44JITAgSTbQD+vIdwMEfyZklsuQX0yI1Q==", - "devOptional": true, - "peer": true - }, - "node_modules/jscodeshift": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.13.1.tgz", - "integrity": "sha512-lGyiEbGOvmMRKgWk4vf+lUrCWO/8YR8sUR3FKF1Cq5fovjZDlIcw3Hu5ppLHAnEXshVffvaM0eyuY/AbOeYpnQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/core": "^7.13.16", - "@babel/parser": "^7.13.16", - "@babel/plugin-proposal-class-properties": "^7.13.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", - "@babel/plugin-proposal-optional-chaining": "^7.13.12", - "@babel/plugin-transform-modules-commonjs": "^7.13.8", - "@babel/preset-flow": "^7.13.13", - "@babel/preset-typescript": "^7.13.0", - "@babel/register": "^7.13.16", - "babel-core": "^7.0.0-bridge.0", - "chalk": "^4.1.2", - "flow-parser": "0.*", - "graceful-fs": "^4.2.4", - "micromatch": "^3.1.10", - "neo-async": "^2.5.0", - "node-dir": "^0.1.17", - "recast": "^0.20.4", - "temp": "^0.8.4", - "write-file-atomic": "^2.3.0" - }, - "bin": { - "jscodeshift": "bin/jscodeshift.js" - }, - "peerDependencies": { - "@babel/preset-env": "^7.1.6" - } - }, - "node_modules/jscodeshift/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jscodeshift/node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "devOptional": true, - "peer": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jscodeshift/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jscodeshift/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==", - "devOptional": true, - "peer": true - }, - "node_modules/jscodeshift/node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/jscodeshift/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "devOptional": true, - "peer": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/jscodeshift/node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "devOptional": true, - "peer": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/jscodeshift/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jscodeshift/node_modules/temp": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", - "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", - "devOptional": true, - "peer": true, - "dependencies": { - "rimraf": "~2.6.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/jscodeshift/node_modules/write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "node_modules/jsdom": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.0.tgz", - "integrity": "sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA==", - "dev": true, - "dependencies": { - "abab": "^2.0.6", - "acorn": "^8.7.1", - "acorn-globals": "^6.0.0", - "cssom": "^0.5.0", - "cssstyle": "^2.3.0", - "data-urls": "^3.0.2", - "decimal.js": "^10.3.1", - "domexception": "^4.0.0", - "escodegen": "^2.0.0", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "^7.0.0", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^3.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0", - "ws": "^8.8.0", - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/jsdom/node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/jsdom/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/jsdom/node_modules/tough-cookie": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", - "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", - "dev": true, - "dependencies": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsdom/node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "devOptional": true, - "peer": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/json-loader": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", - "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", - "dev": true - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "devOptional": true, - "peer": true - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://nexus.es.ecg.tools/repository/npm-all/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "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": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "devOptional": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/just-extend": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", - "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", - "dev": true - }, - "node_modules/karma": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.0.tgz", - "integrity": "sha512-s8m7z0IF5g/bS5ONT7wsOavhW4i4aFkzD4u4wgzAQWT4HGUeWI3i21cK2Yz6jndMAeHETp5XuNsRoyGJZXVd4w==", - "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-browserstack-launcher": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/karma-browserstack-launcher/-/karma-browserstack-launcher-1.6.0.tgz", - "integrity": "sha512-Y/UWPdHZkHIVH2To4GWHCTzmrsB6H7PBWy6pw+TWz5sr4HW2mcE+Uj6qWgoVNxvQU1Pfn5LQQzI6EQ65p8QbiQ==", - "dev": true, - "dependencies": { - "browserstack": "~1.5.1", - "browserstack-local": "^1.3.7", - "q": "~1.5.0" - }, - "peerDependencies": { - "karma": ">=0.9" - } - }, - "node_modules/karma-chai": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/karma-chai/-/karma-chai-0.1.0.tgz", - "integrity": "sha512-mqKCkHwzPMhgTYca10S90aCEX9+HjVjjrBFAsw36Zj7BlQNbokXXCAe6Ji04VUMsxcY5RLP7YphpfO06XOubdg==", - "dev": true, - "peerDependencies": { - "chai": "*", - "karma": ">=0.10.9" - } - }, - "node_modules/karma-chrome-launcher": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", - "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", - "dev": true, - "dependencies": { - "fs-access": "^1.0.0", - "which": "^1.2.1" - } - }, - "node_modules/karma-mocha": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-1.3.0.tgz", - "integrity": "sha512-twRO+KCXIFOBs7o6i7oIpTJhVvjKZbIsUM96A+k2QaeXOzbVQXCkjVzXqNeQoczW4ruasPZYi0iWMTkfTrQVCw==", - "dev": true, - "dependencies": { - "minimist": "1.2.0" - } - }, - "node_modules/karma-mocha/node_modules/minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha512-7Wl+Jz+IGWuSdgsQEJ4JunV0si/iMhg42MnQQG6h1R6TNeVenp4U9x5CC5v/gYqz/fENLQITAWXidNtVL0NNbw==", - "dev": true - }, - "node_modules/karma-webpack": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-5.0.0.tgz", - "integrity": "sha512-+54i/cd3/piZuP3dr54+NcFeKOPnys5QeM1IY+0SPASwrtHsliXUiCL50iW+K9WWA7RvamC4macvvQ86l3KtaA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3", - "minimatch": "^3.0.4", - "webpack-merge": "^4.1.5" - }, - "engines": { - "node": ">= 6" - }, - "peerDependencies": { - "webpack": "^5.0.0" - } - }, - "node_modules/karma/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/karma/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/karma/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/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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==", - "devOptional": true, - "peer": true, - "optionalDependencies": { - "graceful-fs": "^4.1.9" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "devOptional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/lcov-parse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", - "integrity": "sha512-aprLII/vPzuQvYZnDRU78Fns9I2Ag3gi4Ipga/hxnVMCZC8DnR2nI7XBqrPoywGfxqIx/DgarGvDJZAD3YBTgQ==", - "dev": true, - "bin": { - "lcov-parse": "bin/cli.js" - } - }, - "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==", - "devOptional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true, - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "devOptional": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "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==", - "devOptional": true - }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "devOptional": true, - "peer": true - }, - "node_modules/lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", - "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==", - "dev": true - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "node_modules/lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", - "devOptional": true, - "peer": true - }, - "node_modules/log-driver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", - "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", - "dev": true, - "engines": { - "node": ">=0.8.6" - } - }, - "node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "devOptional": true, - "peer": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-symbols/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-symbols/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/log-symbols/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/log-symbols/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==", - "devOptional": true, - "peer": true - }, - "node_modules/log-symbols/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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/log-symbols/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/log4js": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.6.1.tgz", - "integrity": "sha512-J8VYFH2UQq/xucdNu71io4Fo+purYYudyErgBbswWKO0MC6QVOERRomt5su/z6d3RJSmLyTGmXl3Q/XjKCf+/A==", - "dev": true, - "dependencies": { - "date-format": "^4.0.13", - "debug": "^4.3.4", - "flatted": "^3.2.6", - "rfdc": "^1.3.0", - "streamroller": "^3.1.2" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/logkitty": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", - "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-fragments": "^0.2.1", - "dayjs": "^1.8.15", - "yargs": "^15.1.0" - }, - "bin": { - "logkitty": "bin/logkitty.js" - } - }, - "node_modules/logkitty/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/logkitty/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/logkitty/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/logkitty/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/logkitty/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==", - "devOptional": true, - "peer": true - }, - "node_modules/logkitty/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/logkitty/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "devOptional": true, - "peer": true - }, - "node_modules/logkitty/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "devOptional": true, - "peer": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/logkitty/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/lolex": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", - "integrity": "sha512-/bpxDL56TG5LS5zoXxKqA6Ro5tkOS5M8cm/7yQcwLIKIcM2HR5fjjNCaIhJNv96SEk4hNGSafYMZK42Xv5fihQ==", - "dev": true - }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/loupe": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", - "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.0" - } - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "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==", - "devOptional": true, - "dependencies": { - "tmpl": "1.0.5" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", - "dev": true - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", - "devOptional": true, - "peer": true, - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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/memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", - "devOptional": true, - "peer": true - }, - "node_modules/merge-options": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", - "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", - "devOptional": true, - "dependencies": { - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "devOptional": true - }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/metro": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.70.3.tgz", - "integrity": "sha512-uEWS7xg8oTetQDABYNtsyeUjdLhH3KAvLFpaFFoJqUpOk2A3iygszdqmjobFl6W4zrvKDJS+XxdMR1roYvUhTw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "@babel/core": "^7.14.0", - "@babel/generator": "^7.14.0", - "@babel/parser": "^7.14.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.0.0", - "absolute-path": "^0.0.0", - "accepts": "^1.3.7", - "async": "^3.2.2", - "chalk": "^4.0.0", - "ci-info": "^2.0.0", - "connect": "^3.6.5", - "debug": "^2.2.0", - "denodeify": "^1.2.1", - "error-stack-parser": "^2.0.6", - "fs-extra": "^1.0.0", - "graceful-fs": "^4.2.4", - "hermes-parser": "0.6.0", - "image-size": "^0.6.0", - "invariant": "^2.2.4", - "jest-haste-map": "^27.3.1", - "jest-worker": "^27.2.0", - "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.70.3", - "metro-cache": "0.70.3", - "metro-cache-key": "0.70.3", - "metro-config": "0.70.3", - "metro-core": "0.70.3", - "metro-hermes-compiler": "0.70.3", - "metro-inspector-proxy": "0.70.3", - "metro-minify-uglify": "0.70.3", - "metro-react-native-babel-preset": "0.70.3", - "metro-resolver": "0.70.3", - "metro-runtime": "0.70.3", - "metro-source-map": "0.70.3", - "metro-symbolicate": "0.70.3", - "metro-transform-plugins": "0.70.3", - "metro-transform-worker": "0.70.3", - "mime-types": "^2.1.27", - "node-fetch": "^2.2.0", - "nullthrows": "^1.1.1", - "rimraf": "^2.5.4", - "serialize-error": "^2.1.0", - "source-map": "^0.5.6", - "strip-ansi": "^6.0.0", - "temp": "0.8.3", - "throat": "^5.0.0", - "ws": "^7.5.1", - "yargs": "^15.3.1" - }, - "bin": { - "metro": "src/cli.js" - } - }, - "node_modules/metro-babel-transformer": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.70.3.tgz", - "integrity": "sha512-bWhZRMn+mIOR/s3BDpFevWScz9sV8FGktVfMlF1eJBLoX24itHDbXvTktKBYi38PWIKcHedh6THSFpJogfuwNA==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/core": "^7.14.0", - "hermes-parser": "0.6.0", - "metro-source-map": "0.70.3", - "nullthrows": "^1.1.1" - } - }, - "node_modules/metro-cache": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.70.3.tgz", - "integrity": "sha512-iCix/+z812fUqa6KlOxaTkY6LQQDoXIe/VljXkGIvpygSCmYyhjQpfQVZEVVPezFmUBYXNdabdQ6cYx6JX3yMg==", - "devOptional": true, - "peer": true, - "dependencies": { - "metro-core": "0.70.3", - "rimraf": "^2.5.4" - } - }, - "node_modules/metro-cache-key": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.70.3.tgz", - "integrity": "sha512-0zpw+IcpM3hmGd5sKMdxNv3sbOIUYnMUvx1/yaM6vNRReSPmOLX0bP8fYf3CGgk8NEreZ1OHbVsuw7bdKt40Mw==", - "devOptional": true, - "peer": true - }, - "node_modules/metro-cache/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "devOptional": true, - "peer": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/metro-config": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.70.3.tgz", - "integrity": "sha512-SSCDjSTygoCgzoj61DdrBeJzZDRwQxUEfcgc6t6coxWSExXNR4mOngz0q4SAam49Bmjq9J2Jft6qUKnUTPrRgA==", - "devOptional": true, - "peer": true, - "dependencies": { - "cosmiconfig": "^5.0.5", - "jest-validate": "^26.5.2", - "metro": "0.70.3", - "metro-cache": "0.70.3", - "metro-core": "0.70.3", - "metro-runtime": "0.70.3" - } - }, - "node_modules/metro-config/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/metro-config/node_modules/@types/yargs": { - "version": "15.0.14", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", - "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/metro-config/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/metro-config/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/metro-config/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/metro-config/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==", - "devOptional": true, - "peer": true - }, - "node_modules/metro-config/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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro-config/node_modules/jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/metro-config/node_modules/jest-validate": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", - "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@jest/types": "^26.6.2", - "camelcase": "^6.0.0", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "leven": "^3.1.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/metro-config/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/metro-config/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "devOptional": true, - "peer": true - }, - "node_modules/metro-config/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro-core": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.70.3.tgz", - "integrity": "sha512-NzfHB/w5R7yLaOeU1tzPTbBzCRsYSvpKJkLMP0yudszKZzIAZqNdjoEJ9GZ688Wi0ynZxcU0BxukXh4my80ZBw==", - "devOptional": true, - "peer": true, - "dependencies": { - "jest-haste-map": "^27.3.1", - "lodash.throttle": "^4.1.1", - "metro-resolver": "0.70.3" - } - }, - "node_modules/metro-core/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/metro-core/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/metro-core/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/metro-core/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/metro-core/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/metro-core/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==", - "devOptional": true, - "peer": true - }, - "node_modules/metro-core/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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro-core/node_modules/jest-haste-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", - "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", - "devOptional": true, - "peer": true, - "dependencies": { - "@jest/types": "^27.5.1", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^27.5.1", - "jest-serializer": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/metro-core/node_modules/jest-regex-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", - "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", - "devOptional": true, - "peer": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/metro-core/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/metro-core/node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/metro-core/node_modules/jest-worker/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/metro-core/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro-hermes-compiler": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-hermes-compiler/-/metro-hermes-compiler-0.70.3.tgz", - "integrity": "sha512-W6WttLi4E72JL/NyteQ84uxYOFMibe0PUr9aBKuJxxfCq6QRnJKOVcNY0NLW0He2tneXGk+8ZsNz8c0flEvYqg==", - "devOptional": true, - "peer": true - }, - "node_modules/metro-inspector-proxy": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-inspector-proxy/-/metro-inspector-proxy-0.70.3.tgz", - "integrity": "sha512-qQoNdPGrmyoJSWYkxSDpTaAI8xyqVdNDVVj9KRm1PG8niSuYmrCCFGLLFsMvkVYwsCWUGHoGBx0UoAzVp14ejw==", - "devOptional": true, - "peer": true, - "dependencies": { - "connect": "^3.6.5", - "debug": "^2.2.0", - "ws": "^7.5.1", - "yargs": "^15.3.1" - }, - "bin": { - "metro-inspector-proxy": "src/cli.js" - } - }, - "node_modules/metro-inspector-proxy/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/metro-inspector-proxy/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/metro-inspector-proxy/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/metro-inspector-proxy/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/metro-inspector-proxy/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==", - "devOptional": true, - "peer": true - }, - "node_modules/metro-inspector-proxy/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/metro-inspector-proxy/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro-inspector-proxy/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro-inspector-proxy/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - }, - "node_modules/metro-inspector-proxy/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/metro-inspector-proxy/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro-inspector-proxy/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro-inspector-proxy/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro-inspector-proxy/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/metro-inspector-proxy/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "devOptional": true, - "peer": true - }, - "node_modules/metro-inspector-proxy/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "devOptional": true, - "peer": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro-inspector-proxy/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/metro-minify-uglify": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-minify-uglify/-/metro-minify-uglify-0.70.3.tgz", - "integrity": "sha512-oHyjV9WDqOlDE1FPtvs6tIjjeY/oP1PNUPYL1wqyYtqvjN+zzAOrcbsAAL1sv+WARaeiMsWkF2bwtNo+Hghoog==", - "devOptional": true, - "peer": true, - "dependencies": { - "uglify-es": "^3.1.9" - } - }, - "node_modules/metro-react-native-babel-preset": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.70.3.tgz", - "integrity": "sha512-4Nxc1zEiHEu+GTdEMEsHnRgfaBkg8f/Td3+FcQ8NTSvs+xL3LBrQy6N07idWSQZHIdGFf+tTHvRfSIWLD8u8Tg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/core": "^7.14.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.2.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.0.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.0.0", - "@babel/plugin-transform-exponentiation-operator": "^7.0.0", - "@babel/plugin-transform-flow-strip-types": "^7.0.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-template-literals": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "react-refresh": "^0.4.0" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/metro-react-native-babel-transformer": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.70.3.tgz", - "integrity": "sha512-WKBU6S/G50j9cfmFM4k4oRYprd8u3qjleD4so1E2zbTNILg+gYla7ZFGCAvi2G0ZcqS2XuGCR375c2hF6VVvwg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/core": "^7.14.0", - "babel-preset-fbjs": "^3.4.0", - "hermes-parser": "0.6.0", - "metro-babel-transformer": "0.70.3", - "metro-react-native-babel-preset": "0.70.3", - "metro-source-map": "0.70.3", - "nullthrows": "^1.1.1" - }, - "peerDependencies": { - "@babel/core": "*" - } - }, - "node_modules/metro-resolver": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.70.3.tgz", - "integrity": "sha512-5Pc5S/Gs4RlLbziuIWtvtFd9GRoILlaRC8RZDVq5JZWcWHywKy/PjNmOBNhpyvtRlzpJfy/ssIfLhu8zINt1Mw==", - "devOptional": true, - "peer": true, - "dependencies": { - "absolute-path": "^0.0.0" - } - }, - "node_modules/metro-runtime": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.70.3.tgz", - "integrity": "sha512-22xU7UdXZacniTIDZgN2EYtmfau2pPyh97Dcs+cWrLcJYgfMKjWBtesnDcUAQy3PHekDYvBdJZkoQUeskYTM+w==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/runtime": "^7.0.0" - } - }, - "node_modules/metro-source-map": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.70.3.tgz", - "integrity": "sha512-zsYtZGrwRbbGEFHtmMqqeCH9K9aTGNVPsurMOWCUeQA3VGyVGXPGtLMC+CdAM9jLpUyg6jw2xh0esxi+tYH7Uw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.0.0", - "invariant": "^2.2.4", - "metro-symbolicate": "0.70.3", - "nullthrows": "^1.1.1", - "ob1": "0.70.3", - "source-map": "^0.5.6", - "vlq": "^1.0.0" - } - }, - "node_modules/metro-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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/metro-symbolicate": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.70.3.tgz", - "integrity": "sha512-JTYkF1dpeDUssQ84juE1ycnhHki2ylJBBdJE1JHtfu5oC+z1ElDbBdPHq90Uvt8HbRov/ZAnxvv7Zy6asS+WCA==", - "devOptional": true, - "peer": true, - "dependencies": { - "invariant": "^2.2.4", - "metro-source-map": "0.70.3", - "nullthrows": "^1.1.1", - "source-map": "^0.5.6", - "through2": "^2.0.1", - "vlq": "^1.0.0" - }, - "bin": { - "metro-symbolicate": "src/index.js" - }, - "engines": { - "node": ">=8.3" - } - }, - "node_modules/metro-symbolicate/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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/metro-transform-plugins": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.70.3.tgz", - "integrity": "sha512-dQRIJoTkWZN2IVS2KzgS1hs7ZdHDX3fS3esfifPkqFAEwHiLctCf0EsPgIknp0AjMLvmGWfSLJigdRB/dc0ASw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/core": "^7.14.0", - "@babel/generator": "^7.14.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.14.0", - "nullthrows": "^1.1.1" - } - }, - "node_modules/metro-transform-worker": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.70.3.tgz", - "integrity": "sha512-MtVVsnHhhBOp9GRLCdAb2mD1dTCsIzT4+m34KMRdBDCEbDIb90YafT5prpU8qbj5uKd0o2FOQdrJ5iy5zQilHw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/core": "^7.14.0", - "@babel/generator": "^7.14.0", - "@babel/parser": "^7.14.0", - "@babel/types": "^7.0.0", - "babel-preset-fbjs": "^3.4.0", - "metro": "0.70.3", - "metro-babel-transformer": "0.70.3", - "metro-cache": "0.70.3", - "metro-cache-key": "0.70.3", - "metro-hermes-compiler": "0.70.3", - "metro-source-map": "0.70.3", - "metro-transform-plugins": "0.70.3", - "nullthrows": "^1.1.1" - } - }, - "node_modules/metro/node_modules/@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/metro/node_modules/@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/metro/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/metro/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/metro/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/metro/node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "devOptional": true, - "peer": true - }, - "node_modules/metro/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/metro/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/metro/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==", - "devOptional": true, - "peer": true - }, - "node_modules/metro/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/metro/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro/node_modules/fs-extra": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", - "integrity": "sha512-VerQV6vEKuhDWD2HGOybV6v5I73syoc/cXAbKlgTC7M/oFVEtklWlp9QH2Ijw3IaWDOQcMkldSPa7zXy79Z/UQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0" - } - }, - "node_modules/metro/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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro/node_modules/jest-haste-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", - "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", - "devOptional": true, - "peer": true, - "dependencies": { - "@jest/types": "^27.5.1", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^27.5.1", - "jest-serializer": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - }, - "optionalDependencies": { - "fsevents": "^2.3.2" - } - }, - "node_modules/metro/node_modules/jest-regex-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", - "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", - "devOptional": true, - "peer": true, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/metro/node_modules/jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/metro/node_modules/jest-util/node_modules/ci-info": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.4.0.tgz", - "integrity": "sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug==", - "devOptional": true, - "peer": true - }, - "node_modules/metro/node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/metro/node_modules/jest-worker/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, - "node_modules/metro/node_modules/jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", - "devOptional": true, - "peer": true, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "node_modules/metro/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - }, - "node_modules/metro/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/metro/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "devOptional": true, - "peer": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/metro/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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/metro/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/metro/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "devOptional": true, - "peer": true - }, - "node_modules/metro/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "devOptional": true, - "peer": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/metro/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "devOptional": true, - "dependencies": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "devOptional": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "devOptional": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "devOptional": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "devOptional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/mimic-response": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", - "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "devOptional": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "devOptional": true - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "devOptional": true, - "peer": true, - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "devOptional": true, - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", - "dev": true, - "dependencies": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/mocha-lcov-reporter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/mocha-lcov-reporter/-/mocha-lcov-reporter-1.3.0.tgz", - "integrity": "sha512-/5zI2tW4lq/ft8MGpYQ1nIH6yePPtIzdGeUEwFMKfMRdLfAQ1QW2c68eEJop32tNdN5srHa/E2TzB+erm3YMYA==", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/mocha/node_modules/debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/mocha/node_modules/glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha/node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha/node_modules/minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", - "dev": true - }, - "node_modules/mocha/node_modules/mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", - "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", - "dev": true, - "dependencies": { - "minimist": "0.0.8" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/mocha/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/mocha/node_modules/supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "devOptional": true - }, - "node_modules/murmurhash": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/murmurhash/-/murmurhash-2.0.1.tgz", - "integrity": "sha512-5vQEh3y+DG/lMPM0mCGPDnyV8chYg/g7rl6v3Gd8WMF9S429ox3Xk8qrk174kWhG767KQMqqxLD1WnGd77hiew==" - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "devOptional": true, - "peer": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/native-promise-only": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", - "integrity": "sha512-zkVhZUA3y8mbz652WrL5x0fB0ehrBkulWT3TomAQ9iDtyXZvzKeEA6GPxAItBYeNYl5yngKRX612qHOhvMkDeg==", - "dev": true - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "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==", - "devOptional": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "devOptional": true - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "devOptional": true, - "peer": true - }, - "node_modules/nise": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", - "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", - "dev": true, - "dependencies": { - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "lolex": "^5.0.1", - "path-to-regexp": "^1.7.0" - } - }, - "node_modules/nise/node_modules/lolex": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", - "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/nocache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", - "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/nock": { - "version": "11.9.1", - "resolved": "https://registry.npmjs.org/nock/-/nock-11.9.1.tgz", - "integrity": "sha512-U5wPctaY4/ar2JJ5Jg4wJxlbBfayxgKbiAeGh+a1kk6Pwnc2ZEuKviLyDSG6t0uXl56q7AALIxoM6FJrBSsVXA==", - "dev": true, - "dependencies": { - "debug": "^4.1.0", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.13", - "mkdirp": "^0.5.0", - "propagate": "^2.0.0" - }, - "engines": { - "node": ">= 8.0" - } - }, - "node_modules/node-dir": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", - "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==", - "devOptional": true, - "peer": true, - "dependencies": { - "minimatch": "^3.0.2" - }, - "engines": { - "node": ">= 0.10.5" - } - }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "node_modules/node-fetch/node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "devOptional": true, - "peer": true - }, - "node_modules/node-fetch/node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "devOptional": true, - "peer": true - }, - "node_modules/node-fetch/node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "devOptional": true, - "peer": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, - "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==", - "devOptional": true - }, - "node_modules/node-preload": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", - "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", - "dev": true, - "dependencies": { - "process-on-spawn": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", - "devOptional": true - }, - "node_modules/node-stream-zip": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", - "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.12.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/antelle" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "devOptional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/null-check": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", - "integrity": "sha512-j8ZNHg19TyIQOWCGeeQJBuu6xZYIEurf8M1Qsfd8mFrGEfIZytbw18YjKWg+LcO25NowXGZXZpKAx+Ui3TFfDw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nullthrows": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", - "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", - "devOptional": true, - "peer": true - }, - "node_modules/nwsapi": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.1.tgz", - "integrity": "sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg==", - "dev": true - }, - "node_modules/nyc": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", - "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", - "dev": true, - "dependencies": { - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "caching-transform": "^4.0.0", - "convert-source-map": "^1.7.0", - "decamelize": "^1.2.0", - "find-cache-dir": "^3.2.0", - "find-up": "^4.1.0", - "foreground-child": "^2.0.0", - "get-package-type": "^0.1.0", - "glob": "^7.1.6", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-hook": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", - "istanbul-lib-processinfo": "^2.0.2", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "make-dir": "^3.0.0", - "node-preload": "^0.2.1", - "p-map": "^3.0.0", - "process-on-spawn": "^1.0.0", - "resolve-from": "^5.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "spawn-wrap": "^2.0.0", - "test-exclude": "^6.0.0", - "yargs": "^15.0.2" - }, - "bin": { - "nyc": "bin/nyc.js" - }, - "engines": { - "node": ">=8.9" - } - }, - "node_modules/nyc/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/nyc/node_modules/append-transform": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", - "dev": true, - "dependencies": { - "default-require-extensions": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/nyc/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/nyc/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/nyc/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/nyc/node_modules/default-require-extensions": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", - "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", - "dev": true, - "dependencies": { - "strip-bom": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/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==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/istanbul-lib-hook": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", - "dev": true, - "dependencies": { - "append-transform": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/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": ">=8" - } - }, - "node_modules/nyc/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==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/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/nyc/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/nyc/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/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/nyc/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/nyc/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/nyc/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==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "node_modules/nyc/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/ob1": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.70.3.tgz", - "integrity": "sha512-Vy9GGhuXgDRY01QA6kdhToPd8AkLdLpX9GjH5kpqluVqTu70mgOm7tpGoJDZGaNbr9nJlJgnipqHJQRPORixIQ==", - "devOptional": true, - "peer": true - }, - "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==", - "devOptional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "dev": true, - "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", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", - "devOptional": true, - "peer": true, - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "devOptional": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "devOptional": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "devOptional": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/open": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", - "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-wsl": "^1.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "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" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ora/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ora/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==", - "devOptional": true, - "peer": true - }, - "node_modules/ora/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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "devOptional": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "devOptional": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "devOptional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/package-hash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", - "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.15", - "hasha": "^5.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse5": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.0.0.tgz", - "integrity": "sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g==", - "dev": true, - "dependencies": { - "entities": "^4.3.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "devOptional": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "devOptional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "devOptional": true - }, - "node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "dependencies": { - "isarray": "0.0.1" - } - }, - "node_modules/path-to-regexp/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", - "dev": true, - "dependencies": { - "through": "~2.3" - } - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "devOptional": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "devOptional": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "devOptional": 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", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/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==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/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==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/pkg-dir/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/pkg-dir/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/pkg-dir/node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/plist": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.6.tgz", - "integrity": "sha512-WiIVYyrp8TD4w8yCvyeIr+lkmrGRd5u0VbRnU+tP/aRLxP/YadJUYOMZJ/6hIa3oUyVCsycXvtNRgd5XBJIbiA==", - "devOptional": true, - "peer": true, - "dependencies": { - "base64-js": "^1.5.1", - "xmlbuilder": "^15.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "dependencies": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.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==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "devOptional": true, - "peer": true - }, - "node_modules/process-on-spawn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", - "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", - "dev": true, - "dependencies": { - "fromentries": "^1.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/promise": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.2.0.tgz", - "integrity": "sha512-+CMAlLHqwRYwBMXKCP+o8ns7DN+xHDUiI+0nArsiJ9y+kJVPLFxEaSw6Ha9s9H0tftxg2Yzl25wqj9G7m5wLZg==", - "devOptional": true, - "peer": true, - "dependencies": { - "asap": "~2.0.6" - } - }, - "node_modules/promise-polyfill": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.0.tgz", - "integrity": "sha512-OzSf6gcCUQ01byV4BgwyUCswlaQQ6gzXc23aLQWhicvfX9kfsUiUhgt3CCQej8jDnl8/PhGF31JdHX2/MzF3WA==", - "dev": true - }, - "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==", - "devOptional": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/propagate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", - "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/ps-tree": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", - "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", - "dev": true, - "dependencies": { - "event-stream": "=3.3.4" - }, - "bin": { - "ps-tree": "bin/ps-tree.js" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "devOptional": true, - "peer": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", - "dev": true, - "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" - } - }, - "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, - "engines": { - "node": ">=0.9" - } - }, - "node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "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/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "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==", - "devOptional": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "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/react": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.0.0.tgz", - "integrity": "sha512-x+VL6wbT4JRVPm7EGxXhZ8w8LTROaxPXOqhlGyVSrv0sB1jkyFGgXxJ8LVoPRLvPR6/CIZGFmfzqUa2NYeMr2A==", - "devOptional": true, - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-devtools-core": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.24.0.tgz", - "integrity": "sha512-Rw7FzYOOzcfyUPaAm9P3g0tFdGqGq2LLiAI+wjYcp6CsF3DeeMrRS3HZAho4s273C29G/DJhx0e8BpRE/QZNGg==", - "devOptional": true, - "peer": true, - "dependencies": { - "shell-quote": "^1.6.1", - "ws": "^7" - } - }, - "node_modules/react-devtools-core/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "devOptional": true - }, - "node_modules/react-native": { - "version": "0.69.5", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.69.5.tgz", - "integrity": "sha512-4Psrj1nDMLQjBXVH8n3UikzOHQc8+sa6NbxZQR0XKtpx8uC3HiJBgX+/FIum/RWxfi5J/Dt/+A2gLGmq2Hps8g==", - "devOptional": true, - "peer": true, - "dependencies": { - "@jest/create-cache-key-function": "^27.0.1", - "@react-native-community/cli": "^8.0.4", - "@react-native-community/cli-platform-android": "^8.0.4", - "@react-native-community/cli-platform-ios": "^8.0.4", - "@react-native/assets": "1.0.0", - "@react-native/normalize-color": "2.0.0", - "@react-native/polyfills": "2.0.0", - "abort-controller": "^3.0.0", - "anser": "^1.4.9", - "base64-js": "^1.1.2", - "event-target-shim": "^5.0.1", - "hermes-engine": "~0.11.0", - "invariant": "^2.2.4", - "jsc-android": "^250230.2.1", - "memoize-one": "^5.0.0", - "metro-react-native-babel-transformer": "0.70.3", - "metro-runtime": "0.70.3", - "metro-source-map": "0.70.3", - "mkdirp": "^0.5.1", - "nullthrows": "^1.1.1", - "pretty-format": "^26.5.2", - "promise": "^8.0.3", - "react-devtools-core": "4.24.0", - "react-native-codegen": "^0.69.2", - "react-native-gradle-plugin": "^0.0.7", - "react-refresh": "^0.4.0", - "react-shallow-renderer": "16.15.0", - "regenerator-runtime": "^0.13.2", - "scheduler": "^0.21.0", - "stacktrace-parser": "^0.1.3", - "use-sync-external-store": "^1.0.0", - "whatwg-fetch": "^3.0.0", - "ws": "^6.1.4" - }, - "bin": { - "react-native": "cli.js" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "react": "18.0.0" - } - }, - "node_modules/react-native-codegen": { - "version": "0.69.2", - "resolved": "https://registry.npmjs.org/react-native-codegen/-/react-native-codegen-0.69.2.tgz", - "integrity": "sha512-yPcgMHD4mqLbckqnWjFBaxomDnBREfRjDi2G/WxNyPBQLD+PXUEmZTkDx6QoOXN+Bl2SkpnNOSsLE2+/RUHoPw==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/parser": "^7.14.0", - "flow-parser": "^0.121.0", - "jscodeshift": "^0.13.1", - "nullthrows": "^1.1.1" - } - }, - "node_modules/react-native-gradle-plugin": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/react-native-gradle-plugin/-/react-native-gradle-plugin-0.0.7.tgz", - "integrity": "sha512-+4JpbIx42zGTONhBTIXSyfyHICHC29VTvhkkoUOJAh/XHPEixpuBduYgf6Y4y9wsN1ARlQhBBoptTvXvAFQf5g==", - "devOptional": true, - "peer": true - }, - "node_modules/react-native/node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/react-native/node_modules/@types/yargs": { - "version": "15.0.14", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", - "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/react-native/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/react-native/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/react-native/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==", - "devOptional": true, - "peer": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/react-native/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==", - "devOptional": true, - "peer": true - }, - "node_modules/react-native/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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/react-native/node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/react-native/node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "devOptional": true, - "peer": true - }, - "node_modules/react-native/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/react-native/node_modules/ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", - "devOptional": true, - "peer": true, - "dependencies": { - "async-limiter": "~1.0.0" - } - }, - "node_modules/react-refresh": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz", - "integrity": "sha512-Hwln1VNuGl/6bVwnd0Xdn1e84gT/8T9aYNL+HAKDArLCS7LWjwr7StE30IEYbIkx0Vi3vs+coQxe+SQDbGbbpA==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-shallow-renderer": { - "version": "16.15.0", - "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", - "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", - "devOptional": true, - "peer": true, - "dependencies": { - "object-assign": "^4.1.1", - "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" - }, - "peerDependencies": { - "react": "^16.0.0 || ^17.0.0 || ^18.0.0" - } - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "devOptional": true, - "peer": 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", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/readline": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", - "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==", - "devOptional": true, - "peer": true - }, - "node_modules/recast": { - "version": "0.20.5", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.20.5.tgz", - "integrity": "sha512-E5qICoPoNL4yU0H0NoBDntNB0Q5oMSNh9usFctYniLBluTthi3RsQVBXIJNbApOlvSwW/RGxIuokPcAc59J5fQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "ast-types": "0.14.2", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tslib": "^2.0.1" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "devOptional": true, - "peer": true - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "peer": true - }, - "node_modules/regenerator-transform": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", - "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", - "devOptional": true, - "peer": true, - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "devOptional": true, - "peer": true, - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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/regexpu-core": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.1.tgz", - "integrity": "sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsgen": "^0.7.1", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", - "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==", - "devOptional": true, - "peer": true - }, - "node_modules/regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", - "dev": true, - "dependencies": { - "es6-error": "^4.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", - "dev": true, - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/request/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "devOptional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "devOptional": true - }, - "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.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "devOptional": true, - "dependencies": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/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/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/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", - "deprecated": "https://github.com/lydell/resolve-url#deprecated", - "devOptional": true, - "peer": true - }, - "node_modules/resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "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==", - "devOptional": true, - "peer": true, - "dependencies": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.2.0.tgz", - "integrity": "sha512-iAu/j9/WJ0i+zT0sAMuQnsEbmOKzdQ4Yxu5rbPs9aUCyqveI1Kw3H4Fi9NWfCOpb8luEySD2lDyFWL9CrLE8iw==", - "dev": true, - "bin": { - "rollup": "dist/bin/rollup" - }, - "engines": { - "node": ">=10.0.0" - }, - "optionalDependencies": { - "fsevents": "~2.1.2" - } - }, - "node_modules/rollup-plugin-terser": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-5.3.1.tgz", - "integrity": "sha512-1pkwkervMJQGFYvM9nscrUoncPwiKR/K+bHdjv6PFgRo3cgPHoRT83y2Aa3GvINj4539S15t/tpFPb775TDs6w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.5.5", - "jest-worker": "^24.9.0", - "rollup-pluginutils": "^2.8.2", - "serialize-javascript": "^4.0.0", - "terser": "^4.6.2" - }, - "peerDependencies": { - "rollup": ">=0.66.0 <3" - } - }, - "node_modules/rollup-plugin-terser/node_modules/jest-worker": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", - "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", - "dev": true, - "dependencies": { - "merge-stream": "^2.0.0", - "supports-color": "^6.1.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/rollup-plugin-terser/node_modules/supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/rollup-plugin-typescript2": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.27.3.tgz", - "integrity": "sha512-gmYPIFmALj9D3Ga1ZbTZAKTXq1JKlTQBtj299DXhqYz9cL3g/AQfUvbb2UhH+Nf++cCq941W2Mv7UcrcgLzJJg==", - "dev": true, - "dependencies": { - "@rollup/pluginutils": "^3.1.0", - "find-cache-dir": "^3.3.1", - "fs-extra": "8.1.0", - "resolve": "1.17.0", - "tslib": "2.0.1" - }, - "peerDependencies": { - "rollup": ">=1.26.3", - "typescript": ">=2.4.0" - } - }, - "node_modules/rollup-plugin-typescript2/node_modules/resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "dependencies": { - "path-parse": "^1.0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/rollup-plugin-typescript2/node_modules/tslib": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", - "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", - "dev": true - }, - "node_modules/rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", - "dev": true, - "dependencies": { - "estree-walker": "^0.6.1" - } - }, - "node_modules/rollup-pluginutils/node_modules/estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", - "dev": true - }, - "node_modules/rollup/node_modules/fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "deprecated": "\"Please update to latest v2.3 or v2.2\"", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "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": { - "queue-microtask": "^1.2.2" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "devOptional": 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/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", - "devOptional": true, - "peer": true, - "dependencies": { - "ret": "~0.1.10" - } - }, - "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/samsam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", - "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", - "deprecated": "This package has been deprecated in favour of @sinonjs/samsam", - "dev": true - }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } - }, - "node_modules/scheduler": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz", - "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - } - }, - "node_modules/schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - } - }, - "node_modules/semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "devOptional": true, - "peer": true, - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - }, - "node_modules/send/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "devOptional": true, - "peer": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "devOptional": true, - "peer": true - }, - "node_modules/serialize-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", - "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "devOptional": true, - "peer": true, - "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "devOptional": true - }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "devOptional": true, - "peer": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "devOptional": true - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/shell-quote": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", - "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", - "devOptional": true, - "peer": true - }, - "node_modules/side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "devOptional": true - }, - "node_modules/sinon": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-2.4.1.tgz", - "integrity": "sha512-vFTrO9Wt0ECffDYIPSP/E5bBugt0UjcBQOfQUMh66xzkyPEnhl/vM2LRZi2ajuTdkH07sA6DzrM6KvdvGIH8xw==", - "dev": true, - "dependencies": { - "diff": "^3.1.0", - "formatio": "1.2.0", - "lolex": "^1.6.0", - "native-promise-only": "^0.8.1", - "path-to-regexp": "^1.7.0", - "samsam": "^1.1.3", - "text-encoding": "0.6.4", - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=0.1.103" - } - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "devOptional": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "devOptional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "devOptional": true, - "peer": true, - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "devOptional": true, - "peer": true, - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - }, - "node_modules/snapdragon/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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/socket.io": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.1.tgz", - "integrity": "sha512-0y9pnIso5a9i+lJmsCdtmTTgJFFSvNQKDnPQRz28mGNnxbmqYg2QPtJTLFxhymFZhAIn50eHAKzJeiNaKr+yUQ==", - "dev": true, - "dependencies": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "debug": "~4.3.2", - "engine.io": "~6.2.0", - "socket.io-adapter": "~2.4.0", - "socket.io-parser": "~4.0.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-adapter": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", - "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==", - "dev": true - }, - "node_modules/socket.io-parser": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.5.tgz", - "integrity": "sha512-sNjbT9dX63nqUFIOv95tTVm6elyIU4RvB1m8dOeZt+IgWwcWklFDOdmGcfo3zSiRsnR/3pJkjY5lfoGqEe4Eig==", - "dev": true, - "dependencies": { - "@types/component-emitter": "^1.2.10", - "component-emitter": "~1.3.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", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", - "devOptional": true, - "peer": true, - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "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==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "deprecated": "See https://github.com/lydell/source-map-url#deprecated", - "devOptional": true, - "peer": true - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true - }, - "node_modules/spawn-wrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", - "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", - "dev": true, - "dependencies": { - "foreground-child": "^2.0.0", - "is-windows": "^1.0.2", - "make-dir": "^3.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "which": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/spawn-wrap/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/spawn-wrap/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", - "dev": true, - "dependencies": { - "through": "2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "devOptional": true, - "peer": true, - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "devOptional": true - }, - "node_modules/sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "dev": true, - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/stackframe": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", - "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", - "devOptional": true, - "peer": true - }, - "node_modules/stacktrace-parser": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", - "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", - "devOptional": true, - "peer": true, - "dependencies": { - "type-fest": "^0.7.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/stacktrace-parser/node_modules/type-fest": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", - "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", - "devOptional": true, - "peer": true, - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "devOptional": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", - "dev": true, - "dependencies": { - "duplexer": "~0.1.1" - } - }, - "node_modules/streamroller": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.2.tgz", - "integrity": "sha512-wZswqzbgGGsXYIrBYhOE0yP+nQ6XRk7xDcYwuQAGTYXdyAUmvgVFE0YU1g5pvQT0m7GBaQfYcSnlHbapuK0H0A==", - "dev": true, - "dependencies": { - "date-format": "^4.0.13", - "debug": "^4.3.4", - "fs-extra": "^8.1.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "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": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "devOptional": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "devOptional": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "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==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/sudo-prompt": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", - "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==", - "devOptional": true, - "peer": true - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "devOptional": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "devOptional": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/temp": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", - "integrity": "sha512-jtnWJs6B1cZlHs9wPG7BrowKxZw/rf6+UpGAkr8AaYmiTyTO7zQlLoST8zx/8TcUPnZmeBoB+H8ARuHZaSijVw==", - "devOptional": true, - "engines": [ - "node >=0.8.0" - ], - "peer": true, - "dependencies": { - "os-tmpdir": "^1.0.0", - "rimraf": "~2.2.6" - } - }, - "node_modules/temp-fs": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/temp-fs/-/temp-fs-0.9.9.tgz", - "integrity": "sha512-WfecDCR1xC9b0nsrzSaxPf3ZuWeWLUWblW4vlDQAa1biQaKHiImHnJfeQocQe/hXKMcolRzgkcVX/7kK4zoWbw==", - "dev": true, - "dependencies": { - "rimraf": "~2.5.2" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/temp-fs/node_modules/rimraf": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", - "integrity": "sha512-Lw7SHMjssciQb/rRz7JyPIy9+bbUshEucPoLRvWqy09vC5zQixl8Uet+Zl+SROBB/JMWHJRdCk1qdxNWHNMvlQ==", - "dev": true, - "dependencies": { - "glob": "^7.0.5" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/temp/node_modules/rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha512-R5KMKHnPAQaZMqLOsyuyUmcIjSeDm+73eoqQpaXA7AZ22BL+6C+1mcUscgOsNd8WVlJuvlgAPsegcx7pjlV0Dg==", - "devOptional": true, - "peer": true, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terser": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", - "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", - "dev": true, - "dependencies": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.3.tgz", - "integrity": "sha512-Fx60G5HNYknNTNQnzQ1VePRuu89ZVYWfjRAeT5rITuCY/1b08s49e5kSQwHDirKZWuoKOBRFS98EUUoZ9kLEwQ==", - "dev": true, - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.7", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "terser": "^5.7.2" - }, - "engines": { - "node": ">= 10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "webpack": "^5.1.0" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "uglify-js": { - "optional": true - } - } - }, - "node_modules/terser-webpack-plugin/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/terser-webpack-plugin/node_modules/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/terser-webpack-plugin/node_modules/jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/terser-webpack-plugin/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/terser-webpack-plugin/node_modules/terser": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", - "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", - "dev": true, - "dependencies": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/terser/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/terser/node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "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==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-encoding": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", - "integrity": "sha512-hJnc6Qg3dWoOMkqP53F0dzRIgtmsAge09kxUIqGrEUS4qr5rWLckGYaQAVr+opBrIMRErGgy6f5aPnyPpyGRfg==", - "deprecated": "no longer maintained", - "dev": true - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "node_modules/throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "devOptional": true, - "peer": true - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/through2/node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "devOptional": true, - "peer": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/through2/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "devOptional": true, - "peer": true - }, - "node_modules/through2/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "devOptional": true, - "peer": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "devOptional": true - }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "devOptional": true, - "peer": true, - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range/node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "devOptional": true, - "peer": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.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==", - "devOptional": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "dev": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/ts-jest": { - "version": "28.0.8", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.8.tgz", - "integrity": "sha512-5FaG0lXmRPzApix8oFG8RKjAz4ehtm8yMKOTy5HX3fY6W8kmvOrmcY0hKDElW52FJov+clhUbrKAqofnj4mXTg==", - "dev": true, - "dependencies": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^28.0.0", - "json5": "^2.2.1", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "7.x", - "yargs-parser": "^21.0.1" - }, - "bin": { - "ts-jest": "cli.js" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0" - }, - "peerDependencies": { - "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/types": "^28.0.0", - "babel-jest": "^28.0.0", - "jest": "^28.0.0", - "typescript": ">=4.3" - }, - "peerDependenciesMeta": { - "@babel/core": { - "optional": true - }, - "@jest/types": { - "optional": true - }, - "babel-jest": { - "optional": true - }, - "esbuild": { - "optional": true - } - } - }, - "node_modules/ts-jest/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/ts-loader": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.3.1.tgz", - "integrity": "sha512-OkyShkcZTsTwyS3Kt7a4rsT/t2qvEVQuKCTg4LJmpj9fhFR7ukGdZwV6Qq3tRUkqcXtfGpPR7+hFKHCG/0d3Lw==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "typescript": "*", - "webpack": "^5.0.0" - } - }, - "node_modules/ts-loader/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ts-loader/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ts-loader/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/ts-loader/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/ts-loader/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, - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-loader/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ts-mockito": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/ts-mockito/-/ts-mockito-2.6.1.tgz", - "integrity": "sha512-qU9m/oEBQrKq5hwfbJ7MgmVN5Gu6lFnIGWvpxSjrqq6YYEVv+RwVFWySbZMBgazsWqv6ctAyVBpo9TmAxnOEKw==", - "dev": true, - "dependencies": { - "lodash": "^4.17.5" - } - }, - "node_modules/ts-node": { - "version": "8.10.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", - "integrity": "sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==", - "dev": true, - "dependencies": { - "arg": "^4.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "engines": { - "node": ">=6.0.0" - }, - "peerDependencies": { - "typescript": ">=2.7" - } - }, - "node_modules/ts-node/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/ts-node/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ts-node/node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "devOptional": true - }, - "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, - "dependencies": { - "tslib": "^1.8.1" - }, - "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_modules/tsutils/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 - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "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.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "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/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/ua-parser-js": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", - "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - } - ], - "engines": { - "node": "*" - } - }, - "node_modules/uglify-es": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", - "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", - "deprecated": "support for ECMAScript is superseded by `uglify-js` as of v3.13.0", - "devOptional": true, - "peer": true, - "dependencies": { - "commander": "~2.13.0", - "source-map": "~0.6.1" - }, - "bin": { - "uglifyjs": "bin/uglifyjs" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/uglify-es/node_modules/commander": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", - "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", - "devOptional": true, - "peer": true - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "devOptional": true, - "peer": true, - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/union-value/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "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==", - "devOptional": 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==", - "devOptional": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", - "devOptional": true, - "peer": true, - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", - "devOptional": true, - "peer": true, - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", - "devOptional": true, - "peer": true, - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz", - "integrity": "sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==", - "devOptional": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist-lint": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", - "deprecated": "Please see https://github.com/lydell/urix#deprecated", - "devOptional": true, - "peer": true - }, - "node_modules/url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "dependencies": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "devOptional": true, - "peer": true, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "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==", - "devOptional": true, - "peer": true - }, - "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==", - "devOptional": true, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "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.0.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", - "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", - "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_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==", - "devOptional": true, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "node_modules/vlq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", - "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", - "devOptional": true, - "peer": 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/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", - "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", - "dev": true, - "dependencies": { - "xml-name-validator": "^4.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "devOptional": true, - "dependencies": { - "makeerror": "1.0.12" - } - }, - "node_modules/watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, - "dependencies": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - }, - "engines": { - "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==", - "devOptional": true, - "peer": true, - "dependencies": { - "defaults": "^1.0.3" - } - }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/webpack": { - "version": "5.74.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", - "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", - "dev": true, - "dependencies": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, - "node_modules/webpack-merge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", - "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", - "dev": true, - "dependencies": { - "lodash": "^4.17.15" - } - }, - "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack/node_modules/@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "dev": true - }, - "node_modules/whatwg-encoding": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", - "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", - "dev": true, - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-encoding/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==", - "devOptional": true, - "peer": true - }, - "node_modules/whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dev": true, - "dependencies": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "devOptional": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", - "devOptional": 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", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/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/wrap-ansi/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/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "devOptional": 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==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - }, - "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/ws": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", - "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml-name-validator": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/xmlbuilder": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", - "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": 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==", - "devOptional": true, - "peer": true, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "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": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "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", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "devOptional": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - } - }, - "dependencies": { - "@ampproject/remapping": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", - "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", - "devOptional": true, - "requires": { - "@jridgewell/gen-mapping": "^0.1.0", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "devOptional": true, - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@babel/compat-data": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.19.1.tgz", - "integrity": "sha512-72a9ghR0gnESIa7jBN53U32FOVCEoztyIlKaNoU05zRhEecduGK9L9c3ww7Mp06JiR+0ls0GBPFJQwwtjn9ksg==", - "devOptional": true - }, - "@babel/core": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.18.13.tgz", - "integrity": "sha512-ZisbOvRRusFktksHSG6pjj1CSvkPkcZq/KHD45LAkVP/oiHJkNBZWfpvlLmX8OtHDG8IuzsFlVRWo08w7Qxn0A==", - "devOptional": true, - "requires": { - "@ampproject/remapping": "^2.1.0", - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.18.13", - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-module-transforms": "^7.18.9", - "@babel/helpers": "^7.18.9", - "@babel/parser": "^7.18.13", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.18.13", - "@babel/types": "^7.18.13", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", - "semver": "^6.3.0" - }, - "dependencies": { - "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", - "devOptional": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true - } - } - }, - "@babel/generator": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.19.0.tgz", - "integrity": "sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==", - "devOptional": true, - "requires": { - "@babel/types": "^7.19.0", - "@jridgewell/gen-mapping": "^0.3.2", - "jsesc": "^2.5.1" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "devOptional": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "devOptional": true - } - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", - "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.18.9.tgz", - "integrity": "sha512-yFQ0YCHoIqarl8BCRwBL8ulYUaZpz3bNsA7oFepAzee+8/+ImtADXNOmO5vJvsPff3qi+hvpkY/NYBTrBQgdNw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-explode-assignable-expression": "^7.18.6", - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.1.tgz", - "integrity": "sha512-LlLkkqhCMyz2lkQPvJNdIYU7O5YjWRgC2R4omjCTpZd8u8KMQzZvX4qce+/BluN1rcQiV7BoGUpmQ0LeHerbhg==", - "devOptional": true, - "requires": { - "@babel/compat-data": "^7.19.1", - "@babel/helper-validator-option": "^7.18.6", - "browserslist": "^4.21.3", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true - } - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz", - "integrity": "sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6" - } - }, - "@babel/helper-create-regexp-features-plugin": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz", - "integrity": "sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "regexpu-core": "^5.1.0" - } - }, - "@babel/helper-define-polyfill-provider": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz", - "integrity": "sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-compilation-targets": "^7.17.7", - "@babel/helper-plugin-utils": "^7.16.7", - "debug": "^4.1.1", - "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2", - "semver": "^6.1.2" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true - } - } - }, - "@babel/helper-environment-visitor": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", - "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", - "devOptional": true - }, - "@babel/helper-explode-assignable-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz", - "integrity": "sha512-eyAYAsQmB80jNfg4baAtLeWAQHfHFiR483rzFK+BhETlGZaQC9bsfrugfXDCbRHLQbIA7U5NxhhOxN7p/dWIcg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-function-name": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz", - "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==", - "devOptional": true, - "requires": { - "@babel/template": "^7.18.10", - "@babel/types": "^7.19.0" - } - }, - "@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==", - "devOptional": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz", - "integrity": "sha512-RxifAh2ZoVU67PyKIO4AMi1wTenGfMR/O/ae0CCRqwgBAt5v7xjdtRw7UoSbsreKrQn5t7r89eruK/9JjYHuDg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-module-imports": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", - "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", - "devOptional": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-module-transforms": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz", - "integrity": "sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==", - "devOptional": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/helper-validator-identifier": "^7.18.6", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", - "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz", - "integrity": "sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==", - "devOptional": true - }, - "@babel/helper-remap-async-to-generator": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz", - "integrity": "sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-wrap-function": "^7.18.9", - "@babel/types": "^7.18.9" - } - }, - "@babel/helper-replace-supers": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz", - "integrity": "sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-member-expression-to-functions": "^7.18.9", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/traverse": "^7.19.1", - "@babel/types": "^7.19.0" - } - }, - "@babel/helper-simple-access": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz", - "integrity": "sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==", - "devOptional": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz", - "integrity": "sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/types": "^7.18.9" - } - }, - "@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==", - "devOptional": true, - "requires": { - "@babel/types": "^7.18.6" - } - }, - "@babel/helper-string-parser": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz", - "integrity": "sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==", - "devOptional": true - }, - "@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", - "devOptional": true - }, - "@babel/helper-validator-option": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz", - "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==", - "devOptional": true - }, - "@babel/helper-wrap-function": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz", - "integrity": "sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-function-name": "^7.19.0", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.19.0", - "@babel/types": "^7.19.0" - } - }, - "@babel/helpers": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.18.9.tgz", - "integrity": "sha512-Jf5a+rbrLoR4eNdUmnFu8cN5eNJT6qdTdOg5IHIzq87WwyRw9PwguLFOWYgktN/60IP4fgDUawJvs7PjQIzELQ==", - "devOptional": true, - "requires": { - "@babel/template": "^7.18.6", - "@babel/traverse": "^7.18.9", - "@babel/types": "^7.18.9" - } - }, - "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "devOptional": true, - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.19.1.tgz", - "integrity": "sha512-h7RCSorm1DdTVGJf3P2Mhj3kdnkmF/EiysUkzS2TdgAYqyjFdMQJbVuXOBej2SBJaXan/lIVtT6KkGbyyq753A==", - "devOptional": true - }, - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz", - "integrity": "sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.18.9.tgz", - "integrity": "sha512-AHrP9jadvH7qlOj6PINbgSuphjQUAK7AOT7DPjBo9EHoLhQTnnK5u45e1Hd4DbSQEO9nqPWtQ89r+XEOWFScKg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-proposal-optional-chaining": "^7.18.9" - } - }, - "@babel/plugin-proposal-async-generator-functions": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.1.tgz", - "integrity": "sha512-0yu8vNATgLy4ivqMNBIwb1HebCelqN7YX8SL3FDXORv/RqT0zEEWUCH4GH44JsSrvCu6GqnAdR5EBFAPeNBB4Q==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-remap-async-to-generator": "^7.18.9", - "@babel/plugin-syntax-async-generators": "^7.8.4" - } - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.18.6.tgz", - "integrity": "sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-proposal-class-static-block": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.18.6.tgz", - "integrity": "sha512-+I3oIiNxrCpup3Gi8n5IGMwj0gOCAjcJUSQEcotNnCCPMEnixawOQ+KeJPlgfjzx+FKQ1QSyZOWe7wmoJp7vhw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-class-static-block": "^7.14.5" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.18.6.tgz", - "integrity": "sha512-1auuwmK+Rz13SJj36R+jqFPMJWyKEDd7lLSdOj4oJK0UTgGueSAtkrCvz9ewmgyU/P941Rv2fQwZJN8s6QruXw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - } - }, - "@babel/plugin-proposal-export-default-from": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.18.10.tgz", - "integrity": "sha512-5H2N3R2aQFxkV4PIBUR/i7PUSwgTZjouJKzI8eKswfIjT0PhvzkPn0t0wIS5zn6maQuvtT0t1oHtMUz61LOuow==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-default-from": "^7.18.6" - } - }, - "@babel/plugin-proposal-export-namespace-from": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.18.9.tgz", - "integrity": "sha512-k1NtHyOMvlDDFeb9G5PhUXuGj8m/wiwojgQVEhJ/fsVsMCpLyOP4h0uGEjYJKrRI+EVPlb5Jk+Gt9P97lOGwtA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "@babel/plugin-proposal-json-strings": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.18.6.tgz", - "integrity": "sha512-lr1peyn9kOdbYc0xr0OdHTZ5FMqS6Di+H0Fz2I/JwMzGmzJETNeOFq2pBySw6X/KFL5EWDjlJuMsUGRFb8fQgQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3" - } - }, - "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.18.9.tgz", - "integrity": "sha512-128YbMpjCrP35IOExw2Fq+x55LMP42DzhOhX2aNNIdI9avSWl2PI0yuBWarr3RYpZBSPtabfadkH2yeRiMD61Q==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.18.6.tgz", - "integrity": "sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - } - }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.18.6.tgz", - "integrity": "sha512-ozlZFogPqoLm8WBr5Z8UckIoE4YQ5KESVcNudyXOR8uqIkliTEgJ3RoketfG6pmzLdeZF0H/wjE9/cCEitBl7Q==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "@babel/plugin-proposal-object-rest-spread": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz", - "integrity": "sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/compat-data": "^7.18.8", - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.18.8" - } - }, - "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.18.6.tgz", - "integrity": "sha512-Q40HEhs9DJQyaZfUjjn6vE8Cv4GmMHCYuMGIWUnlxH6400VGxOuwWsPt4FxXxJkC/5eOzgn0z21M9gMT4MOhbw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.18.9.tgz", - "integrity": "sha512-v5nwt4IqBXihxGsW2QmCWMDS3B3bzGIk/EQVZz2ei7f3NJl8NzAJVvUmpDW5q1CRNY+Beb/k58UAH1Km1N411w==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - } - }, - "@babel/plugin-proposal-private-methods": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.18.6.tgz", - "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-proposal-private-property-in-object": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz", - "integrity": "sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-create-class-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - } - }, - "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz", - "integrity": "sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@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==", - "devOptional": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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==", - "devOptional": true, - "requires": { - "@babel/helper-plugin-utils": "^7.12.13" - } - }, - "@babel/plugin-syntax-class-static-block": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz", - "integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-export-default-from": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.18.6.tgz", - "integrity": "sha512-Kr//z3ujSVNx6E9z9ih5xXXMqK07VVTuqPmqGe6Mss/zW5XPeLZeSDZoP9ab/hT4wPKqAgjl2PnhPrcpk8Seew==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-flow": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.18.6.tgz", - "integrity": "sha512-LUbR+KNTBWCUAqRG9ex5Gnzu2IOkt8jRJbHHXFT9q+L9zm7M/QQbEqXyw1n1pohYvOyWC8CjeyjrSaIwiYjK7A==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-syntax-import-assertions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz", - "integrity": "sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@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==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@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==", - "devOptional": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-jsx": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz", - "integrity": "sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@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==", - "devOptional": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@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==", - "devOptional": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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==", - "devOptional": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@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==", - "devOptional": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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==", - "devOptional": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@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==", - "devOptional": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@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==", - "devOptional": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.18.6.tgz", - "integrity": "sha512-mAWAuq4rvOepWCBid55JuRNvpTNf2UGVgoz4JV0fXEKolsVZDzsa4NqCef758WZJj/GDu0gVGItjKFiClTAmZA==", - "devOptional": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-arrow-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.18.6.tgz", - "integrity": "sha512-9S9X9RUefzrsHZmKMbDXxweEH+YlE8JJEuat9FdvW9Qh1cw7W64jELCtWNkPBPX5En45uy28KGvA/AySqUh8CQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-async-to-generator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.18.6.tgz", - "integrity": "sha512-ARE5wZLKnTgPW7/1ftQmSi1CmkqqHo2DNmtztFhvgtOWSDfq0Cq9/9L+KnZNYSNrydBekhW3rwShduf59RoXag==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-remap-async-to-generator": "^7.18.6" - } - }, - "@babel/plugin-transform-block-scoped-functions": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz", - "integrity": "sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-block-scoping": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz", - "integrity": "sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-classes": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz", - "integrity": "sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-compilation-targets": "^7.19.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-optimise-call-expression": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-replace-supers": "^7.18.9", - "@babel/helper-split-export-declaration": "^7.18.6", - "globals": "^11.1.0" - }, - "dependencies": { - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "devOptional": true, - "peer": true - } - } - }, - "@babel/plugin-transform-computed-properties": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.18.9.tgz", - "integrity": "sha512-+i0ZU1bCDymKakLxn5srGHrsAPRELC2WIbzwjLhHW9SIE1cPYkLCL0NlnXMZaM1vhfgA2+M7hySk42VBvrkBRw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-destructuring": { - "version": "7.18.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.13.tgz", - "integrity": "sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-dotall-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz", - "integrity": "sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-duplicate-keys": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz", - "integrity": "sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-exponentiation-operator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz", - "integrity": "sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-flow-strip-types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.19.0.tgz", - "integrity": "sha512-sgeMlNaQVbCSpgLSKP4ZZKfsJVnFnNQlUSk6gPYzR/q7tzCgQF2t8RBKAP6cKJeZdveei7Q7Jm527xepI8lNLg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-flow": "^7.18.6" - } - }, - "@babel/plugin-transform-for-of": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.18.8.tgz", - "integrity": "sha512-yEfTRnjuskWYo0k1mHUqrVWaZwrdq8AYbfrpqULOJOaucGSp4mNMVps+YtA8byoevxS/urwU75vyhQIxcCgiBQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-function-name": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz", - "integrity": "sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-compilation-targets": "^7.18.9", - "@babel/helper-function-name": "^7.18.9", - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz", - "integrity": "sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-member-expression-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz", - "integrity": "sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-modules-amd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz", - "integrity": "sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz", - "integrity": "sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-simple-access": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-systemjs": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.0.tgz", - "integrity": "sha512-x9aiR0WXAWmOWsqcsnrzGR+ieaTMVyGyffPVA7F8cXAGt/UxefYv6uSHZLkAFChN5M5Iy1+wjE+xJuPt22H39A==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-module-transforms": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-validator-identifier": "^7.18.6", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-modules-umd": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz", - "integrity": "sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-module-transforms": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.1.tgz", - "integrity": "sha512-oWk9l9WItWBQYS4FgXD4Uyy5kq898lvkXpXQxoJEY1RnvPk4R/Dvu2ebXU9q8lP+rlMwUQTFf2Ok6d78ODa0kw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0" - } - }, - "@babel/plugin-transform-new-target": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.18.6.tgz", - "integrity": "sha512-DjwFA/9Iu3Z+vrAn+8pBUGcjhxKguSMlsFqeCKbhb9BAV756v0krzVK04CRDi/4aqmk8BsHb4a/gFcaA5joXRw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-object-super": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz", - "integrity": "sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-replace-supers": "^7.18.6" - } - }, - "@babel/plugin-transform-parameters": { - "version": "7.18.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz", - "integrity": "sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-property-literals": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz", - "integrity": "sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-react-display-name": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz", - "integrity": "sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-react-jsx": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz", - "integrity": "sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.18.6", - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-jsx": "^7.18.6", - "@babel/types": "^7.19.0" - } - }, - "@babel/plugin-transform-react-jsx-self": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.18.6.tgz", - "integrity": "sha512-A0LQGx4+4Jv7u/tWzoJF7alZwnBDQd6cGLh9P+Ttk4dpiL+J5p7NSNv/9tlEFFJDq3kjxOavWmbm6t0Gk+A3Ig==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-react-jsx-source": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.18.6.tgz", - "integrity": "sha512-utZmlASneDfdaMh0m/WausbjUjEdGrQJz0vFK93d7wD3xf5wBtX219+q6IlCNZeguIcxS2f/CvLZrlLSvSHQXw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-regenerator": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz", - "integrity": "sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "regenerator-transform": "^0.15.0" - } - }, - "@babel/plugin-transform-reserved-words": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz", - "integrity": "sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-runtime": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.19.1.tgz", - "integrity": "sha512-2nJjTUFIzBMP/f/miLxEK9vxwW/KUXsdvN4sR//TmuDhe6yU2h57WmIOE12Gng3MDP/xpjUV/ToZRdcf8Yj4fA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-module-imports": "^7.18.6", - "@babel/helper-plugin-utils": "^7.19.0", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true - } - } - }, - "@babel/plugin-transform-shorthand-properties": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz", - "integrity": "sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-spread": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.19.0.tgz", - "integrity": "sha512-RsuMk7j6n+r752EtzyScnWkQyuJdli6LdO5Klv8Yx0OfPVTcQkIUfS8clx5e9yHXzlnhOZF3CbQ8C2uP5j074w==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.18.9" - } - }, - "@babel/plugin-transform-sticky-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz", - "integrity": "sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/plugin-transform-template-literals": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz", - "integrity": "sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-typeof-symbol": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz", - "integrity": "sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-typescript": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.19.1.tgz", - "integrity": "sha512-+ILcOU+6mWLlvCwnL920m2Ow3wWx3Wo8n2t5aROQmV55GZt+hOiLvBaa3DNzRjSEHa1aauRs4/YLmkCfFkhhRQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.19.0", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/plugin-syntax-typescript": "^7.18.6" - } - }, - "@babel/plugin-transform-unicode-escapes": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", - "integrity": "sha512-kKAdAI+YzPgGY/ftStBFXTI1LZFju38rYThnfMykS+IXy8BVx+res7s2fxf1l8I35DV2T97ezo6+SGrXz6B3iQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.9" - } - }, - "@babel/plugin-transform-unicode-regex": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz", - "integrity": "sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - } - }, - "@babel/preset-env": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.19.1.tgz", - "integrity": "sha512-c8B2c6D16Lp+Nt6HcD+nHl0VbPKVnNPTpszahuxJJnurfMtKeZ80A+qUv48Y7wqvS+dTFuLuaM9oYxyNHbCLWA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/compat-data": "^7.19.1", - "@babel/helper-compilation-targets": "^7.19.1", - "@babel/helper-plugin-utils": "^7.19.0", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.18.6", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-async-generator-functions": "^7.19.1", - "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/plugin-proposal-class-static-block": "^7.18.6", - "@babel/plugin-proposal-dynamic-import": "^7.18.6", - "@babel/plugin-proposal-export-namespace-from": "^7.18.9", - "@babel/plugin-proposal-json-strings": "^7.18.6", - "@babel/plugin-proposal-logical-assignment-operators": "^7.18.9", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", - "@babel/plugin-proposal-numeric-separator": "^7.18.6", - "@babel/plugin-proposal-object-rest-spread": "^7.18.9", - "@babel/plugin-proposal-optional-catch-binding": "^7.18.6", - "@babel/plugin-proposal-optional-chaining": "^7.18.9", - "@babel/plugin-proposal-private-methods": "^7.18.6", - "@babel/plugin-proposal-private-property-in-object": "^7.18.6", - "@babel/plugin-proposal-unicode-property-regex": "^7.18.6", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-class-properties": "^7.12.13", - "@babel/plugin-syntax-class-static-block": "^7.14.5", - "@babel/plugin-syntax-dynamic-import": "^7.8.3", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.18.6", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-top-level-await": "^7.14.5", - "@babel/plugin-transform-arrow-functions": "^7.18.6", - "@babel/plugin-transform-async-to-generator": "^7.18.6", - "@babel/plugin-transform-block-scoped-functions": "^7.18.6", - "@babel/plugin-transform-block-scoping": "^7.18.9", - "@babel/plugin-transform-classes": "^7.19.0", - "@babel/plugin-transform-computed-properties": "^7.18.9", - "@babel/plugin-transform-destructuring": "^7.18.13", - "@babel/plugin-transform-dotall-regex": "^7.18.6", - "@babel/plugin-transform-duplicate-keys": "^7.18.9", - "@babel/plugin-transform-exponentiation-operator": "^7.18.6", - "@babel/plugin-transform-for-of": "^7.18.8", - "@babel/plugin-transform-function-name": "^7.18.9", - "@babel/plugin-transform-literals": "^7.18.9", - "@babel/plugin-transform-member-expression-literals": "^7.18.6", - "@babel/plugin-transform-modules-amd": "^7.18.6", - "@babel/plugin-transform-modules-commonjs": "^7.18.6", - "@babel/plugin-transform-modules-systemjs": "^7.19.0", - "@babel/plugin-transform-modules-umd": "^7.18.6", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.19.1", - "@babel/plugin-transform-new-target": "^7.18.6", - "@babel/plugin-transform-object-super": "^7.18.6", - "@babel/plugin-transform-parameters": "^7.18.8", - "@babel/plugin-transform-property-literals": "^7.18.6", - "@babel/plugin-transform-regenerator": "^7.18.6", - "@babel/plugin-transform-reserved-words": "^7.18.6", - "@babel/plugin-transform-shorthand-properties": "^7.18.6", - "@babel/plugin-transform-spread": "^7.19.0", - "@babel/plugin-transform-sticky-regex": "^7.18.6", - "@babel/plugin-transform-template-literals": "^7.18.9", - "@babel/plugin-transform-typeof-symbol": "^7.18.9", - "@babel/plugin-transform-unicode-escapes": "^7.18.10", - "@babel/plugin-transform-unicode-regex": "^7.18.6", - "@babel/preset-modules": "^0.1.5", - "@babel/types": "^7.19.0", - "babel-plugin-polyfill-corejs2": "^0.3.3", - "babel-plugin-polyfill-corejs3": "^0.6.0", - "babel-plugin-polyfill-regenerator": "^0.4.1", - "core-js-compat": "^3.25.1", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true - } - } - }, - "@babel/preset-flow": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-flow/-/preset-flow-7.18.6.tgz", - "integrity": "sha512-E7BDhL64W6OUqpuyHnSroLnqyRTcG6ZdOBl1OKI/QK/HJfplqK/S3sq1Cckx7oTodJ5yOXyfw7rEADJ6UjoQDQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-flow-strip-types": "^7.18.6" - } - }, - "@babel/preset-modules": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.5.tgz", - "integrity": "sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "@babel/preset-typescript": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz", - "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-plugin-utils": "^7.18.6", - "@babel/helper-validator-option": "^7.18.6", - "@babel/plugin-transform-typescript": "^7.18.6" - } - }, - "@babel/register": { - "version": "7.18.9", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.18.9.tgz", - "integrity": "sha512-ZlbnXDcNYHMR25ITwwNKT88JiaukkdVj/nG7r3wnuXkOTHc60Uy05PwMCPre0hSkY68E6zK3xz+vUJSP2jWmcw==", - "devOptional": true, - "peer": true, - "requires": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.5", - "source-map-support": "^0.5.16" - }, - "dependencies": { - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "devOptional": true, - "peer": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "devOptional": true, - "peer": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "devOptional": true, - "peer": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "devOptional": true, - "peer": true, - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "devOptional": true, - "peer": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "devOptional": true, - "peer": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "devOptional": true, - "peer": true, - "requires": { - "find-up": "^3.0.0" - } - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "devOptional": true, - "peer": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - } - } - }, - "@babel/runtime": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.19.0.tgz", - "integrity": "sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA==", - "peer": true, - "requires": { - "regenerator-runtime": "^0.13.4" - } - }, - "@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", - "devOptional": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" - } - }, - "@babel/traverse": { - "version": "7.19.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.19.1.tgz", - "integrity": "sha512-0j/ZfZMxKukDaag2PtOPDbwuELqIar6lLskVPPJDjXMXjfLb1Obo/1yjxIGqqAJrmfaTIY3z2wFLAQ7qSkLsuA==", - "devOptional": true, - "requires": { - "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.19.0", - "@babel/helper-environment-visitor": "^7.18.9", - "@babel/helper-function-name": "^7.19.0", - "@babel/helper-hoist-variables": "^7.18.6", - "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.19.1", - "@babel/types": "^7.19.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "dependencies": { - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "devOptional": true - } - } - }, - "@babel/types": { - "version": "7.19.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.19.0.tgz", - "integrity": "sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==", - "devOptional": true, - "requires": { - "@babel/helper-string-parser": "^7.18.10", - "@babel/helper-validator-identifier": "^7.18.6", - "to-fast-properties": "^2.0.0" - }, - "dependencies": { - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "devOptional": true - } - } - }, - "@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 - }, - "@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 - }, - "@eslint/eslintrc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.0.tgz", - "integrity": "sha512-UWW0TMTmk2d7hLcWD1/e2g5HDM/HQ3csaLSqXCfqwh4uNDuNqlaKWXmEsL4Cs41Z0KnILNvwbHAah3C2yt06kw==", - "dev": true, - "requires": { - "ajv": "^6.12.4", - "debug": "^4.3.2", - "espree": "^9.3.2", - "globals": "^13.15.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - } - } - }, - "@hapi/hoek": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz", - "integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==", - "devOptional": true, - "peer": true - }, - "@hapi/topo": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz", - "integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==", - "devOptional": true, - "peer": true, - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@humanwhocodes/config-array": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.4.tgz", - "integrity": "sha512-mXAIHxZT3Vcpg83opl1wGlVZ9xydbfZO3r5YfRSH6Gpp2J/PfdBP0wbDa2sO6/qRbcalpoevVyW6A/fI6LfeMw==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", - "minimatch": "^3.0.4" - } - }, - "@humanwhocodes/gitignore-to-minimatch": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@humanwhocodes/gitignore-to-minimatch/-/gitignore-to-minimatch-1.0.2.tgz", - "integrity": "sha512-rSqmMJDdLFUsyxR6FMtD00nfQKKLFb1kv+qBbOVKqErvloEIJLo5bDTJTQNTYgeyp78JsA7u/NPi5jT1GR/MuA==", - "dev": true - }, - "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", - "dev": true - }, - "@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "requires": { - "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" - }, - "dependencies": { - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "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, - "requires": { - "p-try": "^2.0.0" - } - }, - "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, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "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 - } - } - }, - "@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 - }, - "@jest/console": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-28.1.3.tgz", - "integrity": "sha512-QPAkP5EwKdK/bxIr6C1I4Vs0rm2nHiANzj/Z5X2JQkrZo6IqvC4ldZ9K95tF0HdidhA8Bo6egxSzUFPYKcEXLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/core": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-28.1.3.tgz", - "integrity": "sha512-CIKBrlaKOzA7YG19BEqCw3SLIsEwjZkeJzf5bdooVnW4bH5cktqe3JX+G2YV1aK5vP8N9na1IGWFzYaTp6k6NA==", - "dev": true, - "requires": { - "@jest/console": "^28.1.3", - "@jest/reporters": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@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": "^28.1.3", - "jest-config": "^28.1.3", - "jest-haste-map": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-resolve-dependencies": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "jest-watcher": "^28.1.3", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/create-cache-key-function": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-27.5.1.tgz", - "integrity": "sha512-dmH1yW+makpTSURTy8VzdUwFnfQh1G8R+DxO2Ho2FFmBbKFEVm+3jWdvFhE2VqB/LATCTokkP0dotjyQyw5/AQ==", - "devOptional": true, - "peer": true, - "requires": { - "@jest/types": "^27.5.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "devOptional": true, - "peer": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "devOptional": true, - "peer": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "devOptional": true, - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/environment": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.0.1.tgz", - "integrity": "sha512-iLcFfoq2K6DAB+Mc+2VNLzZVmHdwQFeSqvoM/X8SMON6s/+yEi1iuRX3snx/JfwSnvmiMXjSr0lktxNxOcqXYA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^29.0.1", - "@jest/types": "^29.0.1", - "@types/node": "*", - "jest-mock": "^29.0.1" - }, - "dependencies": { - "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/types": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", - "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-lzc8CpUbSoE4dqT0U+g1qODQjBRHPpCPXissXD4mS9+sWQdmmpeJ9zSH1rS1HEkrsMN0fb7nKrJ9giAR1d3wBw==", - "dev": true, - "requires": { - "expect": "^28.1.3", - "jest-snapshot": "^28.1.3" - } - }, - "@jest/expect-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz", - "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==", - "dev": true, - "requires": { - "jest-get-type": "^28.0.2" - } - }, - "@jest/fake-timers": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.0.1.tgz", - "integrity": "sha512-XZ+kAhLChVQ+KJNa5034p7O1Mz3vtWrelxDcMoxhZkgqmWDaEQAW9qJeutaeCfPvwaEwKYVyKDYfWpcyT8RiMw==", - "dev": true, - "requires": { - "@jest/types": "^29.0.1", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^29.0.1", - "jest-mock": "^29.0.1", - "jest-util": "^29.0.1" - }, - "dependencies": { - "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/types": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", - "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "jest-message-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.0.1.tgz", - "integrity": "sha512-wRMAQt3HrLpxSubdnzOo68QoTfQ+NLXFzU0Heb18ZUzO2S9GgaXNEdQ4rpd0fI9dq2NXkpCk1IUWSqzYKji64A==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.0.1", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.0.1", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - } - }, - "jest-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.0.1.tgz", - "integrity": "sha512-GIWkgNfkeA9d84rORDHPGGTFBrRD13A38QVSKE0bVrGSnoR1KDn8Kqz+0yI5kezMgbT/7zrWaruWP1Kbghlb2A==", - "dev": true, - "requires": { - "@jest/types": "^29.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "pretty-format": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.0.1.tgz", - "integrity": "sha512-iTHy3QZMzuL484mSTYbQIM1AHhEQsH8mXWS2/vd2yFBYnG3EBqGiMONo28PlPgrW7P/8s/1ISv+y7WH306l8cw==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/globals": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-28.1.3.tgz", - "integrity": "sha512-XFU4P4phyryCXu1pbcqMO0GSQcYe1IsalYCDzRNyhetyeyxMcIxa11qPNDpVNLeretItNqEmYYQn1UYz/5x1NA==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/types": "^28.1.3" - }, - "dependencies": { - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - } - }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" - } - } - } - }, - "@jest/reporters": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-28.1.3.tgz", - "integrity": "sha512-JuAy7wkxQZVNU/V6g9xKzCGC5LVXx9FDcABKsSXp5MiKPEE2144a/vXTEDoyzjUpZKfVwp08Wqg5A4WfTMAzjg==", - "dev": true, - "requires": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", - "@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": "^28.1.3", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", - "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^9.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/schemas": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz", - "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/source-map": { - "version": "28.1.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-28.1.2.tgz", - "integrity": "sha512-cV8Lx3BeStJb8ipPHnqVw/IM2VCMWO3crWZzYodSIkxXnRcXJipCdx1JCK0K5MsJJouZQTH73mzf4vgxRaH9ww==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.13", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" - } - }, - "@jest/test-result": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-28.1.3.tgz", - "integrity": "sha512-kZAkxnSE+FqE8YjW8gNuoVkkC9I7S1qmenl8sGcDOLropASP+BkcGKwhXoyqQuGOGeYY0y/ixjrd/iERpEXHNg==", - "dev": true, - "requires": { - "@jest/console": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - } - }, - "@jest/test-sequencer": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-28.1.3.tgz", - "integrity": "sha512-NIMPEqqa59MWnDi1kvXXpYbqsfQmSJsIbnd85mdVGkiDfQ9WQQTXOLsvISUfonmnBT+w85WEgneCigEEdHDFxw==", - "dev": true, - "requires": { - "@jest/test-result": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "slash": "^3.0.0" - } - }, - "@jest/transform": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-28.1.3.tgz", - "integrity": "sha512-u5dT5di+oFI6hfcLOHGTAfmUxFRrjK+vnaP0kkVow9Md/M7V/MxqQMOz/VV25UZO8pzeA9PjfTpOu6BDuwSPQA==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/types": "^28.1.3", - "@jridgewell/trace-mapping": "^0.3.13", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.3", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jest/types": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz", - "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.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" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@jridgewell/gen-mapping": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", - "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", - "devOptional": true, - "requires": { - "@jridgewell/set-array": "^1.0.0", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@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==", - "devOptional": true - }, - "@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", - "devOptional": true - }, - "@jridgewell/source-map": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", - "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", - "dev": true, - "requires": { - "@jridgewell/gen-mapping": "^0.3.0", - "@jridgewell/trace-mapping": "^0.3.9" - }, - "dependencies": { - "@jridgewell/gen-mapping": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", - "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", - "dev": true, - "requires": { - "@jridgewell/set-array": "^1.0.1", - "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" - } - } - } - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "devOptional": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", - "devOptional": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, - "@optimizely/js-sdk-datafile-manager": { - "version": "0.9.5", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-datafile-manager/-/js-sdk-datafile-manager-0.9.5.tgz", - "integrity": "sha512-O4ujr1nBBAQBtx8YoKNpzzaEZgsE+aU4dxubT17ePqv/YVUWE+JOY21tSRrqZy/BlbbyzL+ElT8hrGB5ZzVoIQ==", - "requires": { - "@optimizely/js-sdk-logging": "^0.3.1", - "@optimizely/js-sdk-utils": "^0.4.0", - "decompress-response": "^4.2.1" - } - }, - "@optimizely/js-sdk-logging": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-logging/-/js-sdk-logging-0.3.1.tgz", - "integrity": "sha512-K71Jf283FP0E4oXehcXTTM3gvgHZHr7FUrIsw//0mdJlotHJT4Nss4hE0CWPbBxO7LJAtwNnO+VIA/YOcO4vHg==", - "requires": { - "@optimizely/js-sdk-utils": "^0.4.0" - } - }, - "@optimizely/js-sdk-utils": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@optimizely/js-sdk-utils/-/js-sdk-utils-0.4.0.tgz", - "integrity": "sha512-QG2oytnITW+VKTJK+l0RxjaS5VrA6W+AZMzpeg4LCB4Rn4BEKtF+EcW/5S1fBDLAviGq/0TLpkjM3DlFkJ9/Gw==", - "requires": { - "uuid": "^3.3.2" - }, - "dependencies": { - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" - } - } - }, - "@react-native-async-storage/async-storage": { - "version": "1.17.9", - "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.17.9.tgz", - "integrity": "sha512-HKhMvjpA5/YzNMkcY3qeWLdTtUrtJe243knHNNYe1c0IplX69hZyiw7DjFwAgxPG9+YvzHDHliqPV+mBNOv+cQ==", - "devOptional": true, - "requires": { - "merge-options": "^3.0.4" - } - }, - "@react-native-community/cli": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-8.0.6.tgz", - "integrity": "sha512-E36hU/if3quQCfJHGWVkpsCnwtByRCwORuAX0r6yr1ebKktpKeEO49zY9PAu/Z1gfyxCtgluXY0HfRxjKRFXTg==", - "devOptional": true, - "peer": true, - "requires": { - "@react-native-community/cli-clean": "^8.0.4", - "@react-native-community/cli-config": "^8.0.6", - "@react-native-community/cli-debugger-ui": "^8.0.0", - "@react-native-community/cli-doctor": "^8.0.6", - "@react-native-community/cli-hermes": "^8.0.5", - "@react-native-community/cli-plugin-metro": "^8.0.4", - "@react-native-community/cli-server-api": "^8.0.4", - "@react-native-community/cli-tools": "^8.0.4", - "@react-native-community/cli-types": "^8.0.0", - "chalk": "^4.1.2", - "commander": "^2.19.0", - "execa": "^1.0.0", - "find-up": "^4.1.0", - "fs-extra": "^8.1.0", - "graceful-fs": "^4.1.3", - "leven": "^3.1.0", - "lodash": "^4.17.15", - "minimist": "^1.2.0", - "prompts": "^2.4.0", - "semver": "^6.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "devOptional": true, - "peer": true - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "devOptional": true, - "peer": true - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "devOptional": true, - "peer": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true - } - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "devOptional": true, - "peer": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "devOptional": true, - "peer": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "devOptional": true, - "peer": true, - "requires": { - "pump": "^3.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "devOptional": true, - "peer": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "devOptional": true, - "peer": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "devOptional": true, - "peer": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "devOptional": true, - "peer": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true, - "peer": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "devOptional": true, - "peer": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "devOptional": true, - "peer": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-clean": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-clean/-/cli-clean-8.0.4.tgz", - "integrity": "sha512-IwS1M1NHg6+qL8PThZYMSIMYbZ6Zbx+lIck9PLBskbosFo24M3lCOflOl++Bggjakp6mR+sRXxLMexid/GeOsQ==", - "devOptional": true, - "peer": true, - "requires": { - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "execa": "^1.0.0", - "prompts": "^2.4.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "devOptional": true, - "peer": true - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "devOptional": true, - "peer": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "devOptional": true, - "peer": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "devOptional": true, - "peer": true, - "requires": { - "pump": "^3.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "devOptional": true, - "peer": true - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "devOptional": true, - "peer": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "devOptional": true, - "peer": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "devOptional": true, - "peer": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-config": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-config/-/cli-config-8.0.6.tgz", - "integrity": "sha512-mjVpVvdh8AviiO8xtqeX+BkjqE//NMDnISwsLWSJUfNCwTAPmdR8PGbhgP5O4hWHyJ3WkepTopl0ya7Tfi3ifw==", - "devOptional": true, - "peer": true, - "requires": { - "@react-native-community/cli-tools": "^8.0.4", - "cosmiconfig": "^5.1.0", - "deepmerge": "^3.2.0", - "glob": "^7.1.3", - "joi": "^17.2.1" - }, - "dependencies": { - "deepmerge": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.3.0.tgz", - "integrity": "sha512-GRQOafGHwMHpjPx9iCvTgpu9NojZ49q794EEL94JVEw6VaeA8XTUyBKvAkOOjBX9oJNiV6G3P+T+tihFjo2TqA==", - "devOptional": true, - "peer": true - } - } - }, - "@react-native-community/cli-debugger-ui": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-debugger-ui/-/cli-debugger-ui-8.0.0.tgz", - "integrity": "sha512-u2jq06GZwZ9sRERzd9FIgpW6yv4YOW4zz7Ym/B8eSzviLmy3yI/8mxJtvlGW+J8lBsfMcQoqJpqI6Rl1nZy9yQ==", - "devOptional": true, - "peer": true, - "requires": { - "serve-static": "^1.13.1" - } - }, - "@react-native-community/cli-doctor": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-doctor/-/cli-doctor-8.0.6.tgz", - "integrity": "sha512-ZQqyT9mJMVeFEVIwj8rbDYGCA2xXjJfsQjWk2iTRZ1CFHfhPSUuUiG8r6mJmTinAP9t+wYcbbIYzNgdSUKnDMw==", - "devOptional": true, - "peer": true, - "requires": { - "@react-native-community/cli-config": "^8.0.6", - "@react-native-community/cli-platform-ios": "^8.0.6", - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "command-exists": "^1.2.8", - "envinfo": "^7.7.2", - "execa": "^1.0.0", - "hermes-profile-transformer": "^0.0.6", - "ip": "^1.1.5", - "node-stream-zip": "^1.9.1", - "ora": "^5.4.1", - "prompts": "^2.4.0", - "semver": "^6.3.0", - "strip-ansi": "^5.2.0", - "sudo-prompt": "^9.0.0", - "wcwidth": "^1.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "devOptional": true, - "peer": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "devOptional": true, - "peer": true - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "devOptional": true, - "peer": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true - } - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "devOptional": true, - "peer": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "devOptional": true, - "peer": true, - "requires": { - "pump": "^3.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "devOptional": true, - "peer": true - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "devOptional": true, - "peer": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "devOptional": true, - "peer": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "devOptional": true, - "peer": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "devOptional": true, - "peer": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-regex": "^4.1.0" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-hermes": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-hermes/-/cli-hermes-8.0.5.tgz", - "integrity": "sha512-Zm0wM6SfgYAEX1kfJ1QBvTayabvh79GzmjHyuSnEROVNPbl4PeCG4WFbwy489tGwOP9Qx9fMT5tRIFCD8bp6/g==", - "devOptional": true, - "peer": true, - "requires": { - "@react-native-community/cli-platform-android": "^8.0.5", - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "hermes-profile-transformer": "^0.0.6", - "ip": "^1.1.5" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "devOptional": true, - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-platform-android": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-android/-/cli-platform-android-8.0.5.tgz", - "integrity": "sha512-z1YNE4T1lG5o9acoQR1GBvf7mq6Tzayqo/za5sHVSOJAC9SZOuVN/gg/nkBa9a8n5U7qOMFXfwhTMNqA474gXA==", - "devOptional": true, - "peer": true, - "requires": { - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "execa": "^1.0.0", - "fs-extra": "^8.1.0", - "glob": "^7.1.3", - "jetifier": "^1.6.2", - "lodash": "^4.17.15", - "logkitty": "^0.7.1", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "devOptional": true, - "peer": true - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "devOptional": true, - "peer": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "devOptional": true, - "peer": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "devOptional": true, - "peer": true, - "requires": { - "pump": "^3.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "devOptional": true, - "peer": true - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "devOptional": true, - "peer": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "devOptional": true, - "peer": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "devOptional": true, - "peer": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-platform-ios": { - "version": "8.0.6", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-platform-ios/-/cli-platform-ios-8.0.6.tgz", - "integrity": "sha512-CMR6mu/LVx6JVfQRDL9uULsMirJT633bODn+IrYmrwSz250pnhON16We8eLPzxOZHyDjm7JPuSgHG3a/BPiRuQ==", - "devOptional": true, - "peer": true, - "requires": { - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "execa": "^1.0.0", - "glob": "^7.1.3", - "js-yaml": "^3.13.1", - "lodash": "^4.17.15", - "ora": "^5.4.1", - "plist": "^3.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "devOptional": true, - "peer": true - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "devOptional": true, - "peer": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "devOptional": true, - "peer": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "devOptional": true, - "peer": true, - "requires": { - "pump": "^3.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "devOptional": true, - "peer": true - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "devOptional": true, - "peer": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "devOptional": true, - "peer": true - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "devOptional": true, - "peer": true - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "devOptional": true, - "peer": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-plugin-metro": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-plugin-metro/-/cli-plugin-metro-8.0.4.tgz", - "integrity": "sha512-UWzY1eMcEr/6262R2+d0Is5M3L/7Y/xXSDIFMoc5Rv5Wucl3hJM/TxHXmByvHpuJf6fJAfqOskyt4bZCvbI+wQ==", - "devOptional": true, - "peer": true, - "requires": { - "@react-native-community/cli-server-api": "^8.0.4", - "@react-native-community/cli-tools": "^8.0.4", - "chalk": "^4.1.2", - "metro": "^0.70.1", - "metro-config": "^0.70.1", - "metro-core": "^0.70.1", - "metro-react-native-babel-transformer": "^0.70.1", - "metro-resolver": "^0.70.1", - "metro-runtime": "^0.70.1", - "readline": "^1.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "devOptional": true, - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-server-api": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-server-api/-/cli-server-api-8.0.4.tgz", - "integrity": "sha512-Orr14njx1E70CVrUA8bFdl+mrnbuXUjf1Rhhm0RxUadFpvkHuOi5dh8Bryj2MKtf8eZrpEwZ7tuQPhJEULW16A==", - "devOptional": true, - "peer": true, - "requires": { - "@react-native-community/cli-debugger-ui": "^8.0.0", - "@react-native-community/cli-tools": "^8.0.4", - "compression": "^1.7.1", - "connect": "^3.6.5", - "errorhandler": "^1.5.0", - "nocache": "^3.0.1", - "pretty-format": "^26.6.2", - "serve-static": "^1.13.1", - "ws": "^7.5.1" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "devOptional": true, - "peer": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "15.0.14", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", - "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", - "devOptional": true, - "peer": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "devOptional": true, - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "devOptional": true, - "peer": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "devOptional": true, - "peer": true, - "requires": {} - } - } - }, - "@react-native-community/cli-tools": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-tools/-/cli-tools-8.0.4.tgz", - "integrity": "sha512-ePN9lGxh6LRFiotyddEkSmuqpQhnq2iw9oiXYr4EFWpIEy0yCigTuSTiDF68+c8M9B+7bTwkRpz/rMPC4ViO5Q==", - "devOptional": true, - "peer": true, - "requires": { - "appdirsjs": "^1.2.4", - "chalk": "^4.1.2", - "find-up": "^5.0.0", - "lodash": "^4.17.15", - "mime": "^2.4.1", - "node-fetch": "^2.6.0", - "open": "^6.2.0", - "ora": "^5.4.1", - "semver": "^6.3.0", - "shell-quote": "^1.7.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "devOptional": true, - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "@react-native-community/cli-types": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/@react-native-community/cli-types/-/cli-types-8.0.0.tgz", - "integrity": "sha512-1lZS1PEvMlFaN3Se1ksyoFWzMjk+YfKi490GgsqKJln9gvFm8tqVPdnXttI5Uf2DQf3BMse8Bk8dNH4oV6Ewow==", - "devOptional": true, - "peer": true, - "requires": { - "joi": "^17.2.1" - } - }, - "@react-native-community/netinfo": { - "version": "5.9.10", - "resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-5.9.10.tgz", - "integrity": "sha512-1NPlBA2Hu/KWc3EnQcDRPRX0x8Dg9tuQlQQVWVQjlg+u+PjCq7ANEtbikOFKp5yQqfF8tqzU5+84/IfDO8zpiA==", - "dev": true, - "requires": {} - }, - "@react-native/assets": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@react-native/assets/-/assets-1.0.0.tgz", - "integrity": "sha512-KrwSpS1tKI70wuKl68DwJZYEvXktDHdZMG0k2AXD/rJVSlB23/X2CB2cutVR0HwNMJIal9HOUOBB2rVfa6UGtQ==", - "devOptional": true, - "peer": true - }, - "@react-native/normalize-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@react-native/normalize-color/-/normalize-color-2.0.0.tgz", - "integrity": "sha512-Wip/xsc5lw8vsBlmY2MO/gFLp3MvuZ2baBZjDeTjjndMgM0h5sxz7AZR62RDPGgstp8Np7JzjvVqVT7tpFZqsw==", - "devOptional": true, - "peer": true - }, - "@react-native/polyfills": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@react-native/polyfills/-/polyfills-2.0.0.tgz", - "integrity": "sha512-K0aGNn1TjalKj+65D7ycc1//H9roAQ51GJVk5ZJQFb2teECGmzd86bYDC0aYdbRf7gtovescq4Zt6FR0tgXiHQ==", - "devOptional": true, - "peer": true - }, - "@rollup/plugin-commonjs": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-11.1.0.tgz", - "integrity": "sha512-Ycr12N3ZPN96Fw2STurD21jMqzKwL9QuFhms3SD7KKRK7oaXUsBU9Zt0jL/rOPHiPYisI21/rXGO3jr9BnLHUA==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.0.8", - "commondir": "^1.0.1", - "estree-walker": "^1.0.1", - "glob": "^7.1.2", - "is-reference": "^1.1.2", - "magic-string": "^0.25.2", - "resolve": "^1.11.0" - } - }, - "@rollup/plugin-node-resolve": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz", - "integrity": "sha512-RxtSL3XmdTAE2byxekYLnx+98kEUOrPHF/KRVjLH+DEIHy6kjIw7YINQzn+NXiH/NTrQLAwYs0GWB+csWygA9Q==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.0.8", - "@types/resolve": "0.0.8", - "builtin-modules": "^3.1.0", - "is-module": "^1.0.0", - "resolve": "^1.14.2" - } - }, - "@rollup/pluginutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", - "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", - "dev": true, - "requires": { - "@types/estree": "0.0.39", - "estree-walker": "^1.0.1", - "picomatch": "^2.2.2" - } - }, - "@sideway/address": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz", - "integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==", - "devOptional": true, - "peer": true, - "requires": { - "@hapi/hoek": "^9.0.0" - } - }, - "@sideway/formula": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz", - "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==", - "devOptional": true, - "peer": true - }, - "@sideway/pinpoint": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", - "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==", - "devOptional": true, - "peer": true - }, - "@sinclair/typebox": { - "version": "0.24.34", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.34.tgz", - "integrity": "sha512-x3ejWKw7rpy30Bvm6U0AQMOHdjqe2E3YJrBHlTxH0KFsp77bBa+MH324nJxtXZFpnTy/JW2h5HPYVm0vG2WPnw==", - "dev": true - }, - "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/fake-timers": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", - "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "@sinonjs/formatio": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", - "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" - } - }, - "@sinonjs/samsam": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", - "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.3.0", - "array-from": "^2.1.1", - "lodash": "^4.17.15" - } - }, - "@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", - "dev": true - }, - "@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true - }, - "@types/babel__core": { - "version": "7.1.19", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz", - "integrity": "sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw==", - "dev": true, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "@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, - "requires": { - "@babel/types": "^7.0.0" - } - }, - "@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, - "requires": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "@types/babel__traverse": { - "version": "7.18.1", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.18.1.tgz", - "integrity": "sha512-FSdLaZh2UxaMuLp9lixWaHq/golWTRWOnRsAXzDTDSDOQLuZb1nsdCt6pJSPWSEQt2eFZ2YVk3oYhn+1kLMeMA==", - "dev": true, - "requires": { - "@babel/types": "^7.3.0" - } - }, - "@types/chai": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.3.tgz", - "integrity": "sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==", - "dev": true - }, - "@types/component-emitter": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.11.tgz", - "integrity": "sha512-SRXjM+tfsSlA9VuG8hGO2nft2p8zjXCK1VcC6N4NXbBbYbSia9kzCChYQajIjzIqOOOuh5Ock6MmV2oux4jDZQ==", - "dev": true - }, - "@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 - }, - "@types/cors": { - "version": "2.8.12", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", - "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", - "dev": true - }, - "@types/eslint": { - "version": "8.4.5", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.5.tgz", - "integrity": "sha512-dhsC09y1gpJWnK+Ff4SGvCuSnk9DaU0BJZSzOwa6GVSg65XtTugLBITDAAzRU5duGBoXBHpdR/9jHGxJjNflJQ==", - "dev": true, - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, - "@types/eslint-scope": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", - "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", - "dev": true, - "requires": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, - "@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true - }, - "@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "devOptional": true, - "requires": { - "@types/node": "*" - } - }, - "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "devOptional": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "devOptional": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", - "devOptional": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, - "@types/jest": { - "version": "23.3.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.3.14.tgz", - "integrity": "sha512-Q5hTcfdudEL2yOmluA1zaSyPbzWPmJ3XfSWeP3RyoYvS9hnje1ZyagrZOuQ6+1nQC1Gw+7gap3pLNL3xL6UBug==", - "dev": true - }, - "@types/jsdom": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.0.tgz", - "integrity": "sha512-YfAchFs0yM1QPDrLm2VHe+WHGtqms3NXnXAMolrgrVP6fgBHHXy1ozAbo/dFtPNtZC/m66bPiCTWYmqp1F14gA==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/tough-cookie": "*", - "parse5": "^7.0.0" - } - }, - "@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==", - "dev": true - }, - "@types/mocha": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", - "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", - "dev": true - }, - "@types/nise": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@types/nise/-/nise-1.4.0.tgz", - "integrity": "sha512-DPxmjiDwubsNmguG5X4fEJ+XCyzWM3GXWsqQlvUcjJKa91IOoJUy51meDr0GkzK64qqNcq85ymLlyjoct9tInw==", - "dev": true - }, - "@types/node": { - "version": "18.7.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.7.18.tgz", - "integrity": "sha512-m+6nTEOadJZuTPkKR/SYK3A2d7FZrgElol9UP1Kae90VVU4a6mxnPuLiIW1m4Cq4gZ/nWb9GrdVXJCoCazDAbg==", - "devOptional": true - }, - "@types/prettier": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.0.tgz", - "integrity": "sha512-RI1L7N4JnW5gQw2spvL7Sllfuf1SaHdrZpCHiBlCXjIlufi1SMNnbu2teze3/QE67Fg2tBlH7W+mi4hVNk4p0A==", - "dev": true - }, - "@types/resolve": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-0.0.8.tgz", - "integrity": "sha512-auApPaJf3NPfe18hSoJkp8EbZzer2ISk7o8mCC3M9he/a04+gbMF97NkpD2S8riMGvm4BMRI59/SZQSaLTKpsQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@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 - }, - "@types/tough-cookie": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", - "integrity": "sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==", - "dev": true - }, - "@types/uuid": { - "version": "3.4.10", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.10.tgz", - "integrity": "sha512-BgeaZuElf7DEYZhWYDTc/XcLZXdVgFkVSTa13BqKvbnmUrxr3TJFKofUxCtDO9UQOdhnV+HPOESdHiHKZOJV1A==", - "dev": true - }, - "@types/yargs": { - "version": "17.0.12", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.12.tgz", - "integrity": "sha512-Nz4MPhecOFArtm81gFQvQqdV7XYCrWKx5uUt6GNHredFHn1i2mtWqXTON7EPXMtNi1qjtjEM/VCHDhcHsAMLXQ==", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.0.tgz", - "integrity": "sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==", - "devOptional": true - }, - "@typescript-eslint/eslint-plugin": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.33.0.tgz", - "integrity": "sha512-jHvZNSW2WZ31OPJ3enhLrEKvAZNyAFWZ6rx9tUwaessTc4sx9KmgMNhVcqVAl1ETnT5rU5fpXTLmY9YvC1DCNg==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.33.0", - "@typescript-eslint/type-utils": "5.33.0", - "@typescript-eslint/utils": "5.33.0", - "debug": "^4.3.4", - "functional-red-black-tree": "^1.0.1", - "ignore": "^5.2.0", - "regexpp": "^3.2.0", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/parser": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.33.0.tgz", - "integrity": "sha512-cgM5cJrWmrDV2KpvlcSkelTBASAs1mgqq+IUGKJvFxWrapHpaRy5EXPQz9YaKF3nZ8KY18ILTiVpUtbIac86/w==", - "dev": true, - "requires": { - "@typescript-eslint/scope-manager": "5.33.0", - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/typescript-estree": "5.33.0", - "debug": "^4.3.4" - } - }, - "@typescript-eslint/scope-manager": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.33.0.tgz", - "integrity": "sha512-/Jta8yMNpXYpRDl8EwF/M8It2A9sFJTubDo0ATZefGXmOqlaBffEw0ZbkbQ7TNDK6q55NPHFshGBPAZvZkE8Pw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/visitor-keys": "5.33.0" - } - }, - "@typescript-eslint/type-utils": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.33.0.tgz", - "integrity": "sha512-2zB8uEn7hEH2pBeyk3NpzX1p3lF9dKrEbnXq1F7YkpZ6hlyqb2yZujqgRGqXgRBTHWIUG3NGx/WeZk224UKlIA==", - "dev": true, - "requires": { - "@typescript-eslint/utils": "5.33.0", - "debug": "^4.3.4", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/types": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.33.0.tgz", - "integrity": "sha512-nIMt96JngB4MYFYXpZ/3ZNU4GWPNdBbcB5w2rDOCpXOVUkhtNlG2mmm8uXhubhidRZdwMaMBap7Uk8SZMU/ppw==", - "dev": true - }, - "@typescript-eslint/typescript-estree": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.33.0.tgz", - "integrity": "sha512-tqq3MRLlggkJKJUrzM6wltk8NckKyyorCSGMq4eVkyL5sDYzJJcMgZATqmF8fLdsWrW7OjjIZ1m9v81vKcaqwQ==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/visitor-keys": "5.33.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "semver": "^7.3.7", - "tsutils": "^3.21.0" - } - }, - "@typescript-eslint/utils": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.33.0.tgz", - "integrity": "sha512-JxOAnXt9oZjXLIiXb5ZIcZXiwVHCkqZgof0O8KPgz7C7y0HS42gi75PdPlqh1Tf109M0fyUw45Ao6JLo7S5AHw==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.9", - "@typescript-eslint/scope-manager": "5.33.0", - "@typescript-eslint/types": "5.33.0", - "@typescript-eslint/typescript-estree": "5.33.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^3.0.0" - } - }, - "@typescript-eslint/visitor-keys": { - "version": "5.33.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.33.0.tgz", - "integrity": "sha512-/XsqCzD4t+Y9p5wd9HZiptuGKBlaZO5showwqODii5C0nZawxWLF+Q6k5wYHBrQv96h6GYKyqqMHCSTqta8Kiw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "5.33.0", - "eslint-visitor-keys": "^3.3.0" - } - }, - "@webassemblyjs/ast": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", - "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", - "dev": true, - "requires": { - "@webassemblyjs/helper-numbers": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1" - } - }, - "@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", - "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", - "dev": true - }, - "@webassemblyjs/helper-api-error": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", - "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", - "dev": true - }, - "@webassemblyjs/helper-buffer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", - "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", - "dev": true - }, - "@webassemblyjs/helper-numbers": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", - "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", - "dev": true, - "requires": { - "@webassemblyjs/floating-point-hex-parser": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", - "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", - "dev": true - }, - "@webassemblyjs/helper-wasm-section": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", - "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1" - } - }, - "@webassemblyjs/ieee754": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", - "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", - "dev": true, - "requires": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "@webassemblyjs/leb128": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", - "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", - "dev": true, - "requires": { - "@xtuc/long": "4.2.2" - } - }, - "@webassemblyjs/utf8": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", - "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", - "dev": true - }, - "@webassemblyjs/wasm-edit": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", - "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/helper-wasm-section": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-opt": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "@webassemblyjs/wast-printer": "1.11.1" - } - }, - "@webassemblyjs/wasm-gen": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", - "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "@webassemblyjs/wasm-opt": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", - "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-buffer": "1.11.1", - "@webassemblyjs/wasm-gen": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1" - } - }, - "@webassemblyjs/wasm-parser": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", - "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/helper-api-error": "1.11.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.1", - "@webassemblyjs/ieee754": "1.11.1", - "@webassemblyjs/leb128": "1.11.1", - "@webassemblyjs/utf8": "1.11.1" - } - }, - "@webassemblyjs/wast-printer": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", - "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.11.1", - "@xtuc/long": "4.2.2" - } - }, - "@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "dev": true - }, - "abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "devOptional": true, - "peer": true, - "requires": { - "event-target-shim": "^5.0.0" - } - }, - "absolute-path": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/absolute-path/-/absolute-path-0.0.0.tgz", - "integrity": "sha512-HQiug4c+/s3WOvEnDRxXVmNtSG5s2gJM9r19BTcqjp7BWcE48PB+Y2G6jE65kqI0LpsQeMZygt/b60Gi4KxGyA==", - "devOptional": true, - "peer": true - }, - "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==", - "devOptional": true, - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "acorn": { - "version": "8.8.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.0.tgz", - "integrity": "sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==", - "dev": true - }, - "acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "requires": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - }, - "dependencies": { - "acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true - } - } - }, - "acorn-import-assertions": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", - "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", - "dev": true, - "requires": {} - }, - "acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "requires": {} - }, - "acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true - }, - "agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "requires": { - "es6-promisify": "^5.0.0" - } - }, - "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, - "requires": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "requires": {} - }, - "anser": { - "version": "1.4.10", - "resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz", - "integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==", - "devOptional": true, - "peer": true - }, - "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==", - "dev": true, - "requires": { - "type-fest": "^0.21.3" - }, - "dependencies": { - "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==", - "dev": true - } - } - }, - "ansi-fragments": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-fragments/-/ansi-fragments-0.2.1.tgz", - "integrity": "sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==", - "devOptional": true, - "peer": true, - "requires": { - "colorette": "^1.0.7", - "slice-ansi": "^2.0.0", - "strip-ansi": "^5.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "devOptional": true, - "peer": true - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-regex": "^4.1.0" - } - } - } - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "devOptional": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "devOptional": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "devOptional": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "appdirsjs": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/appdirsjs/-/appdirsjs-1.2.7.tgz", - "integrity": "sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==", - "devOptional": true, - "peer": true - }, - "archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", - "dev": true - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "devOptional": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "devOptional": true, - "peer": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "devOptional": true, - "peer": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "devOptional": true, - "peer": true - }, - "array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha512-GQTc6Uupx1FCavi5mPzBvVT7nEOeWMmUA9P95wpfpW1XwMSKs+KaymD5C2Up7KAUKg/mYwbsUYzdZWcoajlNZg==", - "dev": true - }, - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "devOptional": true, - "peer": true - }, - "asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "devOptional": true, - "peer": true - }, - "asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "devOptional": true, - "peer": true - }, - "ast-types": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.14.2.tgz", - "integrity": "sha512-O0yuUDnZeQDL+ncNGlJ78BiO4jnYI3bvMsD5prT0/nsgijG/LpNBIr63gTjVTNsiGkgQhiyCShTgxt8oXOrklA==", - "devOptional": true, - "peer": true, - "requires": { - "tslib": "^2.0.1" - } - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "devOptional": true, - "peer": true - }, - "async": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", - "devOptional": true, - "peer": true - }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "devOptional": true, - "peer": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "devOptional": true, - "peer": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true - }, - "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", - "dev": true - }, - "babel-core": { - "version": "7.0.0-bridge.0", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz", - "integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==", - "devOptional": true, - "peer": true, - "requires": {} - }, - "babel-jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-28.1.3.tgz", - "integrity": "sha512-epUaPOEWMk3cWX0M/sPvCHHCe9fMFAa/9hXEgKP8nFfNl/jlGkE9ucq9NqkZGXLDduCJYS0UvSlPUwC0S+rH6Q==", - "dev": true, - "requires": { - "@jest/transform": "^28.1.3", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^28.1.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "devOptional": true, - "peer": true, - "requires": { - "object.assign": "^4.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "@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" - } - }, - "babel-plugin-jest-hoist": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-28.1.3.tgz", - "integrity": "sha512-Ys3tUKAmfnkRUpPdpa98eYrAR0nV+sSFUZZEGuQ2EbFd1y4SOLtD5QDNHAq+bb9a+bbXvYQC4b+ID/THIMcU6Q==", - "dev": true, - "requires": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" - } - }, - "babel-plugin-polyfill-corejs2": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz", - "integrity": "sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-define-polyfill-provider": "^0.3.3", - "semver": "^6.1.1" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "devOptional": true, - "peer": true - } - } - }, - "babel-plugin-polyfill-corejs3": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz", - "integrity": "sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.3", - "core-js-compat": "^3.25.1" - } - }, - "babel-plugin-polyfill-regenerator": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz", - "integrity": "sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/helper-define-polyfill-provider": "^0.3.3" - } - }, - "babel-plugin-syntax-trailing-function-commas": { - "version": "7.0.0-beta.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-7.0.0-beta.0.tgz", - "integrity": "sha512-Xj9XuRuz3nTSbaTXWv3itLOcxyF4oPD8douBBmj7U9BBC6nEBYfyOJYQMf/8PJAFotC62UY5dFfIGEPr7WswzQ==", - "devOptional": true, - "peer": true - }, - "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, - "requires": { - "@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" - } - }, - "babel-preset-fbjs": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/babel-preset-fbjs/-/babel-preset-fbjs-3.4.0.tgz", - "integrity": "sha512-9ywCsCvo1ojrw0b+XYk7aFvTH6D9064t0RIL1rtMf3nsa02Xw41MS7sZw216Im35xj/UY0PDBQsa1brUDDF1Ow==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0", - "@babel/plugin-syntax-class-properties": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.0.0", - "@babel/plugin-syntax-jsx": "^7.0.0", - "@babel/plugin-syntax-object-rest-spread": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-block-scoped-functions": "^7.0.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.0.0", - "@babel/plugin-transform-flow-strip-types": "^7.0.0", - "@babel/plugin-transform-for-of": "^7.0.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-member-expression-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-object-super": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-property-literals": "^7.0.0", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-template-literals": "^7.0.0", - "babel-plugin-syntax-trailing-function-commas": "^7.0.0-beta.0" - } - }, - "babel-preset-jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-28.1.3.tgz", - "integrity": "sha512-L+fupJvlWAHbQfn74coNX3zf60LXMJsezNvvx8eIh7iOR1luJ1poxYgQk1F8PYtNq/6QODDHCqsSnTFSWC491A==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^28.1.3", - "babel-preset-current-node-syntax": "^1.0.0" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "devOptional": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "devOptional": true, - "peer": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "devOptional": true, - "peer": true, - "requires": { - "is-descriptor": "^1.0.0" - } - } - } - }, - "base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "devOptional": true, - "peer": true - }, - "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 - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "devOptional": true, - "peer": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "body-parser": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.0.tgz", - "integrity": "sha512-DfJ+q6EPcGKZD1QWUjSpqp+Q7bDQTsQIF4zfUAtZ6qk+H/3/QRhg9CEp39ss+/T2vw0+HaidC0ecJj/DRLIaKg==", - "dev": true, - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "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.10.3", - "raw-body": "2.5.1", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "qs": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", - "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", - "dev": true, - "requires": { - "side-channel": "^1.0.4" - } - } - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "devOptional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "devOptional": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "browserslist": { - "version": "4.21.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz", - "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==", - "devOptional": true, - "requires": { - "caniuse-lite": "^1.0.30001400", - "electron-to-chromium": "^1.4.251", - "node-releases": "^2.0.6", - "update-browserslist-db": "^1.0.9" - } - }, - "browserstack": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", - "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", - "dev": true, - "requires": { - "https-proxy-agent": "^2.2.1" - } - }, - "browserstack-local": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.1.tgz", - "integrity": "sha512-T/wxyWDzvBHbDvl7fZKpFU7mYze6nrUkBhNy+d+8bXBqgQX10HTYvajIGO0wb49oGSLCPM0CMZTV/s7e6LF0sA==", - "dev": true, - "requires": { - "agent-base": "^6.0.2", - "https-proxy-agent": "^5.0.1", - "is-running": "^2.1.0", - "ps-tree": "=1.2.0", - "temp-fs": "^0.9.9" - }, - "dependencies": { - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - } - } - }, - "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, - "requires": { - "fast-json-stable-stringify": "2.x" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "devOptional": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "devOptional": true, - "peer": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "devOptional": true - }, - "builtin-modules": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz", - "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==", - "dev": true - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "dev": true - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "devOptional": true, - "peer": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "caching-transform": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", - "dev": true, - "requires": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" - }, - "dependencies": { - "write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - } - } - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "devOptional": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "caller-callsite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", - "devOptional": true, - "peer": true, - "requires": { - "callsites": "^2.0.0" - }, - "dependencies": { - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", - "devOptional": true, - "peer": true - } - } - }, - "caller-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", - "devOptional": true, - "peer": true, - "requires": { - "caller-callsite": "^2.0.0" - } - }, - "callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "devOptional": true - }, - "caniuse-lite": { - "version": "1.0.30001407", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001407.tgz", - "integrity": "sha512-4ydV+t4P7X3zH83fQWNDX/mQEzYomossfpViCOx9zHBSMV+rIe3LFqglHHtVyvNl1FhTNxPxs3jei82iqOW04w==", - "devOptional": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true - }, - "chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "devOptional": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "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 - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true - }, - "ci-info": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.3.2.tgz", - "integrity": "sha512-xmDt/QIAdeZ9+nfdPsaBCpMvHNLFiLdjj59qjqn+6iPe6YmHGQ35sBnQ8uslRBXFmXkiZQOJRjvQeoGppoTjjg==", - "devOptional": true - }, - "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 - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "devOptional": true, - "peer": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "devOptional": true, - "peer": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "devOptional": true, - "peer": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "devOptional": true, - "peer": true - } - } - }, - "clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "devOptional": true, - "peer": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-spinners": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", - "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", - "devOptional": true, - "peer": true - }, - "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, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "devOptional": true, - "peer": true - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true - }, - "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 - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", - "devOptional": true, - "peer": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "devOptional": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "devOptional": true - }, - "colorette": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.4.0.tgz", - "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==", - "devOptional": true, - "peer": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "command-exists": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz", - "integrity": "sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==", - "devOptional": true, - "peer": true - }, - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", - "devOptional": true - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "devOptional": true - }, - "compressible": { - "version": "2.0.18", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", - "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", - "devOptional": true, - "peer": true, - "requires": { - "mime-db": ">= 1.43.0 < 2" - } - }, - "compression": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", - "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", - "devOptional": true, - "peer": true, - "requires": { - "accepts": "~1.3.5", - "bytes": "3.0.0", - "compressible": "~2.0.16", - "debug": "2.6.9", - "on-headers": "~1.0.2", - "safe-buffer": "5.1.2", - "vary": "~1.1.2" - }, - "dependencies": { - "bytes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", - "devOptional": true, - "peer": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "devOptional": true, - "peer": true - } - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "devOptional": true - }, - "connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "devOptional": true, - "requires": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true - } - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", - "dev": true - }, - "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "devOptional": true, - "requires": { - "safe-buffer": "~5.1.1" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "devOptional": true - } - } - }, - "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 - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", - "devOptional": true, - "peer": true - }, - "core-js-compat": { - "version": "3.25.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.25.2.tgz", - "integrity": "sha512-TxfyECD4smdn3/CjWxczVtJqVLEEC2up7/82t7vC0AzNogr+4nQ8vyF7abxAuTXWvjTClSbvGhU0RgqA4ToQaQ==", - "devOptional": true, - "peer": true, - "requires": { - "browserslist": "^4.21.4" - } - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "devOptional": true - }, - "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, - "requires": { - "object-assign": "^4", - "vary": "^1" - } - }, - "cosmiconfig": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz", - "integrity": "sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==", - "devOptional": true, - "peer": true, - "requires": { - "import-fresh": "^2.0.0", - "is-directory": "^0.3.1", - "js-yaml": "^3.13.1", - "parse-json": "^4.0.0" - }, - "dependencies": { - "import-fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", - "devOptional": true, - "peer": true, - "requires": { - "caller-path": "^2.0.0", - "resolve-from": "^3.0.0" - } - }, - "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", - "devOptional": true, - "peer": true, - "requires": { - "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", - "devOptional": true, - "peer": true - } - } - }, - "coveralls": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", - "integrity": "sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==", - "dev": true, - "requires": { - "js-yaml": "^3.13.1", - "lcov-parse": "^1.0.0", - "log-driver": "^1.2.7", - "minimist": "^1.2.5", - "request": "^2.88.2" - } - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "dependencies": { - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "cssom": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", - "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", - "dev": true - }, - "cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "requires": { - "cssom": "~0.3.6" - }, - "dependencies": { - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - } - } - }, - "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 - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "data-urls": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", - "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", - "dev": true, - "requires": { - "abab": "^2.0.6", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0" - } - }, - "date-format": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.13.tgz", - "integrity": "sha512-bnYCwf8Emc3pTD8pXnre+wfnjGtfi5ncMDKy7+cWZXbmRAsdWkOQHrfC1yz/KiwP5thDp2kCHWYWKBX4HP1hoQ==", - "dev": true - }, - "dayjs": { - "version": "1.11.5", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.5.tgz", - "integrity": "sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA==", - "devOptional": true, - "peer": true - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "devOptional": true, - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "devOptional": true - }, - "decimal.js": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.0.tgz", - "integrity": "sha512-Nv6ENEzyPQ6AItkGwLE2PGKinZZ9g59vSh2BeH6NqPu0OTKZ5ruJsVqh/orbAnqXc9pBbgXAIrc2EyaCj8NpGg==", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==", - "devOptional": true, - "peer": true - }, - "decompress-response": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", - "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", - "requires": { - "mimic-response": "^2.0.0" - } - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==", - "dev": true - }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, - "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 - }, - "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true - }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha512-s82itHOnYrN0Ib8r+z7laQz3sdE+4FP3d9Q7VLO7U+KRT+CR0GsWuyHxzdAY82I7cXv0G/twrqomTJLOssO5HA==", - "devOptional": true, - "peer": true, - "requires": { - "clone": "^1.0.2" - } - }, - "define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "devOptional": true, - "peer": true, - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true - }, - "denodeify": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/denodeify/-/denodeify-1.2.1.tgz", - "integrity": "sha512-KNTihKNmQENUZeKu5fzfpzRqR5S2VMp4gl9RFHiWzj9DfvYQPMJ6XHKNaQxaGCXwPk6y9yme3aUoaiAe+KX+vg==", - "devOptional": true, - "peer": true - }, - "depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "devOptional": true - }, - "destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "devOptional": true - }, - "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==", - "dev": true - }, - "di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", - "dev": true - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "diff-sequences": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz", - "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==", - "dev": true - }, - "dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "requires": { - "path-type": "^4.0.0" - }, - "dependencies": { - "path-type": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", - "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", - "dev": true - } - } - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "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, - "requires": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" - } - }, - "domexception": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", - "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", - "dev": true, - "requires": { - "webidl-conversions": "^7.0.0" - } - }, - "duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "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==", - "devOptional": true - }, - "electron-to-chromium": { - "version": "1.4.256", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.256.tgz", - "integrity": "sha512-x+JnqyluoJv8I0U9gVe+Sk2st8vF0CzMt78SXxuoWCooLLY2k5VerIBdpvG7ql6GKI4dzNnPjmqgDJ76EdaAKw==", - "devOptional": true - }, - "emittery": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.10.2.tgz", - "integrity": "sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==", - "dev": true - }, - "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==", - "devOptional": true - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", - "devOptional": true - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "devOptional": true, - "peer": true, - "requires": { - "once": "^1.4.0" - } - }, - "engine.io": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.2.0.tgz", - "integrity": "sha512-4KzwW3F3bk+KlzSOY57fj/Jx6LyRQ1nbcyIadehl+AnXjKT7gDO0ORdRi/84ixvMKTym6ZKuxvbzN62HDDU1Lg==", - "dev": true, - "requires": { - "@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.2.3" - }, - "dependencies": { - "ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "dev": true, - "requires": {} - } - } - }, - "engine.io-parser": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz", - "integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==", - "dev": true - }, - "enhanced-resolve": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz", - "integrity": "sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - } - }, - "ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", - "dev": true - }, - "entities": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz", - "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==", - "dev": true - }, - "envinfo": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", - "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", - "devOptional": true, - "peer": true - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "devOptional": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "error-stack-parser": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", - "integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==", - "devOptional": true, - "peer": true, - "requires": { - "stackframe": "^1.3.4" - } - }, - "errorhandler": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/errorhandler/-/errorhandler-1.5.1.tgz", - "integrity": "sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==", - "devOptional": true, - "peer": true, - "requires": { - "accepts": "~1.3.7", - "escape-html": "~1.0.3" - } - }, - "es-module-lexer": { - "version": "0.9.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", - "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", - "dev": true - }, - "es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true - }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", - "dev": true, - "requires": { - "es6-promise": "^4.0.3" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "devOptional": true - }, - "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==", - "devOptional": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "devOptional": true - }, - "escodegen": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.0.0.tgz", - "integrity": "sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - } - } - }, - "eslint": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.21.0.tgz", - "integrity": "sha512-/XJ1+Qurf1T9G2M5IHrsjp+xrGT73RZf23xA1z5wB1ZzzEAWSZKvRwhWxTFp1rvkvCfwcvAUNAP31bhKTTGfDA==", - "dev": true, - "requires": { - "@eslint/eslintrc": "^1.3.0", - "@humanwhocodes/config-array": "^0.10.4", - "@humanwhocodes/gitignore-to-minimatch": "^1.0.2", - "ajv": "^6.10.0", - "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.1.1", - "eslint-utils": "^3.0.0", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.3.3", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "find-up": "^5.0.0", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^6.0.1", - "globals": "^13.15.0", - "globby": "^11.1.0", - "grapheme-splitter": "^1.0.4", - "ignore": "^5.2.0", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "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", - "regexpp": "^3.2.0", - "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - } - }, - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - }, - "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, - "requires": { - "is-glob": "^4.0.3" - } - }, - "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 - }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "eslint-config-prettier": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-6.15.0.tgz", - "integrity": "sha512-a1+kOYLR8wMGustcgAjdydMsQ2A/2ipRPwRKUmfYaSxc9ZPcrku080Ctl6zrZzZNs/U82MjSv+qKREkoq3bJaw==", - "dev": true, - "requires": { - "get-stdin": "^6.0.0" - } - }, - "eslint-plugin-prettier": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz", - "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==", - "dev": true, - "requires": { - "prettier-linter-helpers": "^1.0.0" - } - }, - "eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "requires": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - } - }, - "eslint-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", - "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", - "dev": true, - "requires": { - "eslint-visitor-keys": "^2.0.0" - }, - "dependencies": { - "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 - } - } - }, - "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", - "dev": true - }, - "espree": { - "version": "9.3.3", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.3.tgz", - "integrity": "sha512-ORs1Rt/uQTqUKjDdGCyrtYxbazf5umATSf/K4qxjmZHORR6HJk+2s/2Pqe+Kk49HHINC/xNIrGfgh8sZcll0ng==", - "dev": true, - "requires": { - "acorn": "^8.8.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" - } - }, - "esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "devOptional": true - }, - "esquery": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", - "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", - "dev": true, - "requires": { - "estraverse": "^5.1.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "requires": { - "estraverse": "^5.2.0" - }, - "dependencies": { - "estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true - } - } - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "estree-walker": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", - "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "devOptional": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "devOptional": true, - "peer": true - }, - "event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", - "dev": true, - "requires": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - } - }, - "event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", - "devOptional": true, - "peer": true - }, - "eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true - }, - "execa": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", - "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^6.0.0", - "human-signals": "^2.1.0", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.1", - "onetime": "^5.1.2", - "signal-exit": "^3.0.3", - "strip-final-newline": "^2.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", - "devOptional": true, - "peer": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "devOptional": true, - "peer": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "devOptional": true, - "peer": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "devOptional": true, - "peer": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - } - } - }, - "expect": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz", - "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==", - "dev": true, - "requires": { - "@jest/expect-utils": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "devOptional": true, - "peer": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "devOptional": true, - "peer": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "devOptional": true, - "peer": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true - } - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "fast-glob": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", - "integrity": "sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.4" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "fastq": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", - "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, - "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "devOptional": true, - "requires": { - "bser": "2.1.1" - } - }, - "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", - "dev": true, - "requires": { - "flat-cache": "^3.0.4" - } - }, - "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==", - "devOptional": true, - "requires": { - "to-regex-range": "^5.0.1" - }, - "dependencies": { - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "devOptional": true, - "requires": { - "is-number": "^7.0.0" - } - } - } - }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "devOptional": true, - "requires": { - "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" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true - }, - "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==", - "devOptional": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "devOptional": true - } - } - }, - "find-cache-dir": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", - "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - } - }, - "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==", - "devOptional": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "dependencies": { - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true - } - } - }, - "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", - "dev": true, - "requires": { - "flatted": "^3.1.0", - "rimraf": "^3.0.2" - }, - "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, - "flatted": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.6.tgz", - "integrity": "sha512-0sQoMh9s0BYsm+12Huy/rkKxVu4R1+r96YX5cG44rHV0pQ6iC3Q+mkoMFaGWObMFYQxCVT+ssG1ksneA2MI9KQ==", - "dev": true - }, - "flow-parser": { - "version": "0.121.0", - "resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.121.0.tgz", - "integrity": "sha512-1gIBiWJNR0tKUNv8gZuk7l9rVX06OuLzY9AoGio7y/JT4V1IZErEMEq2TJS+PFcw/y0RshZ1J/27VfK1UQzYVg==", - "devOptional": true, - "peer": true - }, - "follow-redirects": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz", - "integrity": "sha512-yLAMQs+k0b2m7cVxpS1VKJVvoz7SS9Td1zss3XRwXj+ZDH00RJgnuLx7E44wx02kQLrdM3aOOy+FpzS7+8OizA==", - "dev": true - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", - "devOptional": true, - "peer": true - }, - "foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, - "requires": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "formatio": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", - "integrity": "sha512-YAF05v8+XCxAyHOdiiAmHdgCVPrWO8X744fYIPtBciIorh5LndWfi1gjeJ16sTbJhzek9kd+j3YByhohtz5Wmg==", - "dev": true, - "requires": { - "samsam": "1.x" - } - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", - "devOptional": true, - "peer": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "devOptional": true, - "peer": true - }, - "from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", - "dev": true - }, - "fromentries": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", - "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", - "dev": true - }, - "fs-access": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", - "integrity": "sha512-05cXDIwNbFaoFWaz5gNHlUTbH5whiss/hr/ibzPd4MH3cR4w0ZKeIPiVdbyJurg3O5r/Bjpvn9KOb1/rPMf3nA==", - "dev": true, - "requires": { - "null-check": "^1.0.0" - } - }, - "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==", - "devOptional": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "devOptional": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "devOptional": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", - "dev": true - }, - "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==", - "devOptional": true - }, - "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==", - "devOptional": true - }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", - "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", - "devOptional": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - } - }, - "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 - }, - "get-stdin": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-6.0.0.tgz", - "integrity": "sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==", - "dev": true - }, - "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 - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", - "devOptional": true, - "peer": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "devOptional": true, - "requires": { - "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" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "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 - }, - "globals": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", - "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - }, - "dependencies": { - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - } - } - }, - "globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "requires": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - } - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "devOptional": true - }, - "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 - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "dev": true - }, - "har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "dev": true, - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "devOptional": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "devOptional": true - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "get-intrinsic": "^1.1.1" - } - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "devOptional": true - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", - "devOptional": true, - "peer": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "hasha": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", - "integrity": "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ==", - "dev": true, - "requires": { - "is-stream": "^2.0.0", - "type-fest": "^0.8.0" - }, - "dependencies": { - "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 - } - } - }, - "he": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha512-z/GDPjlRMNOa2XJiB4em8wJpuuBfrFOlYKTZxtpkdr1uPdibHI8rYA3MY0KDObpVyaes0e/aunid/t88ZI2EKA==", - "dev": true - }, - "hermes-engine": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/hermes-engine/-/hermes-engine-0.11.0.tgz", - "integrity": "sha512-7aMUlZja2IyLYAcZ69NBnwJAR5ZOYlSllj0oMpx08a8HzxHOys0eKCzfphrf6D0vX1JGO1QQvVsQKe6TkYherw==", - "devOptional": true, - "peer": true - }, - "hermes-estree": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.6.0.tgz", - "integrity": "sha512-2YTGzJCkhdmT6VuNprWjXnvTvw/3iPNw804oc7yknvQpNKo+vJGZmtvLLCghOZf0OwzKaNAzeIMp71zQbNl09w==", - "devOptional": true, - "peer": true - }, - "hermes-parser": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.6.0.tgz", - "integrity": "sha512-Vf58jBZca2+QBLR9h7B7mdg8oFz2g5ILz1iVouZ5DOrOrAfBmPfJjdjDT8jrO0f+iJ4/hSRrQHqHIjSnTaLUDQ==", - "devOptional": true, - "peer": true, - "requires": { - "hermes-estree": "0.6.0" - } - }, - "hermes-profile-transformer": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/hermes-profile-transformer/-/hermes-profile-transformer-0.0.6.tgz", - "integrity": "sha512-cnN7bQUm65UWOy6cbGcCcZ3rpwW8Q/j4OP5aWRhEry4Z2t2aR1cjrbp0BS+KiBN0smvP1caBgAuxutvyvJILzQ==", - "devOptional": true, - "peer": true, - "requires": { - "source-map": "^0.7.3" - }, - "dependencies": { - "source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", - "devOptional": true, - "peer": true - } - } - }, - "html-encoding-sniffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", - "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", - "dev": true, - "requires": { - "whatwg-encoding": "^2.0.0" - } - }, - "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 - }, - "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==", - "devOptional": true, - "requires": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - } - }, - "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, - "requires": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" - } - }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "requires": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "dependencies": { - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - } - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } - } - }, - "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 - }, - "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, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "devOptional": true, - "peer": true - }, - "ignore": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", - "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", - "dev": true - }, - "image-size": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.6.3.tgz", - "integrity": "sha512-47xSUiQioGaB96nqtp5/q55m0aBQSQdyIloMOc/x+QVTDZLNmXE892IIDrJ0hM1A5vcNUDD5tDffkSP5lCaIIA==", - "devOptional": true, - "peer": true - }, - "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, - "requires": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - } - }, - "import-local": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", - "integrity": "sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==", - "dev": true, - "requires": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "devOptional": true - }, - "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 - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "devOptional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "devOptional": true - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "devOptional": true, - "peer": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, - "ip": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz", - "integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg==", - "devOptional": true, - "peer": true - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "devOptional": true - }, - "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, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "devOptional": true, - "peer": true - }, - "is-core-module": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.10.0.tgz", - "integrity": "sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==", - "devOptional": true, - "requires": { - "has": "^1.0.3" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "devOptional": true, - "peer": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==", - "devOptional": true, - "peer": true - }, - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "devOptional": true, - "peer": true, - "requires": { - "is-plain-object": "^2.0.4" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true - }, - "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==", - "devOptional": true - }, - "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==", - "dev": true - }, - "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, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "devOptional": true, - "peer": true - }, - "is-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", - "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", - "dev": true - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "devOptional": true - }, - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "devOptional": true - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true - }, - "is-reference": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", - "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==", - "dev": true, - "requires": { - "@types/estree": "*" - } - }, - "is-running": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", - "integrity": "sha512-mjJd3PujZMl7j+D395WTIO5tU5RIDBfVSRtRR4VOJou3H66E38UjbjvDGh3slJzPuolsb+yQFqwHNNdyp5jg3w==", - "dev": true - }, - "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 - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, - "is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "devOptional": true, - "peer": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "devOptional": true - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", - "devOptional": true, - "peer": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "devOptional": true, - "peer": true - }, - "isbinaryfile": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", - "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "devOptional": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "devOptional": true, - "peer": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true - }, - "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 - }, - "istanbul-lib-instrument": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.0.tgz", - "integrity": "sha512-6Lthe1hqXHBNsqvgDzGO6l03XNeu3CrG4RqQ1KM9+l5+jNGpEJfIELx1NS3SEHmJQA8np/u+E4EPRKRiu6m19A==", - "dev": true, - "requires": { - "@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" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "istanbul-lib-processinfo": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", - "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", - "dev": true, - "requires": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.3", - "istanbul-lib-coverage": "^3.2.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^8.3.2" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "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, - "requires": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "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 - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "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, - "requires": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "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, - "requires": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - } - }, - "jest": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-28.1.3.tgz", - "integrity": "sha512-N4GT5on8UkZgH0O5LUavMRV1EDEhNTL0KEfRmDIeZHSV7p2XgLoY9t9VDUgL6o+yfdgYHVxuz81G8oB9VG5uyA==", - "dev": true, - "requires": { - "@jest/core": "^28.1.3", - "@jest/types": "^28.1.3", - "import-local": "^3.0.2", - "jest-cli": "^28.1.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "jest-cli": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-28.1.3.tgz", - "integrity": "sha512-roY3kvrv57Azn1yPgdTebPAXvdR2xfezaKKYzVxZ6It/5NCxzJym6tUI5P1zkdWhfUYkxEI9uZWcQdaFLo8mJQ==", - "dev": true, - "requires": { - "@jest/core": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "import-local": "^3.0.2", - "jest-config": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "prompts": "^2.0.1", - "yargs": "^17.3.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "yargs": { - "version": "17.5.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", - "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.0.0" - } - }, - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - } - } - }, - "jest-changed-files": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-28.1.3.tgz", - "integrity": "sha512-esaOfUWJXk2nfZt9SPyC8gA1kNfdKLkQWyzsMlqq8msYSlNKfmZxfRgZn4Cd4MGVUF+7v6dBs0d5TOAKa7iIiA==", - "dev": true, - "requires": { - "execa": "^5.0.0", - "p-limit": "^3.1.0" - } - }, - "jest-circus": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-28.1.3.tgz", - "integrity": "sha512-cZ+eS5zc79MBwt+IhQhiEp0OeBddpc1n8MBo1nMB8A7oPMKEO+Sre+wHaLJexQUj9Ya/8NOBY0RESUgYjB6fow==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.3", - "@jest/expect": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "dedent": "^0.7.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "p-limit": "^3.1.0", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - } - }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-config": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-28.1.3.tgz", - "integrity": "sha512-MG3INjByJ0J4AsNBm7T3hsuxKQqFIiRo/AUqb1q9LRKI5UU6Aar9JHbr9Ivn1TVwfUD9KirRoM/T6u8XlcQPHQ==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^28.1.3", - "@jest/types": "^28.1.3", - "babel-jest": "^28.1.3", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^28.1.3", - "jest-environment-node": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-runner": "^28.1.3", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "micromatch": "^4.0.4", - "parse-json": "^5.2.0", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "strip-json-comments": "^3.1.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-diff": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz", - "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^28.1.1", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-docblock": { - "version": "28.1.1", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-28.1.1.tgz", - "integrity": "sha512-3wayBVNiOYx0cwAbl9rwm5kKFP8yHH3d/fkEaL02NPTkDojPtheGB7HZSFY4wzX+DxyrvhXz0KSCVksmCknCuA==", - "dev": true, - "requires": { - "detect-newline": "^3.0.0" - } - }, - "jest-each": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-28.1.3.tgz", - "integrity": "sha512-arT1z4sg2yABU5uogObVPvSlSMQlDA48owx07BDPAiasW0yYpYHYOo4HHLz9q0BVzDVU4hILFjzJw0So9aCL/g==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", - "jest-util": "^28.1.3", - "pretty-format": "^28.1.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-environment-jsdom": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.0.1.tgz", - "integrity": "sha512-rMF501kfui+bw4AmwowLA2bNaYb633A3ejFMN5pVU0AeOqLv2NbMAY5XzzlMr/+lM1itEf+3ZdCr9dGGrUfoxg==", - "dev": true, - "requires": { - "@jest/environment": "^29.0.1", - "@jest/fake-timers": "^29.0.1", - "@jest/types": "^29.0.1", - "@types/jsdom": "^20.0.0", - "@types/node": "*", - "jest-mock": "^29.0.1", - "jest-util": "^29.0.1", - "jsdom": "^20.0.0" - }, - "dependencies": { - "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/types": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.1.tgz", - "integrity": "sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "jest-util": { - "version": "29.0.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.0.1.tgz", - "integrity": "sha512-GIWkgNfkeA9d84rORDHPGGTFBrRD13A38QVSKE0bVrGSnoR1KDn8Kqz+0yI5kezMgbT/7zrWaruWP1Kbghlb2A==", - "dev": true, - "requires": { - "@jest/types": "^29.0.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-environment-node": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-28.1.3.tgz", - "integrity": "sha512-ugP6XOhEpjAEhGYvp5Xj989ns5cB1K6ZdjBYuS30umT4CQEETaxSiPcZ/E1kFktX4GkrcM4qu07IIlDYX1gp+A==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.3", - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - }, - "dependencies": { - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - } - }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" - } - } - } - }, - "jest-get-type": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz", - "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==", - "dev": true - }, - "jest-haste-map": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-28.1.3.tgz", - "integrity": "sha512-3S+RQWDXccXDKSWnkHa/dPwt+2qwA8CJzR61w3FoYCvoo3Pn8tvGcysmMF0Bj0EX5RYvAI2EIvC57OmotfdtKA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/graceful-fs": "^4.1.3", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^28.0.2", - "jest-util": "^28.1.3", - "jest-worker": "^28.1.3", - "micromatch": "^4.0.4", - "walker": "^1.0.8" - } - }, - "jest-leak-detector": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-28.1.3.tgz", - "integrity": "sha512-WFVJhnQsiKtDEo5lG2mM0v40QWnBM+zMdHHyJs8AWZ7J0QZJS59MsyKeJHWhpBZBH32S48FOVvGyOFT1h0DlqA==", - "dev": true, - "requires": { - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - } - }, - "jest-localstorage-mock": { - "version": "2.4.22", - "resolved": "https://registry.npmjs.org/jest-localstorage-mock/-/jest-localstorage-mock-2.4.22.tgz", - "integrity": "sha512-60PWSDFQOS5v7JzSmYLM3dPLg0JLl+2Vc4lIEz/rj2yrXJzegsFLn7anwc5IL0WzJbBa/Las064CHbFg491/DQ==", - "dev": true - }, - "jest-matcher-utils": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz", - "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "pretty-format": "^28.1.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-message-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz", - "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^28.1.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^28.1.3", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-mock": { - "version": "29.0.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.0.3.tgz", - "integrity": "sha512-ort9pYowltbcrCVR43wdlqfAiFJXBx8l4uJDsD8U72LgBcetvEp+Qxj1W9ZYgMRoeAo+ov5cnAGF2B6+Oth+ww==", - "dev": true, - "requires": { - "@jest/types": "^29.0.3", - "@types/node": "*" - }, - "dependencies": { - "@jest/schemas": { - "version": "29.0.0", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.0.0.tgz", - "integrity": "sha512-3Ab5HgYIIAnS0HjqJHQYZS+zXc4tUmTmBH3z83ajI6afXp8X3ZtdLX+nXx+I7LNkJD7uN9LAVhgnjDgZa2z0kA==", - "dev": true, - "requires": { - "@sinclair/typebox": "^0.24.1" - } - }, - "@jest/types": { - "version": "29.0.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.0.3.tgz", - "integrity": "sha512-coBJmOQvurXjN1Hh5PzF7cmsod0zLIOXpP8KD161mqNlroMhLcwpODiEzi7ZsRl5Z/AIuxpeNm8DCl43F4kz8A==", - "dev": true, - "requires": { - "@jest/schemas": "^29.0.0", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "requires": {} - }, - "jest-regex-util": { - "version": "28.0.2", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-28.0.2.tgz", - "integrity": "sha512-4s0IgyNIy0y9FK+cjoVYoxamT7Zeo7MhzqRGx7YDYmaQn1wucY9rotiGkBzzcMXTtjrCAP/f7f+E0F7+fxPNdw==", - "dev": true - }, - "jest-resolve": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-28.1.3.tgz", - "integrity": "sha512-Z1W3tTjE6QaNI90qo/BJpfnvpxtaFTFw5CDgwpyE/Kz8U/06N1Hjf4ia9quUhCh39qIGWF1ZuxFiBiJQwSEYKQ==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^28.1.3", - "jest-validate": "^28.1.3", - "resolve": "^1.20.0", - "resolve.exports": "^1.1.0", - "slash": "^3.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-resolve-dependencies": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-28.1.3.tgz", - "integrity": "sha512-qa0QO2Q0XzQoNPouMbCc7Bvtsem8eQgVPNkwn9LnS+R2n8DaVDPL/U1gngC0LTl1RYXJU0uJa2BMC2DbTfFrHA==", - "dev": true, - "requires": { - "jest-regex-util": "^28.0.2", - "jest-snapshot": "^28.1.3" - } - }, - "jest-runner": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-28.1.3.tgz", - "integrity": "sha512-GkMw4D/0USd62OVO0oEgjn23TM+YJa2U2Wu5zz9xsQB1MxWKDOlrnykPxnMsN0tnJllfLPinHTka61u0QhaxBA==", - "dev": true, - "requires": { - "@jest/console": "^28.1.3", - "@jest/environment": "^28.1.3", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "graceful-fs": "^4.2.9", - "jest-docblock": "^28.1.1", - "jest-environment-node": "^28.1.3", - "jest-haste-map": "^28.1.3", - "jest-leak-detector": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-resolve": "^28.1.3", - "jest-runtime": "^28.1.3", - "jest-util": "^28.1.3", - "jest-watcher": "^28.1.3", - "jest-worker": "^28.1.3", - "p-limit": "^3.1.0", - "source-map-support": "0.5.13" - }, - "dependencies": { - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - } - }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-runtime": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-28.1.3.tgz", - "integrity": "sha512-NU+881ScBQQLc1JHG5eJGU7Ui3kLKrmwCPPtYsJtBykixrM2OhVQlpMmFWJjMyDfdkGgBMNjXCGB/ebzsgNGQw==", - "dev": true, - "requires": { - "@jest/environment": "^28.1.3", - "@jest/fake-timers": "^28.1.3", - "@jest/globals": "^28.1.3", - "@jest/source-map": "^28.1.2", - "@jest/test-result": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "execa": "^5.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-regex-util": "^28.0.2", - "jest-resolve": "^28.1.3", - "jest-snapshot": "^28.1.3", - "jest-util": "^28.1.3", - "slash": "^3.0.0", - "strip-bom": "^4.0.0" - }, - "dependencies": { - "@jest/environment": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-28.1.3.tgz", - "integrity": "sha512-1bf40cMFTEkKyEf585R9Iz1WayDjHoHqvts0XFYEqyKM3cFWDpeMoqKKTAF9LSYQModPUlh8FKptoM2YcMWAXA==", - "dev": true, - "requires": { - "@jest/fake-timers": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "jest-mock": "^28.1.3" - } - }, - "@jest/fake-timers": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-28.1.3.tgz", - "integrity": "sha512-D/wOkL2POHv52h+ok5Oj/1gOG9HSywdoPtFsRCUmlCILXNn5eIWmcnd3DIiWlJnpGvQtmajqBP95Ei0EimxfLw==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@sinonjs/fake-timers": "^9.1.2", - "@types/node": "*", - "jest-message-util": "^28.1.3", - "jest-mock": "^28.1.3", - "jest-util": "^28.1.3" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "jest-mock": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-28.1.3.tgz", - "integrity": "sha512-o3J2jr6dMMWYVH4Lh/NKmDXdosrsJgi4AviS8oXLujcjpCMBb1FMsblDnOXKZKfSiHLxYub1eS0IHuRXsio9eA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-serializer": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz", - "integrity": "sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==", - "devOptional": true, - "peer": true, - "requires": { - "@types/node": "*", - "graceful-fs": "^4.2.9" - } - }, - "jest-snapshot": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-28.1.3.tgz", - "integrity": "sha512-4lzMgtiNlc3DU/8lZfmqxN3AYD6GGLbl+72rdBpXvcV+whX7mDrREzkPdp2RnmfIiWBg1YbuFSkXduF2JcafJg==", - "dev": true, - "requires": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/traverse": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^28.1.3", - "@jest/transform": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/babel__traverse": "^7.0.6", - "@types/prettier": "^2.1.5", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^28.1.3", - "graceful-fs": "^4.2.9", - "jest-diff": "^28.1.3", - "jest-get-type": "^28.0.2", - "jest-haste-map": "^28.1.3", - "jest-matcher-utils": "^28.1.3", - "jest-message-util": "^28.1.3", - "jest-util": "^28.1.3", - "natural-compare": "^1.4.0", - "pretty-format": "^28.1.3", - "semver": "^7.3.5" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-util": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz", - "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-validate": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-28.1.3.tgz", - "integrity": "sha512-SZbOGBWEsaTxBGCOpsRWlXlvNkvTkY0XxRfh7zYmvd8uL5Qzyg0CHAXiXKROflh801quA6+/DsT4ODDthOC/OA==", - "dev": true, - "requires": { - "@jest/types": "^28.1.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^28.0.2", - "leven": "^3.1.0", - "pretty-format": "^28.1.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-watcher": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-28.1.3.tgz", - "integrity": "sha512-t4qcqj9hze+jviFPUN3YAtAEeFnr/azITXQEMARf5cMwKY2SMBRnCQTXLixTl20OR6mLh9KLMrgVJgJISym+1g==", - "dev": true, - "requires": { - "@jest/test-result": "^28.1.3", - "@jest/types": "^28.1.3", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "emittery": "^0.10.2", - "jest-util": "^28.1.3", - "string-length": "^4.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-worker": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-28.1.3.tgz", - "integrity": "sha512-CqRA220YV/6jCo8VWvAt1KKx6eek1VIHMPeLEbpcfSfkEeWyBNppynM/o6q+Wmw+sOhos2ml34wZbSX3G13//g==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "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 - }, - "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, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jetifier": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/jetifier/-/jetifier-1.6.8.tgz", - "integrity": "sha512-3Zi16h6L5tXDRQJTb221cnRoVG9/9OvreLdLU2/ZjRv/GILL+2Cemt0IKvkowwkDpvouAU1DQPOJ7qaiHeIdrw==", - "devOptional": true, - "peer": true - }, - "joi": { - "version": "17.6.0", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.6.0.tgz", - "integrity": "sha512-OX5dG6DTbcr/kbMFj0KGYxuew69HPcAE3K/sZpEV2nP6e/j/C0HV+HNiBPCASxdx5T7DMoa0s8UeHWMnb6n2zw==", - "devOptional": true, - "peer": true, - "requires": { - "@hapi/hoek": "^9.0.0", - "@hapi/topo": "^5.0.0", - "@sideway/address": "^4.1.3", - "@sideway/formula": "^3.0.0", - "@sideway/pinpoint": "^2.0.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "devOptional": true - }, - "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==", - "devOptional": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true - }, - "jsc-android": { - "version": "250230.2.1", - "resolved": "https://registry.npmjs.org/jsc-android/-/jsc-android-250230.2.1.tgz", - "integrity": "sha512-KmxeBlRjwoqCnBBKGsihFtvsBHyUFlBxJPK4FzeYcIuBfdjv6jFys44JITAgSTbQD+vIdwMEfyZklsuQX0yI1Q==", - "devOptional": true, - "peer": true - }, - "jscodeshift": { - "version": "0.13.1", - "resolved": "https://registry.npmjs.org/jscodeshift/-/jscodeshift-0.13.1.tgz", - "integrity": "sha512-lGyiEbGOvmMRKgWk4vf+lUrCWO/8YR8sUR3FKF1Cq5fovjZDlIcw3Hu5ppLHAnEXshVffvaM0eyuY/AbOeYpnQ==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/core": "^7.13.16", - "@babel/parser": "^7.13.16", - "@babel/plugin-proposal-class-properties": "^7.13.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.8", - "@babel/plugin-proposal-optional-chaining": "^7.13.12", - "@babel/plugin-transform-modules-commonjs": "^7.13.8", - "@babel/preset-flow": "^7.13.13", - "@babel/preset-typescript": "^7.13.0", - "@babel/register": "^7.13.16", - "babel-core": "^7.0.0-bridge.0", - "chalk": "^4.1.2", - "flow-parser": "0.*", - "graceful-fs": "^4.2.4", - "micromatch": "^3.1.10", - "neo-async": "^2.5.0", - "node-dir": "^0.1.17", - "recast": "^0.20.4", - "temp": "^0.8.4", - "write-file-atomic": "^2.3.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "devOptional": true, - "peer": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "devOptional": true, - "peer": true - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "devOptional": true, - "peer": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "devOptional": true, - "peer": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "devOptional": true, - "peer": true, - "requires": { - "glob": "^7.1.3" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "temp": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.4.tgz", - "integrity": "sha512-s0ZZzd0BzYv5tLSptZooSjK8oj6C+c19p7Vqta9+6NPOf7r+fxq0cJe6/oN4LTC79sy5NY8ucOJNgwsKCSbfqg==", - "devOptional": true, - "peer": true, - "requires": { - "rimraf": "~2.6.2" - } - }, - "write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "devOptional": true, - "peer": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - } - } - }, - "jsdom": { - "version": "20.0.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.0.tgz", - "integrity": "sha512-x4a6CKCgx00uCmP+QakBDFXwjAJ69IkkIWHmtmjd3wvXPcdOS44hfX2vqkOQrVrq8l9DhNNADZRXaCEWvgXtVA==", - "dev": true, - "requires": { - "abab": "^2.0.6", - "acorn": "^8.7.1", - "acorn-globals": "^6.0.0", - "cssom": "^0.5.0", - "cssstyle": "^2.3.0", - "data-urls": "^3.0.2", - "decimal.js": "^10.3.1", - "domexception": "^4.0.0", - "escodegen": "^2.0.0", - "form-data": "^4.0.0", - "html-encoding-sniffer": "^3.0.0", - "http-proxy-agent": "^5.0.0", - "https-proxy-agent": "^5.0.1", - "is-potential-custom-element-name": "^1.0.1", - "nwsapi": "^2.2.0", - "parse5": "^7.0.0", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^4.0.0", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^3.0.0", - "webidl-conversions": "^7.0.0", - "whatwg-encoding": "^2.0.0", - "whatwg-mimetype": "^3.0.0", - "whatwg-url": "^11.0.0", - "ws": "^8.8.0", - "xml-name-validator": "^4.0.0" - }, - "dependencies": { - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "requires": { - "debug": "4" - } - }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "tough-cookie": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", - "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", - "dev": true, - "requires": { - "psl": "^1.1.33", - "punycode": "^2.1.1", - "universalify": "^0.2.0", - "url-parse": "^1.5.3" - } - }, - "universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", - "dev": true - } - } - }, - "jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==", - "devOptional": true, - "peer": true - }, - "json-loader": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", - "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", - "dev": true - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "devOptional": true, - "peer": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "json-schema": { - "version": "0.4.0", - "resolved": "https://nexus.es.ecg.tools/repository/npm-all/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==" - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true - }, - "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 - }, - "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", - "dev": true - }, - "jsonfile": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", - "devOptional": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, - "just-extend": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", - "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", - "dev": true - }, - "karma": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.0.tgz", - "integrity": "sha512-s8m7z0IF5g/bS5ONT7wsOavhW4i4aFkzD4u4wgzAQWT4HGUeWI3i21cK2Yz6jndMAeHETp5XuNsRoyGJZXVd4w==", - "dev": true, - "requires": { - "@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" - }, - "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", - "dev": true, - "requires": { - "rimraf": "^3.0.0" - } - } - } - }, - "karma-browserstack-launcher": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/karma-browserstack-launcher/-/karma-browserstack-launcher-1.6.0.tgz", - "integrity": "sha512-Y/UWPdHZkHIVH2To4GWHCTzmrsB6H7PBWy6pw+TWz5sr4HW2mcE+Uj6qWgoVNxvQU1Pfn5LQQzI6EQ65p8QbiQ==", - "dev": true, - "requires": { - "browserstack": "~1.5.1", - "browserstack-local": "^1.3.7", - "q": "~1.5.0" - } - }, - "karma-chai": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/karma-chai/-/karma-chai-0.1.0.tgz", - "integrity": "sha512-mqKCkHwzPMhgTYca10S90aCEX9+HjVjjrBFAsw36Zj7BlQNbokXXCAe6Ji04VUMsxcY5RLP7YphpfO06XOubdg==", - "dev": true, - "requires": {} - }, - "karma-chrome-launcher": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", - "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", - "dev": true, - "requires": { - "fs-access": "^1.0.0", - "which": "^1.2.1" - } - }, - "karma-mocha": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-1.3.0.tgz", - "integrity": "sha512-twRO+KCXIFOBs7o6i7oIpTJhVvjKZbIsUM96A+k2QaeXOzbVQXCkjVzXqNeQoczW4ruasPZYi0iWMTkfTrQVCw==", - "dev": true, - "requires": { - "minimist": "1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha512-7Wl+Jz+IGWuSdgsQEJ4JunV0si/iMhg42MnQQG6h1R6TNeVenp4U9x5CC5v/gYqz/fENLQITAWXidNtVL0NNbw==", - "dev": true - } - } - }, - "karma-webpack": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-5.0.0.tgz", - "integrity": "sha512-+54i/cd3/piZuP3dr54+NcFeKOPnys5QeM1IY+0SPASwrtHsliXUiCL50iW+K9WWA7RvamC4macvvQ86l3KtaA==", - "dev": true, - "requires": { - "glob": "^7.1.3", - "minimatch": "^3.0.4", - "webpack-merge": "^4.1.5" - } - }, - "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==", - "devOptional": true, - "peer": true - }, - "klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==", - "devOptional": true, - "peer": true, - "requires": { - "graceful-fs": "^4.1.9" - } - }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "devOptional": true - }, - "lcov-parse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-1.0.0.tgz", - "integrity": "sha512-aprLII/vPzuQvYZnDRU78Fns9I2Ag3gi4Ipga/hxnVMCZC8DnR2nI7XBqrPoywGfxqIx/DgarGvDJZAD3YBTgQ==", - "dev": true - }, - "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==", - "devOptional": true - }, - "levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - } - }, - "lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true - }, - "loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", - "dev": true - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "devOptional": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "devOptional": true - }, - "lodash.debounce": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", - "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", - "devOptional": true, - "peer": true - }, - "lodash.flattendeep": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", - "integrity": "sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ==", - "dev": true - }, - "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==", - "dev": true - }, - "lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, - "lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", - "devOptional": true, - "peer": true - }, - "log-driver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", - "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", - "dev": true - }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "devOptional": true, - "peer": true, - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "devOptional": true, - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "log4js": { - "version": "6.6.1", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.6.1.tgz", - "integrity": "sha512-J8VYFH2UQq/xucdNu71io4Fo+purYYudyErgBbswWKO0MC6QVOERRomt5su/z6d3RJSmLyTGmXl3Q/XjKCf+/A==", - "dev": true, - "requires": { - "date-format": "^4.0.13", - "debug": "^4.3.4", - "flatted": "^3.2.6", - "rfdc": "^1.3.0", - "streamroller": "^3.1.2" - } - }, - "logkitty": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/logkitty/-/logkitty-0.7.1.tgz", - "integrity": "sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-fragments": "^0.2.1", - "dayjs": "^1.8.15", - "yargs": "^15.1.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "devOptional": true, - "peer": true - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "devOptional": true, - "peer": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "devOptional": true, - "peer": true - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "devOptional": true, - "peer": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "devOptional": true, - "peer": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "devOptional": true, - "peer": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true, - "peer": true - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "devOptional": true, - "peer": true - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "devOptional": true, - "peer": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "devOptional": true, - "peer": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "lolex": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", - "integrity": "sha512-/bpxDL56TG5LS5zoXxKqA6Ro5tkOS5M8cm/7yQcwLIKIcM2HR5fjjNCaIhJNv96SEk4hNGSafYMZK42Xv5fihQ==", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "devOptional": true, - "peer": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "loupe": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", - "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", - "dev": true, - "requires": { - "get-func-name": "^2.0.0" - } - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "requires": { - "yallist": "^4.0.0" - } - }, - "magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "requires": { - "sourcemap-codec": "^1.4.8" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - } - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "devOptional": true, - "requires": { - "tmpl": "1.0.5" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "devOptional": true, - "peer": true - }, - "map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", - "devOptional": true, - "peer": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "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 - }, - "memoize-one": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz", - "integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==", - "devOptional": true, - "peer": true - }, - "merge-options": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", - "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", - "devOptional": true, - "requires": { - "is-plain-obj": "^2.1.0" - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "devOptional": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true - }, - "metro": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.70.3.tgz", - "integrity": "sha512-uEWS7xg8oTetQDABYNtsyeUjdLhH3KAvLFpaFFoJqUpOk2A3iygszdqmjobFl6W4zrvKDJS+XxdMR1roYvUhTw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/core": "^7.14.0", - "@babel/generator": "^7.14.0", - "@babel/parser": "^7.14.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.0.0", - "absolute-path": "^0.0.0", - "accepts": "^1.3.7", - "async": "^3.2.2", - "chalk": "^4.0.0", - "ci-info": "^2.0.0", - "connect": "^3.6.5", - "debug": "^2.2.0", - "denodeify": "^1.2.1", - "error-stack-parser": "^2.0.6", - "fs-extra": "^1.0.0", - "graceful-fs": "^4.2.4", - "hermes-parser": "0.6.0", - "image-size": "^0.6.0", - "invariant": "^2.2.4", - "jest-haste-map": "^27.3.1", - "jest-worker": "^27.2.0", - "lodash.throttle": "^4.1.1", - "metro-babel-transformer": "0.70.3", - "metro-cache": "0.70.3", - "metro-cache-key": "0.70.3", - "metro-config": "0.70.3", - "metro-core": "0.70.3", - "metro-hermes-compiler": "0.70.3", - "metro-inspector-proxy": "0.70.3", - "metro-minify-uglify": "0.70.3", - "metro-react-native-babel-preset": "0.70.3", - "metro-resolver": "0.70.3", - "metro-runtime": "0.70.3", - "metro-source-map": "0.70.3", - "metro-symbolicate": "0.70.3", - "metro-transform-plugins": "0.70.3", - "metro-transform-worker": "0.70.3", - "mime-types": "^2.1.27", - "node-fetch": "^2.2.0", - "nullthrows": "^1.1.1", - "rimraf": "^2.5.4", - "serialize-error": "^2.1.0", - "source-map": "^0.5.6", - "strip-ansi": "^6.0.0", - "temp": "0.8.3", - "throat": "^5.0.0", - "ws": "^7.5.1", - "yargs": "^15.3.1" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "devOptional": true, - "peer": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "devOptional": true, - "peer": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "devOptional": true, - "peer": true - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "devOptional": true, - "peer": true - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "devOptional": true, - "peer": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "devOptional": true, - "peer": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "devOptional": true, - "peer": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "fs-extra": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", - "integrity": "sha512-VerQV6vEKuhDWD2HGOybV6v5I73syoc/cXAbKlgTC7M/oFVEtklWlp9QH2Ijw3IaWDOQcMkldSPa7zXy79Z/UQ==", - "devOptional": true, - "peer": true, - "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^2.1.0", - "klaw": "^1.0.0" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "jest-haste-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", - "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", - "devOptional": true, - "peer": true, - "requires": { - "@jest/types": "^27.5.1", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^27.5.1", - "jest-serializer": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - } - }, - "jest-regex-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", - "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", - "devOptional": true, - "peer": true - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "devOptional": true, - "peer": true, - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - }, - "dependencies": { - "ci-info": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.4.0.tgz", - "integrity": "sha512-t5QdPT5jq3o262DOQ8zA6E1tlH2upmUc4Hlvrbx1pGYJuiiHl7O7rvVNI+l8HTVhd/q3Qc9vqimkNk5yiXsAug==", - "devOptional": true, - "peer": true - } - } - }, - "jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "devOptional": true, - "peer": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", - "devOptional": true, - "peer": true, - "requires": { - "graceful-fs": "^4.1.6" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "devOptional": true, - "peer": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "devOptional": true, - "peer": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true, - "peer": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "devOptional": true, - "peer": true, - "requires": { - "glob": "^7.1.3" - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "devOptional": true, - "peer": true, - "requires": {} - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "devOptional": true, - "peer": true - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "devOptional": true, - "peer": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "devOptional": true, - "peer": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "metro-babel-transformer": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.70.3.tgz", - "integrity": "sha512-bWhZRMn+mIOR/s3BDpFevWScz9sV8FGktVfMlF1eJBLoX24itHDbXvTktKBYi38PWIKcHedh6THSFpJogfuwNA==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/core": "^7.14.0", - "hermes-parser": "0.6.0", - "metro-source-map": "0.70.3", - "nullthrows": "^1.1.1" - } - }, - "metro-cache": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.70.3.tgz", - "integrity": "sha512-iCix/+z812fUqa6KlOxaTkY6LQQDoXIe/VljXkGIvpygSCmYyhjQpfQVZEVVPezFmUBYXNdabdQ6cYx6JX3yMg==", - "devOptional": true, - "peer": true, - "requires": { - "metro-core": "0.70.3", - "rimraf": "^2.5.4" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "devOptional": true, - "peer": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, - "metro-cache-key": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.70.3.tgz", - "integrity": "sha512-0zpw+IcpM3hmGd5sKMdxNv3sbOIUYnMUvx1/yaM6vNRReSPmOLX0bP8fYf3CGgk8NEreZ1OHbVsuw7bdKt40Mw==", - "devOptional": true, - "peer": true - }, - "metro-config": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.70.3.tgz", - "integrity": "sha512-SSCDjSTygoCgzoj61DdrBeJzZDRwQxUEfcgc6t6coxWSExXNR4mOngz0q4SAam49Bmjq9J2Jft6qUKnUTPrRgA==", - "devOptional": true, - "peer": true, - "requires": { - "cosmiconfig": "^5.0.5", - "jest-validate": "^26.5.2", - "metro": "0.70.3", - "metro-cache": "0.70.3", - "metro-core": "0.70.3", - "metro-runtime": "0.70.3" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "devOptional": true, - "peer": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "15.0.14", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", - "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", - "devOptional": true, - "peer": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "devOptional": true, - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "devOptional": true, - "peer": true - }, - "jest-validate": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", - "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", - "devOptional": true, - "peer": true, - "requires": { - "@jest/types": "^26.6.2", - "camelcase": "^6.0.0", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "leven": "^3.1.0", - "pretty-format": "^26.6.2" - } - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "devOptional": true, - "peer": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "metro-core": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.70.3.tgz", - "integrity": "sha512-NzfHB/w5R7yLaOeU1tzPTbBzCRsYSvpKJkLMP0yudszKZzIAZqNdjoEJ9GZ688Wi0ynZxcU0BxukXh4my80ZBw==", - "devOptional": true, - "peer": true, - "requires": { - "jest-haste-map": "^27.3.1", - "lodash.throttle": "^4.1.1", - "metro-resolver": "0.70.3" - }, - "dependencies": { - "@jest/types": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz", - "integrity": "sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==", - "devOptional": true, - "peer": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", - "devOptional": true, - "peer": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "devOptional": true, - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "jest-haste-map": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz", - "integrity": "sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==", - "devOptional": true, - "peer": true, - "requires": { - "@jest/types": "^27.5.1", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "fsevents": "^2.3.2", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^27.5.1", - "jest-serializer": "^27.5.1", - "jest-util": "^27.5.1", - "jest-worker": "^27.5.1", - "micromatch": "^4.0.4", - "walker": "^1.0.7" - } - }, - "jest-regex-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz", - "integrity": "sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==", - "devOptional": true, - "peer": true - }, - "jest-util": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz", - "integrity": "sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==", - "devOptional": true, - "peer": true, - "requires": { - "@jest/types": "^27.5.1", - "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" - } - }, - "jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "devOptional": true, - "peer": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - }, - "dependencies": { - "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "metro-hermes-compiler": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-hermes-compiler/-/metro-hermes-compiler-0.70.3.tgz", - "integrity": "sha512-W6WttLi4E72JL/NyteQ84uxYOFMibe0PUr9aBKuJxxfCq6QRnJKOVcNY0NLW0He2tneXGk+8ZsNz8c0flEvYqg==", - "devOptional": true, - "peer": true - }, - "metro-inspector-proxy": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-inspector-proxy/-/metro-inspector-proxy-0.70.3.tgz", - "integrity": "sha512-qQoNdPGrmyoJSWYkxSDpTaAI8xyqVdNDVVj9KRm1PG8niSuYmrCCFGLLFsMvkVYwsCWUGHoGBx0UoAzVp14ejw==", - "devOptional": true, - "peer": true, - "requires": { - "connect": "^3.6.5", - "debug": "^2.2.0", - "ws": "^7.5.1", - "yargs": "^15.3.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "devOptional": true, - "peer": true - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "devOptional": true, - "peer": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "devOptional": true, - "peer": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "devOptional": true, - "peer": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "devOptional": true, - "peer": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "devOptional": true, - "peer": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "devOptional": true, - "peer": true - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "devOptional": true, - "peer": true, - "requires": {} - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "devOptional": true, - "peer": true - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "devOptional": true, - "peer": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "devOptional": true, - "peer": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "metro-minify-uglify": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-minify-uglify/-/metro-minify-uglify-0.70.3.tgz", - "integrity": "sha512-oHyjV9WDqOlDE1FPtvs6tIjjeY/oP1PNUPYL1wqyYtqvjN+zzAOrcbsAAL1sv+WARaeiMsWkF2bwtNo+Hghoog==", - "devOptional": true, - "peer": true, - "requires": { - "uglify-es": "^3.1.9" - } - }, - "metro-react-native-babel-preset": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-preset/-/metro-react-native-babel-preset-0.70.3.tgz", - "integrity": "sha512-4Nxc1zEiHEu+GTdEMEsHnRgfaBkg8f/Td3+FcQ8NTSvs+xL3LBrQy6N07idWSQZHIdGFf+tTHvRfSIWLD8u8Tg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/core": "^7.14.0", - "@babel/plugin-proposal-async-generator-functions": "^7.0.0", - "@babel/plugin-proposal-class-properties": "^7.0.0", - "@babel/plugin-proposal-export-default-from": "^7.0.0", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-proposal-object-rest-spread": "^7.0.0", - "@babel/plugin-proposal-optional-catch-binding": "^7.0.0", - "@babel/plugin-proposal-optional-chaining": "^7.0.0", - "@babel/plugin-syntax-dynamic-import": "^7.0.0", - "@babel/plugin-syntax-export-default-from": "^7.0.0", - "@babel/plugin-syntax-flow": "^7.2.0", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.0.0", - "@babel/plugin-syntax-optional-chaining": "^7.0.0", - "@babel/plugin-transform-arrow-functions": "^7.0.0", - "@babel/plugin-transform-async-to-generator": "^7.0.0", - "@babel/plugin-transform-block-scoping": "^7.0.0", - "@babel/plugin-transform-classes": "^7.0.0", - "@babel/plugin-transform-computed-properties": "^7.0.0", - "@babel/plugin-transform-destructuring": "^7.0.0", - "@babel/plugin-transform-exponentiation-operator": "^7.0.0", - "@babel/plugin-transform-flow-strip-types": "^7.0.0", - "@babel/plugin-transform-function-name": "^7.0.0", - "@babel/plugin-transform-literals": "^7.0.0", - "@babel/plugin-transform-modules-commonjs": "^7.0.0", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.0.0", - "@babel/plugin-transform-parameters": "^7.0.0", - "@babel/plugin-transform-react-display-name": "^7.0.0", - "@babel/plugin-transform-react-jsx": "^7.0.0", - "@babel/plugin-transform-react-jsx-self": "^7.0.0", - "@babel/plugin-transform-react-jsx-source": "^7.0.0", - "@babel/plugin-transform-runtime": "^7.0.0", - "@babel/plugin-transform-shorthand-properties": "^7.0.0", - "@babel/plugin-transform-spread": "^7.0.0", - "@babel/plugin-transform-sticky-regex": "^7.0.0", - "@babel/plugin-transform-template-literals": "^7.0.0", - "@babel/plugin-transform-typescript": "^7.5.0", - "@babel/plugin-transform-unicode-regex": "^7.0.0", - "@babel/template": "^7.0.0", - "react-refresh": "^0.4.0" - } - }, - "metro-react-native-babel-transformer": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-react-native-babel-transformer/-/metro-react-native-babel-transformer-0.70.3.tgz", - "integrity": "sha512-WKBU6S/G50j9cfmFM4k4oRYprd8u3qjleD4so1E2zbTNILg+gYla7ZFGCAvi2G0ZcqS2XuGCR375c2hF6VVvwg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/core": "^7.14.0", - "babel-preset-fbjs": "^3.4.0", - "hermes-parser": "0.6.0", - "metro-babel-transformer": "0.70.3", - "metro-react-native-babel-preset": "0.70.3", - "metro-source-map": "0.70.3", - "nullthrows": "^1.1.1" - } - }, - "metro-resolver": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.70.3.tgz", - "integrity": "sha512-5Pc5S/Gs4RlLbziuIWtvtFd9GRoILlaRC8RZDVq5JZWcWHywKy/PjNmOBNhpyvtRlzpJfy/ssIfLhu8zINt1Mw==", - "devOptional": true, - "peer": true, - "requires": { - "absolute-path": "^0.0.0" - } - }, - "metro-runtime": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.70.3.tgz", - "integrity": "sha512-22xU7UdXZacniTIDZgN2EYtmfau2pPyh97Dcs+cWrLcJYgfMKjWBtesnDcUAQy3PHekDYvBdJZkoQUeskYTM+w==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/runtime": "^7.0.0" - } - }, - "metro-source-map": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.70.3.tgz", - "integrity": "sha512-zsYtZGrwRbbGEFHtmMqqeCH9K9aTGNVPsurMOWCUeQA3VGyVGXPGtLMC+CdAM9jLpUyg6jw2xh0esxi+tYH7Uw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/traverse": "^7.14.0", - "@babel/types": "^7.0.0", - "invariant": "^2.2.4", - "metro-symbolicate": "0.70.3", - "nullthrows": "^1.1.1", - "ob1": "0.70.3", - "source-map": "^0.5.6", - "vlq": "^1.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "devOptional": true, - "peer": true - } - } - }, - "metro-symbolicate": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.70.3.tgz", - "integrity": "sha512-JTYkF1dpeDUssQ84juE1ycnhHki2ylJBBdJE1JHtfu5oC+z1ElDbBdPHq90Uvt8HbRov/ZAnxvv7Zy6asS+WCA==", - "devOptional": true, - "peer": true, - "requires": { - "invariant": "^2.2.4", - "metro-source-map": "0.70.3", - "nullthrows": "^1.1.1", - "source-map": "^0.5.6", - "through2": "^2.0.1", - "vlq": "^1.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "devOptional": true, - "peer": true - } - } - }, - "metro-transform-plugins": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.70.3.tgz", - "integrity": "sha512-dQRIJoTkWZN2IVS2KzgS1hs7ZdHDX3fS3esfifPkqFAEwHiLctCf0EsPgIknp0AjMLvmGWfSLJigdRB/dc0ASw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/core": "^7.14.0", - "@babel/generator": "^7.14.0", - "@babel/template": "^7.0.0", - "@babel/traverse": "^7.14.0", - "nullthrows": "^1.1.1" - } - }, - "metro-transform-worker": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.70.3.tgz", - "integrity": "sha512-MtVVsnHhhBOp9GRLCdAb2mD1dTCsIzT4+m34KMRdBDCEbDIb90YafT5prpU8qbj5uKd0o2FOQdrJ5iy5zQilHw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/core": "^7.14.0", - "@babel/generator": "^7.14.0", - "@babel/parser": "^7.14.0", - "@babel/types": "^7.0.0", - "babel-preset-fbjs": "^3.4.0", - "metro": "0.70.3", - "metro-babel-transformer": "0.70.3", - "metro-cache": "0.70.3", - "metro-cache-key": "0.70.3", - "metro-hermes-compiler": "0.70.3", - "metro-source-map": "0.70.3", - "metro-transform-plugins": "0.70.3", - "nullthrows": "^1.1.1" - } - }, - "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", - "devOptional": true, - "requires": { - "braces": "^3.0.2", - "picomatch": "^2.3.1" - } - }, - "mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "devOptional": true - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "devOptional": true - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "devOptional": true, - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "devOptional": true - }, - "mimic-response": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", - "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==" - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "devOptional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "devOptional": true - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "devOptional": true, - "peer": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - } - }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "devOptional": true, - "requires": { - "minimist": "^1.2.6" - } - }, - "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", - "dev": true, - "requires": { - "browser-stdout": "1.3.1", - "commander": "2.15.1", - "debug": "3.1.0", - "diff": "3.5.0", - "escape-string-regexp": "1.0.5", - "glob": "7.1.2", - "growl": "1.10.5", - "he": "1.1.1", - "minimatch": "3.0.4", - "mkdirp": "0.5.1", - "supports-color": "5.4.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "mocha-lcov-reporter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/mocha-lcov-reporter/-/mocha-lcov-reporter-1.3.0.tgz", - "integrity": "sha512-/5zI2tW4lq/ft8MGpYQ1nIH6yePPtIzdGeUEwFMKfMRdLfAQ1QW2c68eEJop32tNdN5srHa/E2TzB+erm3YMYA==", - "dev": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "devOptional": true - }, - "murmurhash": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/murmurhash/-/murmurhash-2.0.1.tgz", - "integrity": "sha512-5vQEh3y+DG/lMPM0mCGPDnyV8chYg/g7rl6v3Gd8WMF9S429ox3Xk8qrk174kWhG767KQMqqxLD1WnGd77hiew==" - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "devOptional": true, - "peer": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, - "native-promise-only": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", - "integrity": "sha512-zkVhZUA3y8mbz652WrL5x0fB0ehrBkulWT3TomAQ9iDtyXZvzKeEA6GPxAItBYeNYl5yngKRX612qHOhvMkDeg==", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "devOptional": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "devOptional": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "devOptional": true, - "peer": true - }, - "nise": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", - "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", - "dev": true, - "requires": { - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "lolex": "^5.0.1", - "path-to-regexp": "^1.7.0" - }, - "dependencies": { - "lolex": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", - "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - } - } - }, - "nocache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/nocache/-/nocache-3.0.4.tgz", - "integrity": "sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==", - "devOptional": true, - "peer": true - }, - "nock": { - "version": "11.9.1", - "resolved": "https://registry.npmjs.org/nock/-/nock-11.9.1.tgz", - "integrity": "sha512-U5wPctaY4/ar2JJ5Jg4wJxlbBfayxgKbiAeGh+a1kk6Pwnc2ZEuKviLyDSG6t0uXl56q7AALIxoM6FJrBSsVXA==", - "dev": true, - "requires": { - "debug": "^4.1.0", - "json-stringify-safe": "^5.0.1", - "lodash": "^4.17.13", - "mkdirp": "^0.5.0", - "propagate": "^2.0.0" - } - }, - "node-dir": { - "version": "0.1.17", - "resolved": "https://registry.npmjs.org/node-dir/-/node-dir-0.1.17.tgz", - "integrity": "sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==", - "devOptional": true, - "peer": true, - "requires": { - "minimatch": "^3.0.2" - } - }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "devOptional": true, - "peer": true, - "requires": { - "whatwg-url": "^5.0.0" - }, - "dependencies": { - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "devOptional": true, - "peer": true - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "devOptional": true, - "peer": true - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "devOptional": true, - "peer": true, - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - } - } - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==", - "devOptional": true - }, - "node-preload": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", - "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", - "dev": true, - "requires": { - "process-on-spawn": "^1.0.0" - } - }, - "node-releases": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz", - "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg==", - "devOptional": true - }, - "node-stream-zip": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.15.0.tgz", - "integrity": "sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==", - "devOptional": true, - "peer": true - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "devOptional": true - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "requires": { - "path-key": "^3.0.0" - } - }, - "null-check": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", - "integrity": "sha512-j8ZNHg19TyIQOWCGeeQJBuu6xZYIEurf8M1Qsfd8mFrGEfIZytbw18YjKWg+LcO25NowXGZXZpKAx+Ui3TFfDw==", - "dev": true - }, - "nullthrows": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz", - "integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==", - "devOptional": true, - "peer": true - }, - "nwsapi": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.1.tgz", - "integrity": "sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg==", - "dev": true - }, - "nyc": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", - "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", - "dev": true, - "requires": { - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "caching-transform": "^4.0.0", - "convert-source-map": "^1.7.0", - "decamelize": "^1.2.0", - "find-cache-dir": "^3.2.0", - "find-up": "^4.1.0", - "foreground-child": "^2.0.0", - "get-package-type": "^0.1.0", - "glob": "^7.1.6", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-hook": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", - "istanbul-lib-processinfo": "^2.0.2", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "make-dir": "^3.0.0", - "node-preload": "^0.2.1", - "p-map": "^3.0.0", - "process-on-spawn": "^1.0.0", - "resolve-from": "^5.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "spawn-wrap": "^2.0.0", - "test-exclude": "^6.0.0", - "yargs": "^15.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "append-transform": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", - "dev": true, - "requires": { - "default-require-extensions": "^3.0.0" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "default-require-extensions": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.0.tgz", - "integrity": "sha512-ek6DpXq/SCpvjhpFsLFRVtIxJCRw6fUR42lYMVZuUMK7n8eMz4Uh5clckdBjEpLhn/gEBZo7hDJnJcwdKLKQjg==", - "dev": true, - "requires": { - "strip-bom": "^4.0.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "istanbul-lib-hook": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", - "dev": true, - "requires": { - "append-transform": "^2.0.0" - } - }, - "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, - "requires": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "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, - "requires": { - "p-try": "^2.0.0" - } - }, - "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, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "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 - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "ob1": { - "version": "0.70.3", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.70.3.tgz", - "integrity": "sha512-Vy9GGhuXgDRY01QA6kdhToPd8AkLdLpX9GjH5kpqluVqTu70mgOm7tpGoJDZGaNbr9nJlJgnipqHJQRPORixIQ==", - "devOptional": true, - "peer": true - }, - "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==", - "devOptional": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", - "devOptional": true, - "peer": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "devOptional": true, - "peer": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "devOptional": true, - "peer": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "devOptional": true, - "peer": true - } - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "devOptional": true, - "peer": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", - "devOptional": true, - "peer": true, - "requires": { - "isobject": "^3.0.0" - } - }, - "object.assign": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", - "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", - "devOptional": true, - "peer": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "has-symbols": "^1.0.3", - "object-keys": "^1.1.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "devOptional": true, - "peer": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "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==", - "devOptional": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "on-headers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", - "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", - "devOptional": true, - "peer": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "devOptional": true, - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "devOptional": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "open": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-6.4.0.tgz", - "integrity": "sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==", - "devOptional": true, - "peer": true, - "requires": { - "is-wsl": "^1.1.0" - } - }, - "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "requires": { - "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" - } - }, - "ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "devOptional": true, - "peer": true, - "requires": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "devOptional": true, - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "devOptional": true, - "peer": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "devOptional": true, - "peer": true - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "devOptional": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "devOptional": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "devOptional": true - }, - "package-hash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", - "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.15", - "hasha": "^5.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "requires": { - "callsites": "^3.0.0" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "parse5": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.0.0.tgz", - "integrity": "sha512-y/t8IXSPWTuRZqXc0ajH/UwDj4mnqLEbSttNbThcFhGrZuOyoyvNBO85PBp2jQa55wY9d07PBNjsK8ZP3K5U6g==", - "dev": true, - "requires": { - "entities": "^4.3.0" - } - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "devOptional": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", - "devOptional": true, - "peer": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "devOptional": true, - "peer": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "devOptional": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "devOptional": true - }, - "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "requires": { - "isarray": "0.0.1" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true - } - } - }, - "pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true - }, - "pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==", - "dev": true, - "requires": { - "through": "~2.3" - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "devOptional": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "devOptional": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "devOptional": true, - "peer": true - }, - "pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", - "devOptional": true - }, - "pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "requires": { - "find-up": "^4.0.0" - }, - "dependencies": { - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "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, - "requires": { - "p-try": "^2.0.0" - } - }, - "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, - "requires": { - "p-limit": "^2.2.0" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - } - } - }, - "plist": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/plist/-/plist-3.0.6.tgz", - "integrity": "sha512-WiIVYyrp8TD4w8yCvyeIr+lkmrGRd5u0VbRnU+tP/aRLxP/YadJUYOMZJ/6hIa3oUyVCsycXvtNRgd5XBJIbiA==", - "devOptional": true, - "peer": true, - "requires": { - "base64-js": "^1.5.1", - "xmlbuilder": "^15.1.1" - } - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", - "devOptional": true, - "peer": true - }, - "prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true - }, - "prettier": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz", - "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==", - "dev": true - }, - "prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "requires": { - "fast-diff": "^1.1.2" - } - }, - "pretty-format": { - "version": "28.1.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz", - "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==", - "dev": true, - "requires": { - "@jest/schemas": "^28.1.3", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true - } - } - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "devOptional": true, - "peer": true - }, - "process-on-spawn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.0.0.tgz", - "integrity": "sha512-1WsPDsUSMmZH5LeMLegqkPDrsGgsWwk1Exipy2hvB0o/F0ASzbpIctSCcZIK1ykJvtTJULEH+20WOFjMvGnCTg==", - "dev": true, - "requires": { - "fromentries": "^1.2.0" - } - }, - "promise": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/promise/-/promise-8.2.0.tgz", - "integrity": "sha512-+CMAlLHqwRYwBMXKCP+o8ns7DN+xHDUiI+0nArsiJ9y+kJVPLFxEaSw6Ha9s9H0tftxg2Yzl25wqj9G7m5wLZg==", - "devOptional": true, - "peer": true, - "requires": { - "asap": "~2.0.6" - } - }, - "promise-polyfill": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.0.tgz", - "integrity": "sha512-OzSf6gcCUQ01byV4BgwyUCswlaQQ6gzXc23aLQWhicvfX9kfsUiUhgt3CCQej8jDnl8/PhGF31JdHX2/MzF3WA==", - "dev": true - }, - "prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "devOptional": true, - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, - "propagate": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz", - "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==", - "dev": true - }, - "ps-tree": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", - "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", - "dev": true, - "requires": { - "event-stream": "=3.3.4" - } - }, - "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "devOptional": true, - "peer": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", - "dev": true - }, - "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 - }, - "qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true - }, - "querystringify": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", - "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", - "dev": true - }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "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==", - "devOptional": true - }, - "raw-body": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz", - "integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==", - "dev": true, - "requires": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } - }, - "react": { - "version": "18.0.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.0.0.tgz", - "integrity": "sha512-x+VL6wbT4JRVPm7EGxXhZ8w8LTROaxPXOqhlGyVSrv0sB1jkyFGgXxJ8LVoPRLvPR6/CIZGFmfzqUa2NYeMr2A==", - "devOptional": true, - "peer": true, - "requires": { - "loose-envify": "^1.1.0" - } - }, - "react-devtools-core": { - "version": "4.24.0", - "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-4.24.0.tgz", - "integrity": "sha512-Rw7FzYOOzcfyUPaAm9P3g0tFdGqGq2LLiAI+wjYcp6CsF3DeeMrRS3HZAho4s273C29G/DJhx0e8BpRE/QZNGg==", - "devOptional": true, - "peer": true, - "requires": { - "shell-quote": "^1.6.1", - "ws": "^7" - }, - "dependencies": { - "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", - "devOptional": true, - "peer": true, - "requires": {} - } - } - }, - "react-is": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", - "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==", - "devOptional": true - }, - "react-native": { - "version": "0.69.5", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.69.5.tgz", - "integrity": "sha512-4Psrj1nDMLQjBXVH8n3UikzOHQc8+sa6NbxZQR0XKtpx8uC3HiJBgX+/FIum/RWxfi5J/Dt/+A2gLGmq2Hps8g==", - "devOptional": true, - "peer": true, - "requires": { - "@jest/create-cache-key-function": "^27.0.1", - "@react-native-community/cli": "^8.0.4", - "@react-native-community/cli-platform-android": "^8.0.4", - "@react-native-community/cli-platform-ios": "^8.0.4", - "@react-native/assets": "1.0.0", - "@react-native/normalize-color": "2.0.0", - "@react-native/polyfills": "2.0.0", - "abort-controller": "^3.0.0", - "anser": "^1.4.9", - "base64-js": "^1.1.2", - "event-target-shim": "^5.0.1", - "hermes-engine": "~0.11.0", - "invariant": "^2.2.4", - "jsc-android": "^250230.2.1", - "memoize-one": "^5.0.0", - "metro-react-native-babel-transformer": "0.70.3", - "metro-runtime": "0.70.3", - "metro-source-map": "0.70.3", - "mkdirp": "^0.5.1", - "nullthrows": "^1.1.1", - "pretty-format": "^26.5.2", - "promise": "^8.0.3", - "react-devtools-core": "4.24.0", - "react-native-codegen": "^0.69.2", - "react-native-gradle-plugin": "^0.0.7", - "react-refresh": "^0.4.0", - "react-shallow-renderer": "16.15.0", - "regenerator-runtime": "^0.13.2", - "scheduler": "^0.21.0", - "stacktrace-parser": "^0.1.3", - "use-sync-external-store": "^1.0.0", - "whatwg-fetch": "^3.0.0", - "ws": "^6.1.4" - }, - "dependencies": { - "@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "devOptional": true, - "peer": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - } - }, - "@types/yargs": { - "version": "15.0.14", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.14.tgz", - "integrity": "sha512-yEJzHoxf6SyQGhBhIYGXQDSCkJjB6HohDShto7m8vaKg9Yp0Yn8+71J9eakh2bnPg6BfsH9PRMhiRTZnd4eXGQ==", - "devOptional": true, - "peer": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "devOptional": true, - "peer": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "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==", - "devOptional": true, - "peer": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "devOptional": true, - "peer": true - }, - "pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "devOptional": true, - "peer": true, - "requires": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - } - }, - "react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "devOptional": true, - "peer": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "ws": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.2.tgz", - "integrity": "sha512-zmhltoSR8u1cnDsD43TX59mzoMZsLKqUweyYBAIvTngR3shc0W6aOZylZmq/7hqyVxPdi+5Ud2QInblgyE72fw==", - "devOptional": true, - "peer": true, - "requires": { - "async-limiter": "~1.0.0" - } - } - } - }, - "react-native-codegen": { - "version": "0.69.2", - "resolved": "https://registry.npmjs.org/react-native-codegen/-/react-native-codegen-0.69.2.tgz", - "integrity": "sha512-yPcgMHD4mqLbckqnWjFBaxomDnBREfRjDi2G/WxNyPBQLD+PXUEmZTkDx6QoOXN+Bl2SkpnNOSsLE2+/RUHoPw==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/parser": "^7.14.0", - "flow-parser": "^0.121.0", - "jscodeshift": "^0.13.1", - "nullthrows": "^1.1.1" - } - }, - "react-native-gradle-plugin": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/react-native-gradle-plugin/-/react-native-gradle-plugin-0.0.7.tgz", - "integrity": "sha512-+4JpbIx42zGTONhBTIXSyfyHICHC29VTvhkkoUOJAh/XHPEixpuBduYgf6Y4y9wsN1ARlQhBBoptTvXvAFQf5g==", - "devOptional": true, - "peer": true - }, - "react-refresh": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.4.3.tgz", - "integrity": "sha512-Hwln1VNuGl/6bVwnd0Xdn1e84gT/8T9aYNL+HAKDArLCS7LWjwr7StE30IEYbIkx0Vi3vs+coQxe+SQDbGbbpA==", - "devOptional": true, - "peer": true - }, - "react-shallow-renderer": { - "version": "16.15.0", - "resolved": "https://registry.npmjs.org/react-shallow-renderer/-/react-shallow-renderer-16.15.0.tgz", - "integrity": "sha512-oScf2FqQ9LFVQgA73vr86xl2NaOIX73rh+YFqcOp68CWj56tSfgtGKrEbyhCj0rSijyG9M1CYprTh39fBi5hzA==", - "devOptional": true, - "peer": true, - "requires": { - "object-assign": "^4.1.1", - "react-is": "^16.12.0 || ^17.0.0 || ^18.0.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "devOptional": true, - "peer": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "readline": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", - "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==", - "devOptional": true, - "peer": true - }, - "recast": { - "version": "0.20.5", - "resolved": "https://registry.npmjs.org/recast/-/recast-0.20.5.tgz", - "integrity": "sha512-E5qICoPoNL4yU0H0NoBDntNB0Q5oMSNh9usFctYniLBluTthi3RsQVBXIJNbApOlvSwW/RGxIuokPcAc59J5fQ==", - "devOptional": true, - "peer": true, - "requires": { - "ast-types": "0.14.2", - "esprima": "~4.0.0", - "source-map": "~0.6.1", - "tslib": "^2.0.1" - } - }, - "regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "devOptional": true, - "peer": true - }, - "regenerate-unicode-properties": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz", - "integrity": "sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==", - "devOptional": true, - "peer": true, - "requires": { - "regenerate": "^1.4.2" - } - }, - "regenerator-runtime": { - "version": "0.13.9", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz", - "integrity": "sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==", - "peer": true - }, - "regenerator-transform": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.15.0.tgz", - "integrity": "sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==", - "devOptional": true, - "peer": true, - "requires": { - "@babel/runtime": "^7.8.4" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "devOptional": true, - "peer": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "regexpp": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", - "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", - "dev": true - }, - "regexpu-core": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.2.1.tgz", - "integrity": "sha512-HrnlNtpvqP1Xkb28tMhBUO2EbyUHdQlsnlAhzWcwHy8WJR53UWr7/MAvqrsQKMbV4qdpv03oTMG8iIhfsPFktQ==", - "devOptional": true, - "peer": true, - "requires": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.1.0", - "regjsgen": "^0.7.1", - "regjsparser": "^0.9.1", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.0.0" - } - }, - "regjsgen": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.7.1.tgz", - "integrity": "sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==", - "devOptional": true, - "peer": true - }, - "regjsparser": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", - "integrity": "sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ==", - "devOptional": true, - "peer": true, - "requires": { - "jsesc": "~0.5.0" - } - }, - "release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", - "dev": true, - "requires": { - "es6-error": "^4.0.1" - } - }, - "repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "devOptional": true, - "peer": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "devOptional": true, - "peer": true - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true - } - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "devOptional": true - }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "devOptional": true - }, - "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 - }, - "resolve": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz", - "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==", - "devOptional": true, - "requires": { - "is-core-module": "^2.9.0", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - } - }, - "resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "requires": { - "resolve-from": "^5.0.0" - }, - "dependencies": { - "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 - } - } - }, - "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 - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", - "devOptional": true, - "peer": true - }, - "resolve.exports": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-1.1.0.tgz", - "integrity": "sha512-J1l+Zxxp4XK3LUDZ9m60LRJF/mAe4z6a4xyabPHk7pvK5t35dACV32iIjJDFeWZFfZlO29w6SZ67knR0tHzJtQ==", - "dev": true - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "devOptional": true, - "peer": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "devOptional": true, - "peer": true - }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, - "rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true - }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rollup": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.2.0.tgz", - "integrity": "sha512-iAu/j9/WJ0i+zT0sAMuQnsEbmOKzdQ4Yxu5rbPs9aUCyqveI1Kw3H4Fi9NWfCOpb8luEySD2lDyFWL9CrLE8iw==", - "dev": true, - "requires": { - "fsevents": "~2.1.2" - }, - "dependencies": { - "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, - "optional": true - } - } - }, - "rollup-plugin-terser": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-5.3.1.tgz", - "integrity": "sha512-1pkwkervMJQGFYvM9nscrUoncPwiKR/K+bHdjv6PFgRo3cgPHoRT83y2Aa3GvINj4539S15t/tpFPb775TDs6w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.5.5", - "jest-worker": "^24.9.0", - "rollup-pluginutils": "^2.8.2", - "serialize-javascript": "^4.0.0", - "terser": "^4.6.2" - }, - "dependencies": { - "jest-worker": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.9.0.tgz", - "integrity": "sha512-51PE4haMSXcHohnSMdM42anbvZANYTqMrr52tVKPqqsPJMzoP6FYYDVqahX/HrAoKEKz3uUPzSvKs9A3qR4iVw==", - "dev": true, - "requires": { - "merge-stream": "^2.0.0", - "supports-color": "^6.1.0" - } - }, - "supports-color": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", - "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "rollup-plugin-typescript2": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/rollup-plugin-typescript2/-/rollup-plugin-typescript2-0.27.3.tgz", - "integrity": "sha512-gmYPIFmALj9D3Ga1ZbTZAKTXq1JKlTQBtj299DXhqYz9cL3g/AQfUvbb2UhH+Nf++cCq941W2Mv7UcrcgLzJJg==", - "dev": true, - "requires": { - "@rollup/pluginutils": "^3.1.0", - "find-cache-dir": "^3.3.1", - "fs-extra": "8.1.0", - "resolve": "1.17.0", - "tslib": "2.0.1" - }, - "dependencies": { - "resolve": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", - "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "tslib": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.1.tgz", - "integrity": "sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ==", - "dev": true - } - } - }, - "rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", - "dev": true, - "requires": { - "estree-walker": "^0.6.1" - }, - "dependencies": { - "estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", - "dev": true - } - } - }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "devOptional": true - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", - "devOptional": true, - "peer": true, - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "samsam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", - "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", - "dev": true - }, - "saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "requires": { - "xmlchars": "^2.2.0" - } - }, - "scheduler": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.21.0.tgz", - "integrity": "sha512-1r87x5fz9MXqswA2ERLo0EbOAU74DpIUO090gIasYTqlVoJeMcl+Z1Rg7WHz+qtPujhS/hGIt9kxZOYBV3faRQ==", - "devOptional": true, - "peer": true, - "requires": { - "loose-envify": "^1.1.0" - } - }, - "schema-utils": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", - "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", - "dev": true, - "requires": { - "@types/json-schema": "^7.0.8", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - } - }, - "semver": { - "version": "7.3.7", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", - "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", - "dev": true, - "requires": { - "lru-cache": "^6.0.0" - } - }, - "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "devOptional": true, - "peer": true, - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - } - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "devOptional": true, - "peer": true - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "devOptional": true, - "peer": true - } - } - }, - "serialize-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz", - "integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==", - "devOptional": true, - "peer": true - }, - "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", - "devOptional": true, - "peer": true, - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.18.0" - } - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "devOptional": true - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "devOptional": true, - "peer": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true - } - } - }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "devOptional": true - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^6.0.2" - } - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "shell-quote": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz", - "integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==", - "devOptional": true, - "peer": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "devOptional": true - }, - "sinon": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-2.4.1.tgz", - "integrity": "sha512-vFTrO9Wt0ECffDYIPSP/E5bBugt0UjcBQOfQUMh66xzkyPEnhl/vM2LRZi2ajuTdkH07sA6DzrM6KvdvGIH8xw==", - "dev": true, - "requires": { - "diff": "^3.1.0", - "formatio": "1.2.0", - "lolex": "^1.6.0", - "native-promise-only": "^0.8.1", - "path-to-regexp": "^1.7.0", - "samsam": "^1.1.3", - "text-encoding": "0.6.4", - "type-detect": "^4.0.0" - } - }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "devOptional": true - }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "devOptional": true - }, - "slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "devOptional": true, - "peer": true, - "requires": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "devOptional": true, - "peer": true - } - } - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "devOptional": true, - "peer": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "devOptional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "devOptional": true, - "peer": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "devOptional": true, - "peer": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "devOptional": true, - "peer": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "devOptional": true, - "peer": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "devOptional": true, - "peer": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "devOptional": true, - "peer": true - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "devOptional": true, - "peer": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "devOptional": true, - "peer": true, - "requires": { - "is-descriptor": "^1.0.0" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "socket.io": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.5.1.tgz", - "integrity": "sha512-0y9pnIso5a9i+lJmsCdtmTTgJFFSvNQKDnPQRz28mGNnxbmqYg2QPtJTLFxhymFZhAIn50eHAKzJeiNaKr+yUQ==", - "dev": true, - "requires": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "debug": "~4.3.2", - "engine.io": "~6.2.0", - "socket.io-adapter": "~2.4.0", - "socket.io-parser": "~4.0.4" - } - }, - "socket.io-adapter": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.4.0.tgz", - "integrity": "sha512-W4N+o69rkMEGVuk2D/cvca3uYsvGlMwsySWV447y99gUPghxq42BxqLNMndb+a1mm/5/7NeXVQS7RLa2XyXvYg==", - "dev": true - }, - "socket.io-parser": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.5.tgz", - "integrity": "sha512-sNjbT9dX63nqUFIOv95tTVm6elyIU4RvB1m8dOeZt+IgWwcWklFDOdmGcfo3zSiRsnR/3pJkjY5lfoGqEe4Eig==", - "dev": true, - "requires": { - "@types/component-emitter": "^1.2.10", - "component-emitter": "~1.3.0", - "debug": "~4.3.1" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "devOptional": true - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "devOptional": true, - "peer": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "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==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "devOptional": true, - "peer": true - }, - "sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "dev": true - }, - "spawn-wrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", - "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", - "dev": true, - "requires": { - "foreground-child": "^2.0.0", - "is-windows": "^1.0.2", - "make-dir": "^3.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "which": "^2.0.1" - }, - "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==", - "dev": true, - "requires": { - "through": "2" - } - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "devOptional": true, - "peer": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "devOptional": true - }, - "sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "stack-utils": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "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==", - "dev": true - } - } - }, - "stackframe": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", - "integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==", - "devOptional": true, - "peer": true - }, - "stacktrace-parser": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.10.tgz", - "integrity": "sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==", - "devOptional": true, - "peer": true, - "requires": { - "type-fest": "^0.7.1" - }, - "dependencies": { - "type-fest": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz", - "integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==", - "devOptional": true, - "peer": true - } - } - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", - "devOptional": true, - "peer": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "devOptional": true, - "peer": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "devOptional": true, - "peer": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "devOptional": true, - "peer": true - } - } - }, - "statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "devOptional": true - }, - "stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==", - "dev": true, - "requires": { - "duplexer": "~0.1.1" - } - }, - "streamroller": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.1.2.tgz", - "integrity": "sha512-wZswqzbgGGsXYIrBYhOE0yP+nQ6XRk7xDcYwuQAGTYXdyAUmvgVFE0YU1g5pvQT0m7GBaQfYcSnlHbapuK0H0A==", - "dev": true, - "requires": { - "date-format": "^4.0.13", - "debug": "^4.3.4", - "fs-extra": "^8.1.0" - } - }, - "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==", - "devOptional": true, - "peer": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "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, - "requires": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - } - }, - "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==", - "devOptional": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "devOptional": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "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==", - "dev": true - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", - "devOptional": true, - "peer": true - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "sudo-prompt": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/sudo-prompt/-/sudo-prompt-9.2.1.tgz", - "integrity": "sha512-Mu7R0g4ig9TUuGSxJavny5Rv0egCEtpZRNMrZaYS1vxkiIxGiGUwoezU3LazIQ+KE04hTrTfNPgxU5gzi7F5Pw==", - "devOptional": true, - "peer": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "devOptional": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "supports-hyperlinks": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz", - "integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==", - "dev": true, - "requires": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "dependencies": { - "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 - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "devOptional": true - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true - }, - "temp": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/temp/-/temp-0.8.3.tgz", - "integrity": "sha512-jtnWJs6B1cZlHs9wPG7BrowKxZw/rf6+UpGAkr8AaYmiTyTO7zQlLoST8zx/8TcUPnZmeBoB+H8ARuHZaSijVw==", - "devOptional": true, - "peer": true, - "requires": { - "os-tmpdir": "^1.0.0", - "rimraf": "~2.2.6" - }, - "dependencies": { - "rimraf": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", - "integrity": "sha512-R5KMKHnPAQaZMqLOsyuyUmcIjSeDm+73eoqQpaXA7AZ22BL+6C+1mcUscgOsNd8WVlJuvlgAPsegcx7pjlV0Dg==", - "devOptional": true, - "peer": true - } - } - }, - "temp-fs": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/temp-fs/-/temp-fs-0.9.9.tgz", - "integrity": "sha512-WfecDCR1xC9b0nsrzSaxPf3ZuWeWLUWblW4vlDQAa1biQaKHiImHnJfeQocQe/hXKMcolRzgkcVX/7kK4zoWbw==", - "dev": true, - "requires": { - "rimraf": "~2.5.2" - }, - "dependencies": { - "rimraf": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", - "integrity": "sha512-Lw7SHMjssciQb/rRz7JyPIy9+bbUshEucPoLRvWqy09vC5zQixl8Uet+Zl+SROBB/JMWHJRdCk1qdxNWHNMvlQ==", - "dev": true, - "requires": { - "glob": "^7.0.5" - } - } - } - }, - "terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "requires": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - } - }, - "terser": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.1.tgz", - "integrity": "sha512-4GnLC0x667eJG0ewJTa6z/yXrbLGv80D9Ru6HIpCQmO+Q4PfEtBFi0ObSckqwL6VyQv/7ENJieXHo2ANmdQwgw==", - "dev": true, - "requires": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - } - } - }, - "terser-webpack-plugin": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.3.tgz", - "integrity": "sha512-Fx60G5HNYknNTNQnzQ1VePRuu89ZVYWfjRAeT5rITuCY/1b08s49e5kSQwHDirKZWuoKOBRFS98EUUoZ9kLEwQ==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.7", - "jest-worker": "^27.4.5", - "schema-utils": "^3.1.1", - "serialize-javascript": "^6.0.0", - "terser": "^5.7.2" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "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 - }, - "jest-worker": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", - "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", - "dev": true, - "requires": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" - } - }, - "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "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, - "requires": { - "has-flag": "^4.0.0" - } - }, - "terser": { - "version": "5.14.2", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.14.2.tgz", - "integrity": "sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==", - "dev": true, - "requires": { - "@jridgewell/source-map": "^0.3.2", - "acorn": "^8.5.0", - "commander": "^2.20.0", - "source-map-support": "~0.5.20" - } - } - } - }, - "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==", - "dev": true, - "requires": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - } - }, - "text-encoding": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", - "integrity": "sha512-hJnc6Qg3dWoOMkqP53F0dzRIgtmsAge09kxUIqGrEUS4qr5rWLckGYaQAVr+opBrIMRErGgy6f5aPnyPpyGRfg==", - "dev": true - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, - "throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "devOptional": true, - "peer": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "devOptional": true, - "peer": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "devOptional": true, - "peer": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "devOptional": true, - "peer": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "devOptional": true, - "peer": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "devOptional": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "devOptional": true, - "peer": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", - "devOptional": true, - "peer": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "devOptional": true, - "peer": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "devOptional": true, - "peer": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "devOptional": true - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tr46": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", - "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", - "dev": true, - "requires": { - "punycode": "^2.1.1" - } - }, - "ts-jest": { - "version": "28.0.8", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-28.0.8.tgz", - "integrity": "sha512-5FaG0lXmRPzApix8oFG8RKjAz4ehtm8yMKOTy5HX3fY6W8kmvOrmcY0hKDElW52FJov+clhUbrKAqofnj4mXTg==", - "dev": true, - "requires": { - "bs-logger": "0.x", - "fast-json-stable-stringify": "2.x", - "jest-util": "^28.0.0", - "json5": "^2.2.1", - "lodash.memoize": "4.x", - "make-error": "1.x", - "semver": "7.x", - "yargs-parser": "^21.0.1" - }, - "dependencies": { - "yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true - } - } - }, - "ts-loader": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.3.1.tgz", - "integrity": "sha512-OkyShkcZTsTwyS3Kt7a4rsT/t2qvEVQuKCTg4LJmpj9fhFR7ukGdZwV6Qq3tRUkqcXtfGpPR7+hFKHCG/0d3Lw==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "enhanced-resolve": "^5.0.0", - "micromatch": "^4.0.0", - "semver": "^7.3.4" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - }, - "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 - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "ts-mockito": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/ts-mockito/-/ts-mockito-2.6.1.tgz", - "integrity": "sha512-qU9m/oEBQrKq5hwfbJ7MgmVN5Gu6lFnIGWvpxSjrqq6YYEVv+RwVFWySbZMBgazsWqv6ctAyVBpo9TmAxnOEKw==", - "dev": true, - "requires": { - "lodash": "^4.17.5" - } - }, - "ts-node": { - "version": "8.10.2", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.10.2.tgz", - "integrity": "sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA==", - "dev": true, - "requires": { - "arg": "^4.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "source-map-support": "^0.5.17", - "yn": "3.1.1" - }, - "dependencies": { - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - } - } - }, - "tslib": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz", - "integrity": "sha512-d6xOpEDfsi2CZVlPQzGeux8XMwLT9hssAsaPYExaQMuYskwb+x1x7J371tWlbBdWHroy99KnVB6qIkUbs5X3UQ==", - "devOptional": true - }, - "tsutils": { - "version": "3.21.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", - "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - } - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true - }, - "type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "requires": { - "prelude-ls": "^1.2.1" - } - }, - "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 - }, - "type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true - }, - "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, - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - } - }, - "typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "requires": { - "is-typedarray": "^1.0.0" - } - }, - "typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", - "dev": true - }, - "ua-parser-js": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.31.tgz", - "integrity": "sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==", - "dev": true - }, - "uglify-es": { - "version": "3.3.9", - "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.3.9.tgz", - "integrity": "sha512-r+MU0rfv4L/0eeW3xZrd16t4NZfK8Ld4SWVglYBb7ez5uXFWHuVRs6xCTrf1yirs9a4j4Y27nn7SRfO6v67XsQ==", - "devOptional": true, - "peer": true, - "requires": { - "commander": "~2.13.0", - "source-map": "~0.6.1" - }, - "dependencies": { - "commander": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.13.0.tgz", - "integrity": "sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA==", - "devOptional": true, - "peer": true - } - } - }, - "unicode-canonical-property-names-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", - "integrity": "sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==", - "devOptional": true, - "peer": true - }, - "unicode-match-property-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", - "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", - "devOptional": true, - "peer": true, - "requires": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - } - }, - "unicode-match-property-value-ecmascript": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz", - "integrity": "sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==", - "devOptional": true, - "peer": true - }, - "unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", - "devOptional": true, - "peer": true - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "devOptional": true, - "peer": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "devOptional": true, - "peer": true - } - } - }, - "universalify": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", - "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", - "devOptional": true - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "devOptional": true - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", - "devOptional": true, - "peer": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", - "devOptional": true, - "peer": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", - "devOptional": true, - "peer": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", - "devOptional": true, - "peer": true - } - } - }, - "update-browserslist-db": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.9.tgz", - "integrity": "sha512-/xsqn21EGVdXI3EXSum1Yckj3ZVZugqyOZQ/CxYPBD/R+ko9NSUScf8tFF4dOKY+2pvSSJA/S+5B8s4Zr4kyvg==", - "devOptional": true, - "requires": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", - "devOptional": true, - "peer": true - }, - "url-parse": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", - "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", - "dev": true, - "requires": { - "querystringify": "^2.1.1", - "requires-port": "^1.0.0" - } - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "devOptional": true, - "peer": true - }, - "use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "devOptional": true, - "peer": true, - "requires": {} - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "devOptional": true, - "peer": true - }, - "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==", - "devOptional": true - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - }, - "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 - }, - "v8-to-istanbul": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.0.1.tgz", - "integrity": "sha512-74Y4LqY74kLE6IFyIjPtkSTWzUZmj8tdHT9Ii/26dvQ6K9Dl2NbEfj0XgU2sHCtKgt5VupqhlO/5aWuqS+IY1w==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "^0.3.12", - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" - } - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "devOptional": true - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "vlq": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz", - "integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==", - "devOptional": true, - "peer": true - }, - "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 - }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "w3c-xmlserializer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", - "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", - "dev": true, - "requires": { - "xml-name-validator": "^4.0.0" - } - }, - "walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "devOptional": true, - "requires": { - "makeerror": "1.0.12" - } - }, - "watchpack": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", - "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", - "dev": true, - "requires": { - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.1.2" - } - }, - "wcwidth": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", - "devOptional": true, - "peer": true, - "requires": { - "defaults": "^1.0.3" - } - }, - "webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "dev": true - }, - "webpack": { - "version": "5.74.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.74.0.tgz", - "integrity": "sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==", - "dev": true, - "requires": { - "@types/eslint-scope": "^3.7.3", - "@types/estree": "^0.0.51", - "@webassemblyjs/ast": "1.11.1", - "@webassemblyjs/wasm-edit": "1.11.1", - "@webassemblyjs/wasm-parser": "1.11.1", - "acorn": "^8.7.1", - "acorn-import-assertions": "^1.7.6", - "browserslist": "^4.14.5", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.10.0", - "es-module-lexer": "^0.9.0", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.9", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.1.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.1.3", - "watchpack": "^2.4.0", - "webpack-sources": "^3.2.3" - }, - "dependencies": { - "@types/estree": { - "version": "0.0.51", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", - "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", - "dev": true - } - } - }, - "webpack-merge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", - "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", - "dev": true, - "requires": { - "lodash": "^4.17.15" - } - }, - "webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", - "dev": true - }, - "whatwg-encoding": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", - "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", - "dev": true, - "requires": { - "iconv-lite": "0.6.3" - }, - "dependencies": { - "iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "dev": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - } - } - } - }, - "whatwg-fetch": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", - "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==", - "devOptional": true, - "peer": true - }, - "whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", - "dev": true - }, - "whatwg-url": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", - "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", - "dev": true, - "requires": { - "tr46": "^3.0.0", - "webidl-conversions": "^7.0.0" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "devOptional": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", - "devOptional": true - }, - "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 - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "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, - "requires": { - "color-name": "~1.1.4" - } - }, - "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 - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "devOptional": true - }, - "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==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" - } - }, - "ws": { - "version": "8.8.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz", - "integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==", - "dev": true, - "requires": {} - }, - "xml-name-validator": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", - "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", - "dev": true - }, - "xmlbuilder": { - "version": "15.1.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", - "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==", - "devOptional": true, - "peer": true - }, - "xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "devOptional": true, - "peer": true - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "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" - } - }, - "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "devOptional": true - } - } -} diff --git a/packages/optimizely-sdk/package.json b/packages/optimizely-sdk/package.json deleted file mode 100644 index 46e836aec..000000000 --- a/packages/optimizely-sdk/package.json +++ /dev/null @@ -1,121 +0,0 @@ -{ - "name": "@optimizely/optimizely-sdk", - "version": "4.9.2", - "description": "JavaScript SDK for Optimizely X Full Stack", - "module": "dist/optimizely.browser.es.js", - "main": "dist/optimizely.node.min.js", - "browser": "dist/optimizely.browser.min.js", - "react-native": "dist/optimizely.react_native.min.js", - "typings": "dist/index.browser.d.ts", - "scripts": { - "clean": "rm -rf dist", - "lint": "tsc --noEmit && eslint 'lib/**/*.js' 'lib/**/*.ts'", - "test": "TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' mocha -r ts-node/register -r lib/tests/exit_on_unhandled_rejection.js 'lib/**/*.tests.ts' 'lib/**/*.tests.js' && jest", - "posttest": "npm run lint", - "test-ci": "npm run test-xbrowser && npm run test-umdbrowser", - "test-xbrowser": "karma start karma.bs.conf.js --single-run", - "test-umdbrowser": "npm run build-browser-umd && karma start karma.umd.conf.js --single-run", - "test-karma-local": "karma start karma.local_chrome.bs.conf.js && npm run build-browser-umd && karma start karma.local_chrome.umd.conf.js", - "prebuild": "npm run clean", - "build": "rollup -c && cp dist/index.lite.d.ts dist/optimizely.lite.es.d.ts && cp dist/index.lite.d.ts dist/optimizely.lite.es.min.d.ts && cp dist/index.lite.d.ts dist/optimizely.lite.min.d.ts", - "build-browser-umd": "rollup -c --config-umd", - "coveralls": "nyc --reporter=lcov npm test", - "prepare": "npm run build", - "prepublishOnly": "npm test && npm run test-ci" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/optimizely/javascript-sdk.git", - "directory": "packages/optimizely-sdk" - }, - "license": "Apache-2.0", - "engines": { - "node": ">=14.0.0" - }, - "keywords": [ - "optimizely" - ], - "bugs": { - "url": "https://github.com/optimizely/javascript-sdk/issues" - }, - "homepage": "https://github.com/optimizely/javascript-sdk/tree/master/packages/optimizely-sdk", - "dependencies": { - "@optimizely/js-sdk-datafile-manager": "^0.9.5", - "decompress-response": "^4.2.1", - "json-schema": "^0.4.0", - "murmurhash": "^2.0.1", - "uuid": "^8.3.2" - }, - "devDependencies": { - "@react-native-async-storage/async-storage": "^1.2.0", - "@react-native-community/netinfo": "^5.9.10", - "@rollup/plugin-commonjs": "^11.0.2", - "@rollup/plugin-node-resolve": "^7.1.1", - "@types/chai": "^4.2.11", - "@types/jest": "^23.3.14", - "@types/mocha": "^5.2.7", - "@types/nise": "^1.4.0", - "@types/node": "^18.7.18", - "@types/uuid": "^3.4.4", - "@typescript-eslint/eslint-plugin": "^5.33.0", - "@typescript-eslint/parser": "^5.33.0", - "bluebird": "^3.4.6", - "chai": "^4.2.0", - "coveralls": "^3.0.2", - "eslint": "^8.21.0", - "eslint-config-prettier": "^6.10.0", - "eslint-plugin-prettier": "^3.1.2", - "jest": "^28.1.3", - "jest-environment-jsdom": "^29.0.0", - "jest-localstorage-mock": "^2.4.22", - "json-loader": "^0.5.4", - "karma": "^6.4.0", - "karma-browserstack-launcher": "^1.5.1", - "karma-chai": "^0.1.0", - "karma-chrome-launcher": "^2.1.1", - "karma-mocha": "^1.3.0", - "karma-webpack": "^5.0.0", - "lodash": "^4.17.11", - "mocha": "^5.2.0", - "mocha-lcov-reporter": "^1.3.0", - "nise": "^1.4.10", - "nock": "11.9.1", - "nyc": "^15.0.1", - "prettier": "^1.19.1", - "promise-polyfill": "8.1.0", - "rollup": "2.2.0", - "rollup-plugin-terser": "^5.3.0", - "rollup-plugin-typescript2": "^0.27.1", - "sinon": "^2.3.1", - "ts-jest": "^28.0.8", - "ts-loader": "^9.3.1", - "ts-mockito": "^2.6.1", - "ts-node": "^8.10.2", - "tslib": "^2.4.0", - "typescript": "^4.7.4", - "webpack": "^5.74.0" - }, - "peerDependencies": { - "@babel/runtime": "^7.0.0", - "@react-native-async-storage/async-storage": "^1.2.0", - "@react-native-community/netinfo": "5.9.4" - }, - "peerDependenciesMeta": { - "@react-native-async-storage/async-storage": { - "optional": true - }, - "@react-native-community/netinfo": { - "optional": true - } - }, - "publishConfig": { - "access": "public" - }, - "files": [ - "dist/", - "lib/" - ], - "nyc": { - "temp-dir": "coverage/raw" - } -} diff --git a/packages/optimizely-sdk/rollup.config.js b/packages/optimizely-sdk/rollup.config.js deleted file mode 100644 index 70d60c278..000000000 --- a/packages/optimizely-sdk/rollup.config.js +++ /dev/null @@ -1,147 +0,0 @@ -/** - * Copyright 2020-2022 Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import commonjs from '@rollup/plugin-commonjs'; -import { terser } from 'rollup-plugin-terser'; -import resolve from '@rollup/plugin-node-resolve'; -import { dependencies, peerDependencies } from './package.json'; -import typescript from 'rollup-plugin-typescript2'; - -const typescriptPluginOptions = { - allowJs: true, - exclude: [ - './dist', - './lib/**/*.tests.js', - './lib/**/*.tests.ts', - './lib/**/*.umdtests.js', - './lib/tests', - 'node_modules' - ], - include: [ - './lib/**/*.ts', - './lib/**/*.js', - ], -}; - -const cjsBundleFor = (platform) => ({ - plugins: [ - resolve(), - commonjs(), - typescript(typescriptPluginOptions), - ], - external: ['https', 'http', 'url'].concat(Object.keys({ ...dependencies, ...peerDependencies } || {})), - input: `lib/index.${platform}.ts`, - output: { - exports: 'named', - format: 'cjs', - file: `dist/optimizely.${platform}.min.js`, - plugins: [terser()], - sourcemap: true, - }, -}); - -const esmBundleFor = (platform) => ({ - ...cjsBundleFor(platform), - output: [ - { - format: 'es', - file: `dist/optimizely.${platform}.es.js`, - sourcemap: true, - }, - { - format: 'es', - file: `dist/optimizely.${platform}.es.min.js`, - plugins: [terser()], - sourcemap: true, - }, - ], -}) - -const umdBundle = { - plugins: [ - resolve({ browser: true }), - commonjs({ - namedExports: { - '@optimizely/js-sdk-event-processor': ['LogTierV1EventProcessor', 'LocalStoragePendingEventsDispatcher'], - }, - }), - typescript(typescriptPluginOptions), - ], - input: 'lib/index.browser.ts', - output: [ - { - name: 'optimizelySdk', - format: 'umd', - file: 'dist/optimizely.browser.umd.js', - exports: 'named', - }, - { - name: 'optimizelySdk', - format: 'umd', - file: 'dist/optimizely.browser.umd.min.js', - exports: 'named', - plugins: [terser()], - sourcemap: true, - }, - ], -}; - -// A separate bundle for json schema validator. -const jsonSchemaBundle = { - plugins: [ - resolve(), - commonjs(), - typescript(typescriptPluginOptions), - ], - external: ['json-schema', 'uuid'], - input: 'lib/utils/json_schema_validator/index.ts', - output: { - exports: 'named', - format: 'cjs', - file: 'dist/optimizely.json_schema_validator.min.js', - plugins: [terser()], - sourcemap: true, - }, -}; - -const bundles = { - 'cjs-node': cjsBundleFor('node'), - 'cjs-browser': cjsBundleFor('browser'), - 'cjs-react-native': cjsBundleFor('react_native'), - 'cjs-lite': cjsBundleFor('lite'), - esm: esmBundleFor('browser'), - 'esm-lite': esmBundleFor('lite'), - 'json-schema': jsonSchemaBundle, - umd: umdBundle, -}; - -// Collect all --config-* options and return the matching bundle configs -// Builds all bundles if no --config-* option given -// --config-cjs will build all three cjs-* bundles -// --config-umd will build only the umd bundle -// --config-umd --config-json will build both umd and the json-schema bundles -export default (args) => { - const patterns = Object.keys(args) - .filter((arg) => arg.startsWith('config-')) - .map((arg) => arg.replace(/config-/, '')); - - // default to matching all bundles - if (!patterns.length) patterns.push(/.*/); - - return Object.entries(bundles) - .filter(([name, config]) => patterns.some((pattern) => name.match(pattern))) - .map(([name, config]) => config); -}; diff --git a/packages/optimizely-sdk/tests/backoffController.spec.ts b/packages/optimizely-sdk/tests/backoffController.spec.ts deleted file mode 100644 index ff37a1e73..000000000 --- a/packages/optimizely-sdk/tests/backoffController.spec.ts +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import BackoffController from '../lib/modules/datafile-manager/backoffController'; - -describe('backoffController', () => { - describe('getDelay', () => { - it('returns 0 from getDelay if there have been no errors', () => { - const controller = new BackoffController(); - expect(controller.getDelay()).toBe(0); - }); - - it('increases the delay returned from getDelay (up to a maximum value) after each call to countError', () => { - const controller = new BackoffController(); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(8000); - expect(controller.getDelay()).toBeLessThan(9000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(16000); - expect(controller.getDelay()).toBeLessThan(17000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(32000); - expect(controller.getDelay()).toBeLessThan(33000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(64000); - expect(controller.getDelay()).toBeLessThan(65000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(128000); - expect(controller.getDelay()).toBeLessThan(129000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(256000); - expect(controller.getDelay()).toBeLessThan(257000); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(512000); - expect(controller.getDelay()).toBeLessThan(513000); - // Maximum reached - additional errors should not increase the delay further - controller.countError(); - expect(controller.getDelay()).toBeGreaterThanOrEqual(512000); - expect(controller.getDelay()).toBeLessThan(513000); - }); - - it('resets the error count when reset is called', () => { - const controller = new BackoffController(); - controller.countError(); - expect(controller.getDelay()).toBeGreaterThan(0); - controller.reset(); - expect(controller.getDelay()).toBe(0); - }); - }); -}); diff --git a/packages/optimizely-sdk/tests/browserAsyncStorageCache.spec.ts b/packages/optimizely-sdk/tests/browserAsyncStorageCache.spec.ts deleted file mode 100644 index 0e690443a..000000000 --- a/packages/optimizely-sdk/tests/browserAsyncStorageCache.spec.ts +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/// <reference types="jest" /> - -import BrowserAsyncStorageCache from '../lib/plugins/key_value_cache/browserAsyncStorageCache'; - -describe('BrowserAsyncStorageCache', () => { - const KEY_THAT_EXISTS = 'keyThatExists'; - const VALUE_FOR_KEY_THAT_EXISTS = 'some really super value that exists for keyThatExists'; - const NONEXISTENT_KEY = 'someKeyThatDoesNotExist'; - - let cacheInstance: BrowserAsyncStorageCache; - - beforeEach(() => { - const stubData = new Map<string, string>(); - stubData.set(KEY_THAT_EXISTS, VALUE_FOR_KEY_THAT_EXISTS); - - cacheInstance = new BrowserAsyncStorageCache(); - - jest - .spyOn(localStorage, 'getItem') - .mockImplementation((key) => key == KEY_THAT_EXISTS ? VALUE_FOR_KEY_THAT_EXISTS : null); - jest - .spyOn(localStorage, 'setItem') - .mockImplementation(() => 1); - jest - .spyOn(localStorage, 'removeItem') - .mockImplementation((key) => key == KEY_THAT_EXISTS); - }); - - describe('contains', () => { - it('should return true if value with key exists', async () => { - const keyWasFound = await cacheInstance.contains(KEY_THAT_EXISTS); - - expect(keyWasFound).toBe(true); - }); - - it('should return false if value with key does not exist', async () => { - const keyWasFound = await cacheInstance.contains(NONEXISTENT_KEY); - - expect(keyWasFound).toBe(false); - }); - }); - - describe('get', () => { - it('should return correct string when item is found in cache', async () => { - const foundValue = await cacheInstance.get(KEY_THAT_EXISTS); - - expect(foundValue).toEqual(VALUE_FOR_KEY_THAT_EXISTS); - }); - - it('should return empty string if item is not found in cache', async () => { - const json = await cacheInstance.get(NONEXISTENT_KEY); - - expect(json).toBeNull(); - }); - }); - - describe('remove', () => { - it('should return true after removing a found entry', async () => { - const wasSuccessful = await cacheInstance.remove(KEY_THAT_EXISTS); - - expect(wasSuccessful).toBe(true); - }); - - it('should return false after trying to remove an entry that is not found ', async () => { - const wasSuccessful = await cacheInstance.remove(NONEXISTENT_KEY); - - expect(wasSuccessful).toBe(false); - }); - }); - - describe('set', () => { - it('should resolve promise if item was successfully set in the cache', async () => { - await cacheInstance.set('newTestKey', 'a value for this newTestKey'); - }); - }); -}); diff --git a/packages/optimizely-sdk/tests/browserDatafileManager.spec.ts b/packages/optimizely-sdk/tests/browserDatafileManager.spec.ts deleted file mode 100644 index bfd1adb1c..000000000 --- a/packages/optimizely-sdk/tests/browserDatafileManager.spec.ts +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import BrowserDatafileManager from '../lib/modules/datafile-manager/browserDatafileManager'; -import * as browserRequest from '../lib/modules/datafile-manager/browserRequest'; -import { Headers, AbortableRequest } from '../lib/modules/datafile-manager/http'; -import { advanceTimersByTime, getTimerCount } from './testUtils'; - -describe('browserDatafileManager', () => { - let makeGetRequestSpy: jest.SpyInstance<AbortableRequest, [string, Headers]>; - beforeEach(() => { - jest.useFakeTimers(); - makeGetRequestSpy = jest.spyOn(browserRequest, 'makeGetRequest'); - }); - - afterEach(() => { - jest.restoreAllMocks(); - jest.clearAllTimers(); - }); - - it('calls makeGetRequest when started', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - - const manager = new BrowserDatafileManager({ - sdkKey: '1234', - autoUpdate: false, - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy.mock.calls[0][0]).toBe('https://cdn.optimizely.com/datafiles/1234.json'); - expect(makeGetRequestSpy.mock.calls[0][1]).toEqual({}); - - await manager.onReady(); - await manager.stop(); - }); - - it('calls makeGetRequest for live update requests', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'last-modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - }), - }); - const manager = new BrowserDatafileManager({ - sdkKey: '1234', - autoUpdate: true, - }); - manager.start(); - await manager.onReady(); - await advanceTimersByTime(300000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - expect(makeGetRequestSpy.mock.calls[1][0]).toBe('https://cdn.optimizely.com/datafiles/1234.json'); - expect(makeGetRequestSpy.mock.calls[1][1]).toEqual({ - 'if-modified-since': 'Fri, 08 Mar 2019 18:57:17 GMT', - }); - - await manager.stop(); - }); - - it('defaults to false for autoUpdate', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'last-modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - }), - }); - const manager = new BrowserDatafileManager({ - sdkKey: '1234', - }); - manager.start(); - await manager.onReady(); - // Should not set a timeout for a later update - expect(getTimerCount()).toBe(0); - - await manager.stop(); - }); -}); diff --git a/packages/optimizely-sdk/tests/browserRequest.spec.ts b/packages/optimizely-sdk/tests/browserRequest.spec.ts deleted file mode 100644 index 24ab30dd9..000000000 --- a/packages/optimizely-sdk/tests/browserRequest.spec.ts +++ /dev/null @@ -1,132 +0,0 @@ -/** - * @jest-environment jsdom - */ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { FakeXMLHttpRequest, FakeXMLHttpRequestStatic, fakeXhr } from 'nise'; -import { makeGetRequest } from '../lib/modules/datafile-manager/browserRequest'; - -describe('browserRequest', () => { - describe('makeGetRequest', () => { - let mockXHR: FakeXMLHttpRequestStatic; - let xhrs: FakeXMLHttpRequest[]; - beforeEach(() => { - xhrs = []; - mockXHR = fakeXhr.useFakeXMLHttpRequest(); - mockXHR.onCreate = (req): number => xhrs.push(req); - }); - - afterEach(() => { - mockXHR.restore(); - }); - - it('makes a GET request to the argument URL', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - - expect(xhrs.length).toBe(1); - const xhr = xhrs[0]; - const { url, method } = xhr; - expect({ url, method }).toEqual({ - url: 'https://cdn.optimizely.com/datafiles/123.json', - method: 'GET', - }); - - xhr.respond(200, {}, '{"foo":"bar"}'); - - await req.responsePromise; - }); - - it('returns a 200 response back to its superclass', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - - const xhr = xhrs[0]; - xhr.respond(200, {}, '{"foo":"bar"}'); - - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - headers: {}, - body: '{"foo":"bar"}', - }); - }); - - it('returns a 404 response back to its superclass', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - - const xhr = xhrs[0]; - xhr.respond(404, {}, ''); - - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 404, - headers: {}, - body: '', - }); - }); - - it('includes headers from the headers argument in the request', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/dataifles/123.json', { - 'if-modified-since': 'Fri, 08 Mar 2019 18:57:18 GMT', - }); - - expect(xhrs.length).toBe(1); - expect(xhrs[0].requestHeaders['if-modified-since']).toBe('Fri, 08 Mar 2019 18:57:18 GMT'); - - xhrs[0].respond(404, {}, ''); - - await req.responsePromise; - }); - - it('includes headers from the response in the eventual response in the return value', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - - const xhr = xhrs[0]; - xhr.respond( - 200, - { - 'content-type': 'application/json', - 'last-modified': 'Fri, 08 Mar 2019 18:57:18 GMT', - }, - '{"foo":"bar"}' - ); - - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'content-type': 'application/json', - 'last-modified': 'Fri, 08 Mar 2019 18:57:18 GMT', - }, - }); - }); - - it('returns a rejected promise when there is a request error', async () => { - const req = makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - xhrs[0].error(); - await expect(req.responsePromise).rejects.toThrow(); - }); - - it('sets a timeout on the request object', () => { - const onCreateMock = jest.fn(); - mockXHR.onCreate = onCreateMock; - makeGetRequest('https://cdn.optimizely.com/datafiles/123.json', {}); - expect(onCreateMock).toBeCalledTimes(1); - expect(onCreateMock.mock.calls[0][0].timeout).toBe(60000); - }); - }); -}); diff --git a/packages/optimizely-sdk/tests/buildEventV1.spec.ts b/packages/optimizely-sdk/tests/buildEventV1.spec.ts deleted file mode 100644 index 273bcea6e..000000000 --- a/packages/optimizely-sdk/tests/buildEventV1.spec.ts +++ /dev/null @@ -1,812 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/// <reference types="jest" /> - -import { - buildConversionEventV1, - buildImpressionEventV1, - makeBatchedEventV1, -} from '../lib/modules/event_processor/v1/buildEventV1' -import { ImpressionEvent, ConversionEvent } from '../lib/modules/event_processor/events' - -describe('buildEventV1', () => { - describe('buildImpressionEventV1', () => { - it('should build an ImpressionEventV1 when experiment and variation are defined', () => { - const impressionEvent: ImpressionEvent = { - type: 'impression', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - layer: { - id: 'layerId', - }, - - experiment: { - id: 'expId', - key: 'expKey', - }, - - variation: { - id: 'varId', - key: 'varKey', - }, - - ruleKey: 'expKey', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: true, - } - - const result = buildImpressionEventV1(impressionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - decisions: [ - { - campaign_id: 'layerId', - experiment_id: 'expId', - variation_id: 'varId', - metadata: { - flag_key: 'flagKey1', - rule_key: 'expKey', - rule_type: 'experiment', - variation_key: 'varKey', - enabled: true, - }, - }, - ], - events: [ - { - entity_id: 'layerId', - timestamp: 69, - key: 'campaign_activated', - uuid: 'uuid', - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - - it('should build an ImpressionEventV1 when experiment and variation are not defined', () => { - const impressionEvent: ImpressionEvent = { - type: 'impression', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - layer: { - id: null, - }, - - experiment: { - id: null, - key: '', - }, - - variation: { - id: null, - key: '', - }, - - ruleKey: '', - flagKey: 'flagKey1', - ruleType: 'rollout', - enabled: true, - } - - const result = buildImpressionEventV1(impressionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - decisions: [ - { - campaign_id: null, - experiment_id: "", - variation_id: "", - metadata: { - flag_key: 'flagKey1', - rule_key: '', - rule_type: 'rollout', - variation_key: '', - enabled: true, - }, - }, - ], - events: [ - { - entity_id: null, - timestamp: 69, - key: 'campaign_activated', - uuid: 'uuid', - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - }) - - describe('buildConversionEventV1', () => { - it('should build a ConversionEventV1 when tags object is defined', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - - revenue: 1000, - value: 123, - } - - const result = buildConversionEventV1(conversionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - events: [ - { - entity_id: 'event-id', - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - revenue: 1000, - value: 123, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - - it('should build a ConversionEventV1 when tags object is undefined', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: undefined, - - revenue: 1000, - value: 123, - } - - const result = buildConversionEventV1(conversionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - events: [ - { - entity_id: 'event-id', - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: undefined, - revenue: 1000, - value: 123, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - - it('should build a ConversionEventV1 when event id is null', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: null, - key: 'event-key', - }, - - tags: undefined, - - revenue: 1000, - value: 123, - } - - const result = buildConversionEventV1(conversionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - events: [ - { - entity_id: null, - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: undefined, - revenue: 1000, - value: 123, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - - it('should include revenue and value if they are 0', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: 0, - revenue: 0, - }, - - revenue: 0, - value: 0, - } - - const result = buildConversionEventV1(conversionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - events: [ - { - entity_id: 'event-id', - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: { - foo: 'bar', - value: 0, - revenue: 0, - }, - revenue: 0, - value: 0, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - - it('should not include $opt_bot_filtering attribute if context.botFiltering is undefined', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - - revenue: 1000, - value: 123, - } - - const result = buildConversionEventV1(conversionEvent) - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - events: [ - { - entity_id: 'event-id', - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - revenue: 1000, - value: 123, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - ], - }, - ], - }) - }) - }) - - describe('makeBatchedEventV1', () => { - it('should batch Conversion and Impression events together', () => { - const conversionEvent: ConversionEvent = { - type: 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - - revenue: 1000, - value: 123, - } - - const impressionEvent: ImpressionEvent = { - type: 'impression', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: 'revision', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - layer: { - id: 'layerId', - }, - - experiment: { - id: 'expId', - key: 'expKey', - }, - - variation: { - id: 'varId', - key: 'varKey', - }, - - ruleKey: 'expKey', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: true, - } - - const result = makeBatchedEventV1([impressionEvent, conversionEvent]) - - expect(result).toEqual({ - client_name: 'node-sdk', - client_version: '3.0.0', - account_id: 'accountId', - project_id: 'projectId', - revision: 'revision', - anonymize_ip: true, - enrich_decisions: true, - - visitors: [ - { - snapshots: [ - { - decisions: [ - { - campaign_id: 'layerId', - experiment_id: 'expId', - variation_id: 'varId', - metadata: { - flag_key: 'flagKey1', - rule_key: 'expKey', - rule_type: 'experiment', - variation_key: 'varKey', - enabled: true, - }, - }, - ], - events: [ - { - entity_id: 'layerId', - timestamp: 69, - key: 'campaign_activated', - uuid: 'uuid', - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - { - snapshots: [ - { - events: [ - { - entity_id: 'event-id', - timestamp: 69, - key: 'event-key', - uuid: 'uuid', - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - revenue: 1000, - value: 123, - }, - ], - }, - ], - visitor_id: 'userId', - attributes: [ - { - entity_id: 'attr1-id', - key: 'attr1-key', - type: 'custom', - value: 'attr1-value', - }, - { - entity_id: '$opt_bot_filtering', - key: '$opt_bot_filtering', - type: 'custom', - value: true, - }, - ], - }, - ], - }) - }) - }) -}) diff --git a/packages/optimizely-sdk/tests/eventEmitter.spec.ts b/packages/optimizely-sdk/tests/eventEmitter.spec.ts deleted file mode 100644 index bd1d83ebf..000000000 --- a/packages/optimizely-sdk/tests/eventEmitter.spec.ts +++ /dev/null @@ -1,116 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import EventEmitter from '../lib/modules/datafile-manager/eventEmitter'; - -describe('event_emitter', () => { - describe('on', () => { - let emitter: EventEmitter; - beforeEach(() => { - emitter = new EventEmitter(); - }); - - it('can add a listener for the update event', () => { - const listener = jest.fn(); - emitter.on('update', listener); - emitter.emit('update', { datafile: 'abcd' }); - expect(listener).toBeCalledTimes(1); - }); - - it('passes the argument from emit to the listener', () => { - const listener = jest.fn(); - emitter.on('update', listener); - emitter.emit('update', { datafile: 'abcd' }); - expect(listener).toBeCalledWith({ datafile: 'abcd' }); - }); - - it('returns a dispose function that removes the listener', () => { - const listener = jest.fn(); - const disposer = emitter.on('update', listener); - disposer(); - emitter.emit('update', { datafile: 'efgh' }); - expect(listener).toBeCalledTimes(0); - }); - - it('can add several listeners for the update event', () => { - const listener1 = jest.fn(); - const listener2 = jest.fn(); - const listener3 = jest.fn(); - emitter.on('update', listener1); - emitter.on('update', listener2); - emitter.on('update', listener3); - emitter.emit('update', { datafile: 'abcd' }); - expect(listener1).toBeCalledTimes(1); - expect(listener2).toBeCalledTimes(1); - expect(listener3).toBeCalledTimes(1); - }); - - it('can add several listeners and remove only some of them', () => { - const listener1 = jest.fn(); - const listener2 = jest.fn(); - const listener3 = jest.fn(); - const disposer1 = emitter.on('update', listener1); - const disposer2 = emitter.on('update', listener2); - emitter.on('update', listener3); - emitter.emit('update', { datafile: 'abcd' }); - expect(listener1).toBeCalledTimes(1); - expect(listener2).toBeCalledTimes(1); - expect(listener3).toBeCalledTimes(1); - disposer1(); - disposer2(); - emitter.emit('update', { datafile: 'efgh' }); - expect(listener1).toBeCalledTimes(1); - expect(listener2).toBeCalledTimes(1); - expect(listener3).toBeCalledTimes(2); - }); - - it('can add listeners for different events and remove only some of them', () => { - const readyListener = jest.fn(); - const updateListener = jest.fn(); - const readyDisposer = emitter.on('ready', readyListener); - const updateDisposer = emitter.on('update', updateListener); - emitter.emit('ready'); - expect(readyListener).toBeCalledTimes(1); - expect(updateListener).toBeCalledTimes(0); - emitter.emit('update', { datafile: 'abcd' }); - expect(readyListener).toBeCalledTimes(1); - expect(updateListener).toBeCalledTimes(1); - readyDisposer(); - emitter.emit('ready'); - expect(readyListener).toBeCalledTimes(1); - expect(updateListener).toBeCalledTimes(1); - emitter.emit('update', { datafile: 'efgh' }); - expect(readyListener).toBeCalledTimes(1); - expect(updateListener).toBeCalledTimes(2); - updateDisposer(); - emitter.emit('update', { datafile: 'ijkl' }); - expect(readyListener).toBeCalledTimes(1); - expect(updateListener).toBeCalledTimes(2); - }); - - it('can remove all listeners', () => { - const readyListener = jest.fn(); - const updateListener = jest.fn(); - emitter.on('ready', readyListener); - emitter.on('update', updateListener); - emitter.removeAllListeners(); - emitter.emit('update', { datafile: 'abcd' }); - emitter.emit('ready'); - expect(readyListener).toBeCalledTimes(0); - expect(updateListener).toBeCalledTimes(0); - }); - }); -}); diff --git a/packages/optimizely-sdk/tests/eventQueue.spec.ts b/packages/optimizely-sdk/tests/eventQueue.spec.ts deleted file mode 100644 index 0ebf82454..000000000 --- a/packages/optimizely-sdk/tests/eventQueue.spec.ts +++ /dev/null @@ -1,290 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/// <reference types="jest" /> - -import { DefaultEventQueue, SingleEventQueue } from '../lib/modules/event_processor/eventQueue' - -describe('eventQueue', () => { - beforeEach(() => { - jest.useFakeTimers() - }) - - afterEach(() => { - jest.useRealTimers() - jest.resetAllMocks() - }) - - describe('SingleEventQueue', () => { - it('should immediately invoke the sink function when items are enqueued', () => { - const sinkFn = jest.fn() - const queue = new SingleEventQueue<number>({ - sink: sinkFn, - }) - - queue.start() - - queue.enqueue(1) - - expect(sinkFn).toBeCalledTimes(1) - expect(sinkFn).toHaveBeenLastCalledWith([1]) - - queue.enqueue(2) - expect(sinkFn).toBeCalledTimes(2) - expect(sinkFn).toHaveBeenLastCalledWith([2]) - - queue.stop() - }) - }) - - describe('DefaultEventQueue', () => { - it('should treat maxQueueSize = -1 as 1', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: -1, - sink: sinkFn, - batchComparator: () => true - }) - - queue.start() - - queue.enqueue(1) - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - queue.enqueue(2) - expect(sinkFn).toHaveBeenCalledTimes(2) - expect(sinkFn).toHaveBeenCalledWith([2]) - - queue.stop() - }) - - it('should treat maxQueueSize = 0 as 1', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 0, - sink: sinkFn, - batchComparator: () => true - }) - - queue.start() - - queue.enqueue(1) - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - queue.enqueue(2) - expect(sinkFn).toHaveBeenCalledTimes(2) - expect(sinkFn).toHaveBeenCalledWith([2]) - - queue.stop() - }) - - it('should invoke the sink function when maxQueueSize is reached', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 3, - sink: sinkFn, - batchComparator: () => true - }) - - queue.start() - - queue.enqueue(1) - queue.enqueue(2) - expect(sinkFn).not.toHaveBeenCalled() - - queue.enqueue(3) - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1, 2, 3]) - - queue.enqueue(4) - queue.enqueue(5) - queue.enqueue(6) - expect(sinkFn).toHaveBeenCalledTimes(2) - expect(sinkFn).toHaveBeenCalledWith([4, 5, 6]) - - queue.stop() - }) - - it('should invoke the sink function when the interval has expired', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - batchComparator: () => true - }) - - queue.start() - - queue.enqueue(1) - queue.enqueue(2) - expect(sinkFn).not.toHaveBeenCalled() - - jest.advanceTimersByTime(100) - - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1, 2]) - - queue.enqueue(3) - jest.advanceTimersByTime(100) - - expect(sinkFn).toHaveBeenCalledTimes(2) - expect(sinkFn).toHaveBeenCalledWith([3]) - - queue.stop() - }) - - it('should invoke the sink function when an item incompatable with the current batch (according to batchComparator) is received', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<string>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - // This batchComparator returns true when the argument strings start with the same letter - batchComparator: (s1, s2) => s1[0] === s2[0] - }) - - queue.start() - - queue.enqueue('a1') - queue.enqueue('a2') - // After enqueuing these strings, both starting with 'a', the sinkFn should not yet be called. Thus far all the items enqueued are - // compatible according to the batchComparator. - expect(sinkFn).not.toHaveBeenCalled() - - // Enqueuing a string starting with 'b' should cause the sinkFn to be called - queue.enqueue('b1') - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith(['a1', 'a2']) - }) - - it('stop() should flush the existing queue and call timer.stop()', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - batchComparator: () => true - }) - - jest.spyOn(queue.timer, 'stop') - - queue.start() - queue.enqueue(1) - - // stop + start is called when the first item is enqueued - expect(queue.timer.stop).toHaveBeenCalledTimes(1) - - queue.stop() - - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - expect(queue.timer.stop).toHaveBeenCalledTimes(2) - }) - - it('flush() should clear the current batch', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - batchComparator: () => true - }) - - jest.spyOn(queue.timer, 'refresh') - - queue.start() - queue.enqueue(1) - queue.flush() - - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - expect(queue.timer.refresh).toBeCalledTimes(1) - - queue.stop() - }) - - it('stop() should return a promise', () => { - const promise = Promise.resolve() - const sinkFn = jest.fn().mockReturnValue(promise) - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - batchComparator: () => true - }) - - expect(queue.stop()).toBe(promise) - }) - - it('should start the timer when the first event is put into the queue', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 100, - maxQueueSize: 100, - sink: sinkFn, - batchComparator: () => true - }) - - queue.start() - jest.advanceTimersByTime(99) - queue.enqueue(1) - - jest.advanceTimersByTime(2) - expect(sinkFn).toHaveBeenCalledTimes(0) - jest.advanceTimersByTime(98) - - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - - jest.advanceTimersByTime(500) - // ensure sink function wasnt called again since no events have - // been added - expect(sinkFn).toHaveBeenCalledTimes(1) - - queue.enqueue(2) - - jest.advanceTimersByTime(100) - expect(sinkFn).toHaveBeenCalledTimes(2) - expect(sinkFn).toHaveBeenLastCalledWith([2]) - - queue.stop() - - }) - - it('should not enqueue additional events after stop() is called', () => { - const sinkFn = jest.fn() - const queue = new DefaultEventQueue<number>({ - flushInterval: 30000, - maxQueueSize: 3, - sink: sinkFn, - batchComparator: () => true - }) - queue.start() - queue.enqueue(1) - queue.stop() - expect(sinkFn).toHaveBeenCalledTimes(1) - expect(sinkFn).toHaveBeenCalledWith([1]) - sinkFn.mockClear() - queue.enqueue(2) - queue.enqueue(3) - queue.enqueue(4) - expect(sinkFn).toBeCalledTimes(0) - }) - }) -}) diff --git a/packages/optimizely-sdk/tests/graphQlManager.spec.ts b/packages/optimizely-sdk/tests/graphQlManager.spec.ts deleted file mode 100644 index 8f2c228ff..000000000 --- a/packages/optimizely-sdk/tests/graphQlManager.spec.ts +++ /dev/null @@ -1,258 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/// <reference types="jest" /> - -import { anyString, anything, instance, mock, resetCalls, verify, when } from 'ts-mockito'; -import { LogHandler, LogLevel } from '../lib/modules/logging'; -import { GraphQLManager } from '../lib/plugins/odp/graphql_manager'; -import { RequestHandler } from '../lib/utils/http_request_handler/http'; -import { ODP_USER_KEY } from '../lib/utils/enums'; - -const API_key = 'not-real-api-key'; -const GRAPHQL_ENDPOINT = 'https://some.example.com/graphql/endpoint'; -const USER_KEY = ODP_USER_KEY.FS_USER_ID; -const USER_VALUE = 'tester-101'; -const SEGMENTS_TO_CHECK = [ - 'has_email', - 'has_email_opted_in', - 'push_on_sale', -]; - -describe('GraphQLManager', () => { - let mockLogger: LogHandler; - let mockRequestHandler: RequestHandler; - - beforeAll(() => { - mockLogger = mock<LogHandler>(); - mockRequestHandler = mock<RequestHandler>(); - }); - - beforeEach(() => { - resetCalls(mockLogger); - resetCalls(mockRequestHandler); - }); - - const managerInstance = () => new GraphQLManager(instance(mockRequestHandler), instance(mockLogger)); - - const abortableRequest = (statusCode: number, body: string) => { - return { - abort: () => { - }, - responsePromise: Promise.resolve({ - statusCode, - body, - headers: {}, - }), - }; - }; - - it('should parse a successful response', () => { - const validJsonResponse = `{ - "data": { - "customer": { - "audiences": { - "edges": [ - { - "node": { - "name": "has_email", - "state": "qualified" - } - }, - { - "node": { - "name": "has_email_opted_in", - "state": "not-qualified" - } - } - ] - } - } - } - }`; - const manager = managerInstance(); - - const response = manager['parseSegmentsResponseJson'](validJsonResponse); - - expect(response).not.toBeUndefined(); - expect(response?.errors).toHaveLength(0); - expect(response?.data?.customer?.audiences?.edges).not.toBeNull(); - expect(response?.data.customer.audiences.edges).toHaveLength(2); - let node = response?.data.customer.audiences.edges[0].node; - expect(node?.name).toEqual('has_email'); - expect(node?.state).toEqual('qualified'); - node = response?.data.customer.audiences.edges[1].node; - expect(node?.name).toEqual('has_email_opted_in'); - expect(node?.state).not.toEqual('qualified'); - }); - - it('should parse an error response', () => { - const errorJsonResponse = `{ - "errors": [ - { - "message": "Exception while fetching data (/customer) : Exception: could not resolve _fs_user_id = mock_user_id", - "locations": [ - { - "line": 2, - "column": 3 - } - ], - "path": [ - "customer" - ], - "extensions": { - "classification": "InvalidIdentifierException" - } - } - ], - "data": { - "customer": null - } -}`; - const manager = managerInstance(); - - const response = manager['parseSegmentsResponseJson'](errorJsonResponse); - - expect(response).not.toBeUndefined(); - expect(response?.data.customer).toBeNull(); - expect(response?.errors).not.toBeNull(); - expect(response?.errors[0].extensions.classification).toEqual('InvalidIdentifierException'); - }); - - it('should construct a valid GraphQL query string', () => { - const manager = managerInstance(); - - const response = manager['toGraphQLJson'](USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); - - expect(response) - .toBe(`{"query" : "query {customer"(${USER_KEY} : "${USER_VALUE}") {audiences(subset: [\\"has_email\\",\\"has_email_opted_in\\",\\"push_on_sale\\"] {edges {node {name state}}}}}"}`, - ); - }); - - it('should fetch valid qualified segments', async () => { - const responseJsonWithQualifiedSegments = '{"data":{"customer":{"audiences":' + - '{"edges":[{"node":{"name":"has_email",' + - '"state":"qualified"}},{"node":{"name":' + - '"has_email_opted_in","state":"qualified"}}]}}}}'; - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(200, responseJsonWithQualifiedSegments)); - const manager = managerInstance(); - - const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); - - expect(segments).toHaveLength(2); - expect(segments).toContain('has_email'); - expect(segments).toContain('has_email_opted_in'); - verify(mockLogger.log(anything(), anyString())).never(); - }); - - it('should handle a request to query no segments', async () => { - const manager = managerInstance(); - - const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, ODP_USER_KEY.FS_USER_ID, USER_VALUE, []); - - expect(segments).toHaveLength(0); - verify(mockLogger.log(anything(), anyString())).never(); - }); - - it('should handle empty qualified segments', async () => { - const responseJsonWithNoQualifiedSegments = '{"data":{"customer":{"audiences":' + - '{"edges":[ ]}}}}'; - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(200, responseJsonWithNoQualifiedSegments)); - const manager = managerInstance(); - - const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); - - expect(segments).toHaveLength(0); - verify(mockLogger.log(anything(), anyString())).never(); - }); - - it('should handle error with invalid identifier', async () => { - const INVALID_USER_ID = 'invalid-user'; - const errorJsonResponse = '{"errors":[{"message":' + - '"Exception while fetching data (/customer) : ' + - `Exception: could not resolve _fs_user_id = ${INVALID_USER_ID}",` + - '"locations":[{"line":1,"column":8}],"path":["customer"],' + - '"extensions":{"classification":"DataFetchingException"}}],' + - '"data":{"customer":null}}'; - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(200, errorJsonResponse)); - const manager = managerInstance(); - - const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, INVALID_USER_ID, SEGMENTS_TO_CHECK); - - expect(segments).toBeNull(); - verify(mockLogger.log(anything(), anyString())).once(); - }); - - it('should handle unrecognized JSON responses', async () => { - const unrecognizedJson = '{"unExpectedObject":{ "withSome": "value", "thatIsNotParseable": "true" }}'; - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(200, unrecognizedJson)); - const manager = managerInstance(); - - const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); - - expect(segments).toBeNull(); - verify(mockLogger.log(LogLevel.ERROR, 'Audience segments fetch failed (decode error)')).once(); - }); - - it('should handle other exception types', async () => { - const errorJsonResponse = '{"errors":[{"message":"Validation error of type ' + - 'UnknownArgument: Unknown field argument not_real_userKey @ ' + - '\'customer\'","locations":[{"line":1,"column":17}],' + - '"extensions":{"classification":"ValidationError"}}]}'; - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(200, errorJsonResponse)); - const manager = managerInstance(); - - const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); - - expect(segments).toBeNull(); - verify(mockLogger.log(anything(), anyString())).once(); - }); - - it('should handle bad responses', async () => { - const badResponse = '{"data":{ }}'; - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(200, badResponse)); - const manager = managerInstance(); - - const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); - - expect(segments).toBeNull(); - verify(mockLogger.log(LogLevel.ERROR, 'Audience segments fetch failed (decode error)')).once(); - }); - - it('should handle non 200 HTTP status code response', async () => { - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(400, '')); - const manager = managerInstance(); - - const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); - - expect(segments).toBeNull(); - verify(mockLogger.log(LogLevel.ERROR, 'Audience segments fetch failed (network error)')).once(); - }); - - it('should handle a timeout', async () => { - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn({ - abort: () => { - }, - responsePromise: Promise.reject(new Error('Request timed out')), - }); - const manager = managerInstance(); - - const segments = await manager.fetchSegments(API_key, GRAPHQL_ENDPOINT, USER_KEY, USER_VALUE, SEGMENTS_TO_CHECK); - - expect(segments).toBeNull(); - verify(mockLogger.log(LogLevel.ERROR, 'Audience segments fetch failed (network error)')).once(); - }); -}); diff --git a/packages/optimizely-sdk/tests/httpPollingDatafileManager.spec.ts b/packages/optimizely-sdk/tests/httpPollingDatafileManager.spec.ts deleted file mode 100644 index 288a30367..000000000 --- a/packages/optimizely-sdk/tests/httpPollingDatafileManager.spec.ts +++ /dev/null @@ -1,712 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import HttpPollingDatafileManager from '../lib/modules/datafile-manager/httpPollingDatafileManager'; -import { Headers, AbortableRequest, Response } from '../lib/modules/datafile-manager/http'; -import { DatafileManagerConfig } from '../lib/modules/datafile-manager/datafileManager'; -import { advanceTimersByTime, getTimerCount } from './testUtils'; -import PersistentKeyValueCache from '../lib/modules/datafile-manager/persistentKeyValueCache'; - -jest.mock('../lib/modules/datafile-manager/backoffController', () => { - return jest.fn().mockImplementation(() => { - const getDelayMock = jest.fn().mockImplementation(() => 0); - return { - getDelay: getDelayMock, - countError: jest.fn(), - reset: jest.fn(), - }; - }); -}); - -import BackoffController from '../lib/modules/datafile-manager/backoffController'; - -// Test implementation: -// - Does not make any real requests: just resolves with queued responses (tests push onto queuedResponses) -class TestDatafileManager extends HttpPollingDatafileManager { - queuedResponses: (Response | Error)[] = []; - - responsePromises: Promise<Response>[] = []; - - simulateResponseDelay = false; - - // Need these unsued vars for the mock call types to work (being able to check calls) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - makeGetRequest(url: string, headers: Headers): AbortableRequest { - const nextResponse: Error | Response | undefined = this.queuedResponses.pop(); - let responsePromise: Promise<Response>; - if (nextResponse === undefined) { - responsePromise = Promise.reject('No responses queued'); - } else if (nextResponse instanceof Error) { - responsePromise = Promise.reject(nextResponse); - } else { - if (this.simulateResponseDelay) { - // Actual response will have some delay. This is required to get expected behavior for caching. - responsePromise = new Promise(resolve => setTimeout(() => resolve(nextResponse), 50)); - } else { - responsePromise = Promise.resolve(nextResponse); - } - } - this.responsePromises.push(responsePromise); - return { responsePromise, abort: jest.fn() }; - } - - getConfigDefaults(): Partial<DatafileManagerConfig> { - return {}; - } -} - -const testCache: PersistentKeyValueCache = { - get(key: string): Promise<string> { - let val = ''; - switch (key) { - case 'opt-datafile-keyThatExists': - val = JSON.stringify({ name: 'keyThatExists' }); - break; - } - return Promise.resolve(val); - }, - - set(): Promise<void> { - return Promise.resolve(); - }, - - contains(): Promise<boolean> { - return Promise.resolve(false); - }, - - remove(): Promise<void> { - return Promise.resolve(); - }, -}; - -describe('httpPollingDatafileManager', () => { - beforeEach(() => { - jest.useFakeTimers(); - }); - - let manager: TestDatafileManager; - afterEach(async () => { - if (manager) { - manager.stop(); - } - jest.clearAllMocks(); - jest.restoreAllMocks(); - jest.clearAllTimers(); - }); - - describe('when constructed with sdkKey and datafile and autoUpdate: true,', () => { - beforeEach(() => { - manager = new TestDatafileManager({ datafile: JSON.stringify({ foo: 'abcd' }), sdkKey: '123', autoUpdate: true }); - }); - - it('returns the passed datafile from get', () => { - expect(JSON.parse(manager.get())).toEqual({ foo: 'abcd' }); - }); - - it('after being started, fetches the datafile, updates itself, and updates itself again after a timeout', async () => { - manager.queuedResponses.push( - { - statusCode: 200, - body: '{"fooz": "barz"}', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - } - ); - const updateFn = jest.fn(); - manager.on('update', updateFn); - manager.start(); - expect(manager.responsePromises.length).toBe(1); - await manager.responsePromises[0]; - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - updateFn.mockReset(); - - await advanceTimersByTime(300000); - - expect(manager.responsePromises.length).toBe(2); - await manager.responsePromises[1]; - expect(updateFn).toBeCalledTimes(1); - expect(updateFn.mock.calls[0][0]).toEqual({ datafile: '{"fooz": "barz"}' }); - expect(JSON.parse(manager.get())).toEqual({ fooz: 'barz' }); - }); - }); - - describe('when constructed with sdkKey and datafile and autoUpdate: false,', () => { - beforeEach(() => { - manager = new TestDatafileManager({ - datafile: JSON.stringify({ foo: 'abcd' }), - sdkKey: '123', - autoUpdate: false, - }); - }); - - it('returns the passed datafile from get', () => { - expect(JSON.parse(manager.get())).toEqual({ foo: 'abcd' }); - }); - - it('after being started, fetches the datafile, updates itself once, but does not schedule a future update', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - expect(manager.responsePromises.length).toBe(1); - await manager.responsePromises[0]; - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - expect(getTimerCount()).toBe(0); - }); - }); - - describe('when constructed with sdkKey and autoUpdate: true', () => { - beforeEach(() => { - manager = new TestDatafileManager({ sdkKey: '123', updateInterval: 1000, autoUpdate: true }); - }); - - describe('initial state', () => { - it('returns null from get before becoming ready', () => { - expect(manager.get()).toEqual(''); - }); - }); - - describe('started state', () => { - it('passes the default datafile URL to the makeGetRequest method', async () => { - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy.mock.calls[0][0]).toBe('https://cdn.optimizely.com/datafiles/123.json'); - await manager.onReady(); - }); - - it('after being started, fetches the datafile and resolves onReady', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - }); - - describe('live updates', () => { - it('sets a timeout to update again after the update interval', async () => { - manager.queuedResponses.push( - { - statusCode: 200, - body: '{"foo3": "bar3"}', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo4": "bar4"}', - headers: {}, - } - ); - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - await manager.responsePromises[0]; - await advanceTimersByTime(1000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - }); - - it('emits update events after live updates', async () => { - manager.queuedResponses.push( - { - statusCode: 200, - body: '{"foo3": "bar3"}', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo2": "bar2"}', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - } - ); - - const updateFn = jest.fn(); - manager.on('update', updateFn); - - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - expect(updateFn).toBeCalledTimes(0); - - await advanceTimersByTime(1000); - await manager.responsePromises[1]; - expect(updateFn).toBeCalledTimes(1); - expect(updateFn.mock.calls[0][0]).toEqual({ datafile: '{"foo2": "bar2"}' }); - expect(JSON.parse(manager.get())).toEqual({ foo2: 'bar2' }); - - updateFn.mockReset(); - - await advanceTimersByTime(1000); - await manager.responsePromises[2]; - expect(updateFn).toBeCalledTimes(1); - expect(updateFn.mock.calls[0][0]).toEqual({ datafile: '{"foo3": "bar3"}' }); - expect(JSON.parse(manager.get())).toEqual({ foo3: 'bar3' }); - }); - - describe('when the update interval time fires before the request is complete', () => { - it('waits until the request is complete before making the next request', async () => { - let resolveResponsePromise: (resp: Response) => void; - const responsePromise: Promise<Response> = new Promise(res => { - resolveResponsePromise = res; - }); - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest').mockReturnValueOnce({ - abort() {}, - responsePromise, - }); - - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - - await advanceTimersByTime(1000); - expect(makeGetRequestSpy).toBeCalledTimes(1); - - resolveResponsePromise!({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - await responsePromise; - await advanceTimersByTime(0); - expect(makeGetRequestSpy).toBeCalledTimes(2); - }); - }); - - it('cancels a pending timeout when stop is called', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - - manager.start(); - await manager.onReady(); - - expect(getTimerCount()).toBe(1); - manager.stop(); - expect(getTimerCount()).toBe(0); - }); - - it('cancels reactions to a pending fetch when stop is called', async () => { - manager.queuedResponses.push( - { - statusCode: 200, - body: '{"foo2": "bar2"}', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - } - ); - - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - - await advanceTimersByTime(1000); - - expect(manager.responsePromises.length).toBe(2); - manager.stop(); - await manager.responsePromises[1]; - // Should not have updated datafile since manager was stopped - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - }); - - it('calls abort on the current request if there is a current request when stop is called', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo2": "bar2"}', - headers: {}, - }); - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); - manager.start(); - const currentRequest = makeGetRequestSpy.mock.results[0]; - // @ts-ignore - expect(currentRequest.type).toBe('return'); - expect(currentRequest.value.abort).toBeCalledTimes(0); - manager.stop(); - expect(currentRequest.value.abort).toBeCalledTimes(1); - }); - - it('can fail to become ready on the initial request, but succeed after a later polling update', async () => { - manager.queuedResponses.push( - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }, - { - statusCode: 404, - body: '', - headers: {}, - } - ); - - manager.start(); - expect(manager.responsePromises.length).toBe(1); - await manager.responsePromises[0]; - // Not ready yet due to first request failed, but should have queued a live update - expect(getTimerCount()).toBe(1); - // Trigger the update, should fetch the next response which should succeed, then we get ready - advanceTimersByTime(1000); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - }); - - describe('newness checking', () => { - it('does not update if the response status is 304', async () => { - manager.queuedResponses.push( - { - statusCode: 304, - body: '', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: { - 'Last-Modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - } - ); - - const updateFn = jest.fn(); - manager.on('update', updateFn); - - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - // First response promise was for the initial 200 response - expect(manager.responsePromises.length).toBe(1); - // Trigger the queued update - await advanceTimersByTime(1000); - // Second response promise is for the 304 response - expect(manager.responsePromises.length).toBe(2); - await manager.responsePromises[1]; - // Since the response was 304, updateFn should not have been called - expect(updateFn).toBeCalledTimes(0); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - }); - - it('sends if-modified-since using the last observed response last-modified', async () => { - manager.queuedResponses.push( - { - statusCode: 304, - body: '', - headers: {}, - }, - { - statusCode: 200, - body: '{"foo": "bar"}', - headers: { - 'Last-Modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - } - ); - manager.start(); - await manager.onReady(); - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); - await advanceTimersByTime(1000); - expect(makeGetRequestSpy).toBeCalledTimes(1); - const firstCall = makeGetRequestSpy.mock.calls[0]; - const headers = firstCall[1]; - expect(headers).toEqual({ - 'if-modified-since': 'Fri, 08 Mar 2019 18:57:17 GMT', - }); - }); - }); - - describe('backoff', () => { - it('uses the delay from the backoff controller getDelay method when greater than updateInterval', async () => { - const BackoffControllerMock = (BackoffController as unknown) as jest.Mock<BackoffController, []>; - const getDelayMock = BackoffControllerMock.mock.results[0].value.getDelay; - getDelayMock.mockImplementationOnce(() => 5432); - - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); - - manager.queuedResponses.push({ - statusCode: 404, - body: '', - headers: {}, - }); - manager.start(); - await manager.responsePromises[0]; - expect(makeGetRequestSpy).toBeCalledTimes(1); - - // Should not make another request after 1 second because the error should have triggered backoff - advanceTimersByTime(1000); - expect(makeGetRequestSpy).toBeCalledTimes(1); - - // But after another 5 seconds, another request should be made - await advanceTimersByTime(5000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - }); - - it('calls countError on the backoff controller when a non-success status code response is received', async () => { - manager.queuedResponses.push({ - statusCode: 404, - body: '', - headers: {}, - }); - manager.start(); - await manager.responsePromises[0]; - const BackoffControllerMock = (BackoffController as unknown) as jest.Mock<BackoffController, []>; - expect(BackoffControllerMock.mock.results[0].value.countError).toBeCalledTimes(1); - }); - - it('calls countError on the backoff controller when the response promise rejects', async () => { - manager.queuedResponses.push(new Error('Connection failed')); - manager.start(); - try { - await manager.responsePromises[0]; - } catch (e) { - //empty - } - const BackoffControllerMock = (BackoffController as unknown) as jest.Mock<BackoffController, []>; - expect(BackoffControllerMock.mock.results[0].value.countError).toBeCalledTimes(1); - }); - - it('calls reset on the backoff controller when a success status code response is received', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: { - 'Last-Modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - }); - manager.start(); - const BackoffControllerMock = (BackoffController as unknown) as jest.Mock<BackoffController, []>; - // Reset is called in start - we want to check that it is also called after the response, so reset the mock here - BackoffControllerMock.mock.results[0].value.reset.mockReset(); - await manager.onReady(); - expect(BackoffControllerMock.mock.results[0].value.reset).toBeCalledTimes(1); - }); - - it('resets the backoff controller when start is called', async () => { - const BackoffControllerMock = (BackoffController as unknown) as jest.Mock<BackoffController, []>; - manager.start(); - expect(BackoffControllerMock.mock.results[0].value.reset).toBeCalledTimes(1); - try { - await manager.responsePromises[0]; - } catch (e) { - // empty - } - }); - }); - }); - }); - }); - - describe('when constructed with sdkKey and autoUpdate: false', () => { - beforeEach(() => { - manager = new TestDatafileManager({ sdkKey: '123', autoUpdate: false }); - }); - - it('after being started, fetches the datafile and resolves onReady', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - }); - - it('does not schedule a live update after ready', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - const updateFn = jest.fn(); - manager.on('update', updateFn); - manager.start(); - await manager.onReady(); - expect(getTimerCount()).toBe(0); - }); - - // TODO: figure out what's wrong with this test - it.skip('rejects the onReady promise if the initial request promise rejects', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.makeGetRequest = (): AbortableRequest => ({ - abort(): void {}, - responsePromise: Promise.reject(new Error('Could not connect')), - }); - manager.start(); - let didReject = false; - try { - await manager.onReady(); - } catch (e) { - didReject = true; - } - expect(didReject).toBe(true); - }); - }); - - describe('when constructed with sdkKey and a valid urlTemplate', () => { - beforeEach(() => { - manager = new TestDatafileManager({ - sdkKey: '456', - updateInterval: 1000, - urlTemplate: 'https://localhost:5556/datafiles/%s', - }); - }); - - it('uses the urlTemplate to create the url passed to the makeGetRequest method', async () => { - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy.mock.calls[0][0]).toBe('https://localhost:5556/datafiles/456'); - await manager.onReady(); - }); - }); - - describe('when constructed with an update interval below the minimum', () => { - beforeEach(() => { - manager = new TestDatafileManager({ sdkKey: '123', updateInterval: 500, autoUpdate: true }); - }); - - it('uses the default update interval', async () => { - const makeGetRequestSpy = jest.spyOn(manager, 'makeGetRequest'); - - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo3": "bar3"}', - headers: {}, - }); - - manager.start(); - await manager.onReady(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - await advanceTimersByTime(300000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - }); - }); - - describe('when constructed with a cache implementation having an already cached datafile', () => { - beforeEach(() => { - manager = new TestDatafileManager({ - sdkKey: 'keyThatExists', - updateInterval: 500, - autoUpdate: true, - cache: testCache, - }); - manager.simulateResponseDelay = true; - }); - - it('uses cached version of datafile first and resolves the promise while network throws error and no update event is triggered', async () => { - manager.queuedResponses.push(new Error('Connection Error')); - const updateFn = jest.fn(); - manager.on('update', updateFn); - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ name: 'keyThatExists' }); - await advanceTimersByTime(50); - expect(JSON.parse(manager.get())).toEqual({ name: 'keyThatExists' }); - expect(updateFn).toBeCalledTimes(0); - }); - - it('uses cached datafile, resolves ready promise, fetches new datafile from network and triggers update event', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - - const updateFn = jest.fn(); - manager.on('update', updateFn); - manager.start(); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ name: 'keyThatExists' }); - expect(updateFn).toBeCalledTimes(0); - await advanceTimersByTime(50); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - expect(updateFn).toBeCalledTimes(1); - }); - - it('sets newly recieved datafile in to cache', async () => { - const cacheSetSpy = jest.spyOn(testCache, 'set'); - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - manager.start(); - await manager.onReady(); - await advanceTimersByTime(50); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - expect(cacheSetSpy.mock.calls[0][0]).toEqual('opt-datafile-keyThatExists'); - expect(JSON.parse(cacheSetSpy.mock.calls[0][1])).toEqual({ foo: 'bar' }); - }); - }); - - describe('when constructed with a cache implementation without an already cached datafile', () => { - beforeEach(() => { - manager = new TestDatafileManager({ - sdkKey: 'keyThatDoesExists', - updateInterval: 500, - autoUpdate: true, - cache: testCache, - }); - manager.simulateResponseDelay = true; - }); - - it('does not find cached datafile, fetches new datafile from network, resolves promise and does not trigger update event', async () => { - manager.queuedResponses.push({ - statusCode: 200, - body: '{"foo": "bar"}', - headers: {}, - }); - - const updateFn = jest.fn(); - manager.on('update', updateFn); - manager.start(); - await advanceTimersByTime(50); - await manager.onReady(); - expect(JSON.parse(manager.get())).toEqual({ foo: 'bar' }); - expect(updateFn).toBeCalledTimes(0); - }); - }); -}); diff --git a/packages/optimizely-sdk/tests/index.react_native.spec.ts b/packages/optimizely-sdk/tests/index.react_native.spec.ts deleted file mode 100644 index 1717bdf55..000000000 --- a/packages/optimizely-sdk/tests/index.react_native.spec.ts +++ /dev/null @@ -1,340 +0,0 @@ -/** - * Copyright 2019-2020, 2022 Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/// <reference types="jest" /> -import * as logging from '../lib/modules/logging/logger'; -import * as eventProcessor from '../lib//plugins/event_processor/index.react_native'; - -import Optimizely from '../lib/optimizely'; -import testData from '../lib/tests/test_data'; -import packageJSON from '../package.json'; -import optimizelyFactory from '../lib/index.react_native'; -import configValidator from '../lib/utils/config_validator'; -import eventProcessorConfigValidator from '../lib/utils/event_processor_config_validator'; - -describe('javascript-sdk/react-native', () => { - beforeEach(() => { - jest.spyOn(optimizelyFactory.eventDispatcher, 'dispatchEvent'); - jest.useFakeTimers(); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - describe('APIs', () => { - it('should expose logger, errorHandler, eventDispatcher and enums', () => { - expect(optimizelyFactory.logging).toBeDefined(); - expect(optimizelyFactory.logging.createLogger).toBeDefined(); - expect(optimizelyFactory.logging.createNoOpLogger).toBeDefined(); - expect(optimizelyFactory.errorHandler).toBeDefined(); - expect(optimizelyFactory.eventDispatcher).toBeDefined(); - expect(optimizelyFactory.enums).toBeDefined(); - }); - - describe('createInstance', () => { - var fakeErrorHandler = { handleError: function () {} }; - var fakeEventDispatcher = { dispatchEvent: function () {} }; - // @ts-ignore - var silentLogger; - - beforeEach(() => { - // @ts-ignore - silentLogger = optimizelyFactory.logging.createLogger(); - jest.spyOn(console, 'error'); - jest.spyOn(configValidator, 'validate').mockImplementation(() => { - throw new Error('Invalid config or something'); - }); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should not throw if the provided config is not valid', () => { - expect(function () { - var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, - // @ts-ignore - logger: silentLogger, - }); - // Invalid datafile causes onReady Promise rejection - catch this error - // @ts-ignore - optlyInstance.onReady().catch(function () {}); - }).not.toThrow(); - }); - - it('should create an instance of optimizely', () => { - var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - // @ts-ignore - logger: silentLogger, - }); - // Invalid datafile causes onReady Promise rejection - catch this error - // @ts-ignore - optlyInstance.onReady().catch(function () {}); - - expect(optlyInstance).toBeInstanceOf(Optimizely); - // @ts-ignore - expect(optlyInstance.clientVersion).toEqual('4.9.2'); - }); - - it('should set the React Native JS client engine and javascript SDK version', () => { - var optlyInstance = optimizelyFactory.createInstance({ - datafile: {}, - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - // @ts-ignore - logger: silentLogger, - }); - // Invalid datafile causes onReady Promise rejection - catch this error - // @ts-ignore - optlyInstance.onReady().catch(function () {}); - // @ts-ignore - expect('react-native-js-sdk').toEqual(optlyInstance.clientEngine); - // @ts-ignore - expect(packageJSON.version).toEqual(optlyInstance.clientVersion); - }); - - it('should allow passing of "react-sdk" as the clientEngine and convert it to "react-native-sdk"', () => { - var optlyInstance = optimizelyFactory.createInstance({ - clientEngine: 'react-sdk', - datafile: {}, - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - // @ts-ignore - logger: silentLogger, - }); - // Invalid datafile causes onReady Promise rejection - catch this error - // @ts-ignore - optlyInstance.onReady().catch(function () {}); - // @ts-ignore - expect('react-native-sdk').toEqual(optlyInstance.clientEngine); - }); - - describe('when passing in logLevel', () => { - beforeEach(() => { - jest.spyOn(logging, 'setLogLevel'); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should call logging.setLogLevel', () => { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), - logLevel: optimizelyFactory.enums.LOG_LEVEL.ERROR, - }); - expect(logging.setLogLevel).toBeCalledTimes(1); - expect(logging.setLogLevel).toBeCalledWith(optimizelyFactory.enums.LOG_LEVEL.ERROR); - }); - }); - - describe('when passing in logger', () => { - beforeEach(() => { - jest.spyOn(logging, 'setLogHandler'); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it( - 'should call logging.setLogHandler with the supplied logger', - () => { - var fakeLogger = { log: function () { } }; - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfig(), - // @ts-ignore - logger: fakeLogger, - }); - expect(logging.setLogHandler).toBeCalledTimes(1); - expect(logging.setLogHandler).toBeCalledWith(fakeLogger); - } - ); - }); - - describe('event processor configuration', () => { - // @ts-ignore - var eventProcessorSpy; - beforeEach(() => { - eventProcessorSpy = jest.spyOn(eventProcessor, 'createEventProcessor'); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should use default event flush interval when none is provided', () => { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - // @ts-ignore - logger: silentLogger, - }); - - expect( - // @ts-ignore - eventProcessorSpy - ).toBeCalledWith( - expect.objectContaining({ - flushInterval: 1000, - }) - ); - }); - - describe('with an invalid flush interval', () => { - beforeEach(() => { - jest.spyOn(eventProcessorConfigValidator, 'validateEventFlushInterval').mockImplementation(() => false); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should ignore the event flush interval and use the default instead', () => { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - // @ts-ignore - logger: silentLogger, - // @ts-ignore - eventFlushInterval: ['invalid', 'flush', 'interval'], - }); - expect( - // @ts-ignore - eventProcessorSpy - ).toBeCalledWith( - expect.objectContaining({ - flushInterval: 1000, - }) - ); - }); - }); - - describe('with a valid flush interval', () => { - beforeEach(() => { - jest.spyOn(eventProcessorConfigValidator, 'validateEventFlushInterval').mockImplementation(() => true); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should use the provided event flush interval', () => { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - // @ts-ignore - logger: silentLogger, - eventFlushInterval: 9000, - }); - expect( - // @ts-ignore - eventProcessorSpy - ).toBeCalledWith( - expect.objectContaining({ - flushInterval: 9000, - }) - ); - }); - }); - - it('should use default event batch size when none is provided', () => { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - // @ts-ignore - logger: silentLogger, - }); - expect( - // @ts-ignore - eventProcessorSpy - ).toBeCalledWith( - expect.objectContaining({ - batchSize: 10, - }) - ); - }); - - describe('with an invalid event batch size', () => { - beforeEach(() => { - jest.spyOn(eventProcessorConfigValidator, 'validateEventBatchSize').mockImplementation(() => false); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should ignore the event batch size and use the default instead', () => { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - // @ts-ignore - logger: silentLogger, - // @ts-ignore - eventBatchSize: null, - }); - expect( - // @ts-ignore - eventProcessorSpy - ).toBeCalledWith( - expect.objectContaining({ - batchSize: 10, - }) - ); - }); - }); - - describe('with a valid event batch size', () => { - beforeEach(() => { - jest.spyOn(eventProcessorConfigValidator, 'validateEventBatchSize').mockImplementation(() => true); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should use the provided event batch size', () => { - optimizelyFactory.createInstance({ - datafile: testData.getTestProjectConfigWithFeatures(), - errorHandler: fakeErrorHandler, - eventDispatcher: fakeEventDispatcher, - // @ts-ignore - logger: silentLogger, - eventBatchSize: 300, - }); - expect( - // @ts-ignore - eventProcessorSpy - ).toBeCalledWith( - expect.objectContaining({ - batchSize: 300, - }) - ); - }); - }); - }); - }); - }); -}); diff --git a/packages/optimizely-sdk/tests/logger.spec.ts b/packages/optimizely-sdk/tests/logger.spec.ts deleted file mode 100644 index f34afbb0e..000000000 --- a/packages/optimizely-sdk/tests/logger.spec.ts +++ /dev/null @@ -1,386 +0,0 @@ -/// <reference types="jest" /> -import { - LogLevel, - LogHandler, - LoggerFacade, -} from '../lib/modules/logging/models' - -import { - setLogHandler, - setLogLevel, - getLogger, - ConsoleLogHandler, - resetLogger, - getLogLevel, -} from '../lib/modules/logging/logger' - -import { resetErrorHandler } from '../lib/modules/logging/errorHandler' -import { ErrorHandler, setErrorHandler } from '../lib/modules/logging/errorHandler' - -describe('logger', () => { - afterEach(() => { - resetLogger() - resetErrorHandler() - }) - - describe('OptimizelyLogger', () => { - let stubLogger: LogHandler - let logger: LoggerFacade - let stubErrorHandler: ErrorHandler - - beforeEach(() => { - stubLogger = { - log: jest.fn(), - } - stubErrorHandler = { - handleError: jest.fn(), - } - setLogLevel(LogLevel.DEBUG) - setLogHandler(stubLogger) - setErrorHandler(stubErrorHandler) - logger = getLogger() - }) - - describe('setLogLevel', () => { - it('should coerce "debug"', () => { - setLogLevel('debug') - expect(getLogLevel()).toBe(LogLevel.DEBUG) - }) - - it('should coerce "deBug"', () => { - setLogLevel('deBug') - expect(getLogLevel()).toBe(LogLevel.DEBUG) - }) - - it('should coerce "INFO"', () => { - setLogLevel('INFO') - expect(getLogLevel()).toBe(LogLevel.INFO) - }) - - it('should coerce "WARN"', () => { - setLogLevel('WARN') - expect(getLogLevel()).toBe(LogLevel.WARNING) - }) - - it('should coerce "warning"', () => { - setLogLevel('warning') - expect(getLogLevel()).toBe(LogLevel.WARNING) - }) - - it('should coerce "ERROR"', () => { - setLogLevel('WARN') - expect(getLogLevel()).toBe(LogLevel.WARNING) - }) - - it('should default to error if invalid', () => { - setLogLevel('invalid') - expect(getLogLevel()).toBe(LogLevel.ERROR) - }) - }) - - describe('getLogger(name)', () => { - it('should prepend the name in the log messages', () => { - const myLogger = getLogger('doit') - myLogger.info('test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'doit: test') - }) - }) - - describe('logger.log(level, msg)', () => { - it('should work with a string logLevel', () => { - setLogLevel(LogLevel.INFO) - logger.log('info', 'test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test') - }) - - it('should call the loggerBackend when the message logLevel is equal to the configured logLevel threshold', () => { - setLogLevel(LogLevel.INFO) - logger.log(LogLevel.INFO, 'test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test') - }) - - it('should call the loggerBackend when the message logLevel is above to the configured logLevel threshold', () => { - setLogLevel(LogLevel.INFO) - logger.log(LogLevel.WARNING, 'test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test') - }) - - it('should not call the loggerBackend when the message logLevel is above to the configured logLevel threshold', () => { - setLogLevel(LogLevel.INFO) - logger.log(LogLevel.DEBUG, 'test') - - expect(stubLogger.log).toHaveBeenCalledTimes(0) - }) - - it('should not throw if loggerBackend is not supplied', () => { - setLogLevel(LogLevel.INFO) - logger.log(LogLevel.ERROR, 'test') - }) - }) - - describe('logger.info', () => { - it('should handle info(message)', () => { - logger.info('test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test') - }) - it('should handle info(message, ...splat)', () => { - logger.info('test: %s %s', 'hey', 'jude') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test: hey jude') - }) - - it('should handle info(message, ...splat, error)', () => { - const error = new Error('hey') - logger.info('test: %s', 'hey', error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'test: hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - - it('should handle info(error)', () => { - const error = new Error('hey') - logger.info(error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.INFO, 'hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - }) - - describe('logger.debug', () => { - it('should handle debug(message)', () => { - logger.debug('test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'test') - }) - - it('should handle debug(message, ...splat)', () => { - logger.debug('test: %s', 'hey') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'test: hey') - }) - - it('should handle debug(message, ...splat, error)', () => { - const error = new Error('hey') - logger.debug('test: %s', 'hey', error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'test: hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - - it('should handle debug(error)', () => { - const error = new Error('hey') - logger.debug(error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.DEBUG, 'hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - }) - - describe('logger.warn', () => { - it('should handle warn(message)', () => { - logger.warn('test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test') - }) - - it('should handle warn(message, ...splat)', () => { - logger.warn('test: %s', 'hey') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test: hey') - }) - - it('should handle warn(message, ...splat, error)', () => { - const error = new Error('hey') - logger.warn('test: %s', 'hey', error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'test: hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - - it('should handle info(error)', () => { - const error = new Error('hey') - logger.warn(error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.WARNING, 'hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - }) - - describe('logger.error', () => { - it('should handle error(message)', () => { - logger.error('test') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'test') - }) - - it('should handle error(message, ...splat)', () => { - logger.error('test: %s', 'hey') - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'test: hey') - }) - - it('should handle error(message, ...splat, error)', () => { - const error = new Error('hey') - logger.error('test: %s', 'hey', error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'test: hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - - it('should handle error(error)', () => { - const error = new Error('hey') - logger.error(error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'hey') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - - it('should work with an insufficient amount of splat args error(msg, ...splat, message)', () => { - const error = new Error('hey') - logger.error('hey %s', error) - - expect(stubLogger.log).toHaveBeenCalledTimes(1) - expect(stubLogger.log).toHaveBeenCalledWith(LogLevel.ERROR, 'hey undefined') - expect(stubErrorHandler.handleError).toHaveBeenCalledWith(error) - }) - }) - - describe('using ConsoleLoggerHandler', () => { - beforeEach(() => { - jest.spyOn(console, 'info').mockImplementation(() => {}) - }) - - afterEach(() => { - jest.resetAllMocks() - }) - - it('should work with BasicLogger', () => { - const logger = new ConsoleLogHandler() - const TIME = '12:00' - setLogHandler(logger) - setLogLevel(LogLevel.INFO) - jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) - - logger.log(LogLevel.INFO, 'hey') - - expect(console.info).toBeCalledTimes(1) - expect(console.info).toBeCalledWith('[OPTIMIZELY] - INFO 12:00 hey') - }) - - it('should set logLevel to ERROR when setLogLevel is called with invalid value', () => { - const logger = new ConsoleLogHandler() - logger.setLogLevel('invalid' as any) - - expect(logger.logLevel).toEqual(LogLevel.ERROR) - }) - - it('should set logLevel to ERROR when setLogLevel is called with no value', () => { - const logger = new ConsoleLogHandler() - // @ts-ignore - logger.setLogLevel() - - expect(logger.logLevel).toEqual(LogLevel.ERROR) - }) - }) - }) - - describe('ConsoleLogger', function() { - beforeEach(() => { - jest.spyOn(console, 'info') - jest.spyOn(console, 'log') - jest.spyOn(console, 'warn') - jest.spyOn(console, 'error') - }) - - afterEach(() => { - jest.resetAllMocks() - }) - - it('should log to console.info for LogLevel.INFO', () => { - const logger = new ConsoleLogHandler({ - logLevel: LogLevel.DEBUG, - }) - const TIME = '12:00' - jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) - - logger.log(LogLevel.INFO, 'test') - - expect(console.info).toBeCalledTimes(1) - expect(console.info).toBeCalledWith('[OPTIMIZELY] - INFO 12:00 test') - }) - - it('should log to console.log for LogLevel.DEBUG', () => { - const logger = new ConsoleLogHandler({ - logLevel: LogLevel.DEBUG, - }) - const TIME = '12:00' - jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) - - logger.log(LogLevel.DEBUG, 'debug') - - expect(console.log).toBeCalledTimes(1) - expect(console.log).toBeCalledWith('[OPTIMIZELY] - DEBUG 12:00 debug') - }) - - it('should log to console.warn for LogLevel.WARNING', () => { - const logger = new ConsoleLogHandler({ - logLevel: LogLevel.DEBUG, - }) - const TIME = '12:00' - jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) - - logger.log(LogLevel.WARNING, 'warning') - - expect(console.warn).toBeCalledTimes(1) - expect(console.warn).toBeCalledWith('[OPTIMIZELY] - WARN 12:00 warning') - }) - - it('should log to console.error for LogLevel.ERROR', () => { - const logger = new ConsoleLogHandler({ - logLevel: LogLevel.DEBUG, - }) - const TIME = '12:00' - jest.spyOn(logger, 'getTime').mockImplementation(() => TIME) - - logger.log(LogLevel.ERROR, 'error') - - expect(console.error).toBeCalledTimes(1) - expect(console.error).toBeCalledWith('[OPTIMIZELY] - ERROR 12:00 error') - }) - - it('should not log if the configured logLevel is higher', () => { - const logger = new ConsoleLogHandler({ - logLevel: LogLevel.INFO, - }) - - logger.log(LogLevel.DEBUG, 'debug') - - expect(console.log).toBeCalledTimes(0) - }) - }) -}) diff --git a/packages/optimizely-sdk/tests/nodeDatafileManager.spec.ts b/packages/optimizely-sdk/tests/nodeDatafileManager.spec.ts deleted file mode 100644 index 14fb49d05..000000000 --- a/packages/optimizely-sdk/tests/nodeDatafileManager.spec.ts +++ /dev/null @@ -1,186 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import NodeDatafileManager from '../lib/modules/datafile-manager/nodeDatafileManager'; -import * as nodeRequest from '../lib/modules/datafile-manager/nodeRequest'; -import { Headers, AbortableRequest } from '../lib/modules/datafile-manager/http'; -import { advanceTimersByTime, getTimerCount } from './testUtils'; - -describe('nodeDatafileManager', () => { - let makeGetRequestSpy: jest.SpyInstance<AbortableRequest, [string, Headers]>; - beforeEach(() => { - jest.useFakeTimers(); - makeGetRequestSpy = jest.spyOn(nodeRequest, 'makeGetRequest'); - }); - - afterEach(() => { - jest.restoreAllMocks(); - jest.clearAllTimers(); - }); - - it('calls nodeEnvironment.makeGetRequest when started', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - - const manager = new NodeDatafileManager({ - sdkKey: '1234', - autoUpdate: false, - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy.mock.calls[0][0]).toBe('https://cdn.optimizely.com/datafiles/1234.json'); - expect(makeGetRequestSpy.mock.calls[0][1]).toEqual({}); - - await manager.onReady(); - await manager.stop(); - }); - - it('calls nodeEnvironment.makeGetRequest for live update requests', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'last-modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - autoUpdate: true, - }); - manager.start(); - await manager.onReady(); - await advanceTimersByTime(300000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - expect(makeGetRequestSpy.mock.calls[1][0]).toBe('https://cdn.optimizely.com/datafiles/1234.json'); - expect(makeGetRequestSpy.mock.calls[1][1]).toEqual({ - 'if-modified-since': 'Fri, 08 Mar 2019 18:57:17 GMT', - }); - - await manager.stop(); - }); - - it('defaults to true for autoUpdate', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'last-modified': 'Fri, 08 Mar 2019 18:57:17 GMT', - }, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - }); - manager.start(); - await manager.onReady(); - // Should set a timeout for a later update - expect(getTimerCount()).toBe(1); - await advanceTimersByTime(300000); - expect(makeGetRequestSpy).toBeCalledTimes(2); - - await manager.stop(); - }); - - it('uses authenticated default datafile url when auth token is provided', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - datafileAccessToken: 'abcdefgh', - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy).toBeCalledWith( - 'https://config.optimizely.com/datafiles/auth/1234.json', - expect.anything() - ); - await manager.stop(); - }); - - it('uses public default datafile url when auth token is not provided', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy).toBeCalledWith('https://cdn.optimizely.com/datafiles/1234.json', expect.anything()); - await manager.stop(); - }); - - it('adds authorization header with bearer token when auth token is provided', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - datafileAccessToken: 'abcdefgh', - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy).toBeCalledWith(expect.anything(), { Authorization: 'Bearer abcdefgh' }); - await manager.stop(); - }); - - it('prefers user provided url template over defaults', async () => { - makeGetRequestSpy.mockReturnValue({ - abort: jest.fn(), - responsePromise: Promise.resolve({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }), - }); - const manager = new NodeDatafileManager({ - sdkKey: '1234', - datafileAccessToken: 'abcdefgh', - urlTemplate: 'https://myawesomeurl/', - }); - manager.start(); - expect(makeGetRequestSpy).toBeCalledTimes(1); - expect(makeGetRequestSpy).toBeCalledWith('https://myawesomeurl/', expect.anything()); - await manager.stop(); - }); -}); diff --git a/packages/optimizely-sdk/tests/nodeRequest.spec.ts b/packages/optimizely-sdk/tests/nodeRequest.spec.ts deleted file mode 100644 index ed5e0f0b2..000000000 --- a/packages/optimizely-sdk/tests/nodeRequest.spec.ts +++ /dev/null @@ -1,216 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import nock from 'nock'; -import zlib from 'zlib'; -import { makeGetRequest } from '../lib/modules/datafile-manager/nodeRequest'; -import { advanceTimersByTime } from './testUtils'; - -beforeAll(() => { - nock.disableNetConnect(); -}); - -afterAll(() => { - nock.enableNetConnect(); -}); - -describe('nodeEnvironment', () => { - const host = 'https://cdn.optimizely.com/'; - const path = '/datafiles/123.json'; - - afterEach(async () => { - nock.cleanAll(); - }); - - describe('makeGetRequest', () => { - it('returns a 200 response back to its superclass', async () => { - const scope = nock(host) - .get(path) - .reply(200, '{"foo":"bar"}'); - const req = makeGetRequest(`${host}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }); - scope.done(); - }); - - it('returns a 404 response back to its superclass', async () => { - const scope = nock(host) - .get(path) - .reply(404, ''); - const req = makeGetRequest(`${host}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 404, - body: '', - headers: {}, - }); - scope.done(); - }); - - it('includes headers from the headers argument in the request', async () => { - const scope = nock(host) - .matchHeader('if-modified-since', 'Fri, 08 Mar 2019 18:57:18 GMT') - .get(path) - .reply(304, ''); - const req = makeGetRequest(`${host}${path}`, { - 'if-modified-since': 'Fri, 08 Mar 2019 18:57:18 GMT', - }); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 304, - body: '', - headers: {}, - }); - scope.done(); - }); - - it('adds an Accept-Encoding request header and unzips a gzipped response body', async () => { - const scope = nock(host) - .matchHeader('accept-encoding', 'gzip,deflate') - .get(path) - .reply(200, () => zlib.gzipSync('{"foo":"bar"}'), { 'content-encoding': 'gzip' }); - const req = makeGetRequest(`${host}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toMatchObject({ - statusCode: 200, - body: '{"foo":"bar"}', - }); - scope.done(); - }); - - it('includes headers from the response in the eventual response in the return value', async () => { - const scope = await nock(host) - .get(path) - .reply( - 200, - { foo: 'bar' }, - { - 'last-modified': 'Fri, 08 Mar 2019 18:57:18 GMT', - } - ); - const req = await makeGetRequest(`${host}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: { - 'content-type': 'application/json', - 'last-modified': 'Fri, 08 Mar 2019 18:57:18 GMT', - }, - }); - scope.done(); - }); - - it('handles a URL with a query string', async () => { - const pathWithQuery = '/datafiles/123.json?from_my_app=true'; - const scope = nock(host) - .get(pathWithQuery) - .reply(200, { foo: 'bar' }); - const req = makeGetRequest(`${host}${pathWithQuery}`, {}); - await req.responsePromise; - scope.done(); - }); - - it('handles a URL with http protocol (not https)', async () => { - const httpHost = 'http://cdn.optimizely.com/'; - const scope = nock(httpHost) - .get(path) - .reply(200, '{"foo":"bar"}'); - const req = makeGetRequest(`${httpHost}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }); - scope.done(); - }); - - it('returns a rejected response promise when the URL protocol is unsupported', async () => { - const invalidProtocolUrl = 'ftp://something/datafiles/123.json'; - const req = makeGetRequest(invalidProtocolUrl, {}); - await expect(req.responsePromise).rejects.toThrow(); - }); - - it('returns a rejected promise when there is a request error', async () => { - const scope = nock(host) - .get(path) - .replyWithError({ - message: 'Connection error', - code: 'CONNECTION_ERROR', - }); - const req = makeGetRequest(`${host}${path}`, {}); - await expect(req.responsePromise).rejects.toThrow(); - scope.done(); - }); - - it('handles a url with a host and a port', async () => { - const hostWithPort = 'http://datafiles:3000/'; - const path = '/12/345.json'; - const scope = nock(hostWithPort) - .get(path) - .reply(200, '{"foo":"bar"}'); - const req = makeGetRequest(`${hostWithPort}${path}`, {}); - const resp = await req.responsePromise; - expect(resp).toEqual({ - statusCode: 200, - body: '{"foo":"bar"}', - headers: {}, - }); - scope.done(); - }); - - describe('timeout', () => { - beforeEach(() => { - jest.useFakeTimers(); - }); - - afterEach(() => { - jest.clearAllTimers(); - }); - - it('rejects the response promise and aborts the request when the response is not received before the timeout', async () => { - const scope = nock(host) - .get(path) - .delay(61000) - .reply(200, '{"foo":"bar"}'); - - const abortEventListener = jest.fn(); - let emittedReq: any; - const requestListener = (request: any): void => { - emittedReq = request; - emittedReq.once('abort', abortEventListener); - }; - scope.on('request', requestListener); - - const req = makeGetRequest(`${host}${path}`, {}); - await advanceTimersByTime(60000); - await expect(req.responsePromise).rejects.toThrow(); - expect(abortEventListener).toBeCalledTimes(1); - - scope.done(); - if (emittedReq) { - emittedReq.off('abort', abortEventListener); - } - scope.off('request', requestListener); - }); - }); - }); -}); diff --git a/packages/optimizely-sdk/tests/odpEventManager.spec.ts b/packages/optimizely-sdk/tests/odpEventManager.spec.ts deleted file mode 100644 index 12ee14a84..000000000 --- a/packages/optimizely-sdk/tests/odpEventManager.spec.ts +++ /dev/null @@ -1,379 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import { OdpConfig } from '../lib/plugins/odp/odp_config'; -import { OdpEventManager, STATE } from '../lib/plugins/odp/odp_event_manager'; -import { anything, capture, instance, mock, resetCalls, spy, verify, when } from 'ts-mockito'; -import { RestApiManager } from '../lib/plugins/odp/rest_api_manager'; -import { LogHandler, LogLevel } from '../lib/modules/logging'; -import { OdpEvent } from '../lib/plugins/odp/odp_event'; -import { RequestHandler } from '../lib/utils/http_request_handler/http'; - -const API_KEY = 'test-api-key'; -const API_HOST = 'https://odp.example.com/'; -const MOCK_IDEMPOTENCE_ID = 'c1dc758e-f095-4f09-9b49-172d74c53880'; -const EVENTS: OdpEvent[] = [ - new OdpEvent( - 't1', - 'a1', - new Map([['id-key-1', 'id-value-1']]), - new Map(Object.entries({ - 'key-1': 'value1', - 'key-2': null, - 'key-3': 3.3, - 'key-4': true, - })), - ), - new OdpEvent( - 't2', - 'a2', - new Map([['id-key-2', 'id-value-2']]), - new Map(Object.entries({ - 'key-2': 'value2', - 'data_source': 'my-source', - })), - ), -]; -// naming for object destructuring -const clientEngine = 'javascript-sdk'; -const clientVersion = '4.9.2'; -const PROCESSED_EVENTS: OdpEvent[] = [ - new OdpEvent( - 't1', - 'a1', - new Map([['id-key-1', 'id-value-1']]), - new Map(Object.entries({ - 'idempotence_id': MOCK_IDEMPOTENCE_ID, - 'data_source_type': 'sdk', - 'data_source': clientEngine, - 'data_source_version': clientVersion, - 'key-1': 'value1', - 'key-2': null, - 'key-3': 3.3, - 'key-4': true, - })), - ), - new OdpEvent( - 't2', - 'a2', - new Map([['id-key-2', 'id-value-2']]), - new Map(Object.entries({ - 'idempotence_id': MOCK_IDEMPOTENCE_ID, - 'data_source_type': 'sdk', - 'data_source': clientEngine, - 'data_source_version': clientVersion, - 'key-2': 'value2', - })), - ), -]; -const makeEvent = (id: number) => { - const identifiers = new Map<string, string>(); - identifiers.set('identifier1', 'value1-' + id); - identifiers.set('identifier2', 'value2-' + id); - - const data = new Map<string, unknown>(); - data.set('data1', 'data-value1-' + id); - data.set('data2', id); - - return new OdpEvent('test-type-' + id, 'test-action-' + id, identifiers, data); -}; -const pause = (timeoutMilliseconds: number): Promise<void> => { - return new Promise(resolve => setTimeout(resolve, timeoutMilliseconds)); -}; -const abortableRequest = (statusCode: number, body: string) => { - return { - abort: () => { - }, - responsePromise: Promise.resolve({ - statusCode, - body, - headers: {}, - }), - }; -}; - -describe('OdpEventManager', () => { - let mockLogger: LogHandler; - let mockApiManager: RestApiManager; - - let odpConfig: OdpConfig; - let logger: LogHandler; - let apiManager: RestApiManager; - - beforeAll(() => { - mockLogger = mock<LogHandler>(); - mockApiManager = mock<RestApiManager>(); - - odpConfig = new OdpConfig(API_KEY, API_HOST, []); - logger = instance(mockLogger); - apiManager = instance(mockApiManager); - }); - - beforeEach(() => { - resetCalls(mockLogger); - resetCalls(mockApiManager); - }); - - it('should log and discard events when event manager not running', () => { - const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, - }); - // since we've not called start() then... - - eventManager.sendEvent(EVENTS[0]); - - // ...we should get a notice after trying to send an event - verify(mockLogger.log(LogLevel.WARNING, 'Failed to Process ODP Event. ODPEventManager is not running.')).once(); - }); - - it('should log and discard events when event manager config is not ready', () => { - const mockOdpConfig = mock<OdpConfig>(); - when(mockOdpConfig.isReady()).thenReturn(false); - const odpConfig = instance(mockOdpConfig); - const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, - }); - eventManager['state'] = STATE.RUNNING; // simulate running without calling start() - - eventManager.sendEvent(EVENTS[0]); - - verify(mockLogger.log(LogLevel.DEBUG, 'Unable to Process ODP Event. ODPConfig is not ready.')).once(); - }); - - it('should discard events with invalid data', () => { - const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, - }); - // make an event with invalid data key-value entry - const badEvent = new OdpEvent( - 't3', - 'a3', - new Map([['id-key-3', 'id-value-3']]), - new Map(Object.entries({ - 'key-1': false, - 'key-2': { random: 'object', whichShouldFail: true }, - })), - ); - eventManager.sendEvent(badEvent); - - verify(mockLogger.log(LogLevel.ERROR, 'Event data found to be invalid.')).once(); - }); - - it('should log a max queue hit and discard ', () => { - // set queue to maximum of 1 - const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, queueSize: 1, // With max queue size set to 1... - }); - eventManager['state'] = STATE.RUNNING; - eventManager['queue'].push(EVENTS[0]); // simulate 1 event already in the queue then... - - // ...try adding the second event - eventManager.sendEvent(EVENTS[1]); - - verify(mockLogger.log(LogLevel.WARNING, 'Failed to Process ODP Event. Event Queue full. queueSize = %s.', 1)).once(); - }); - - it('should add additional information to each event', () => { - const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, - }); - const processedEventData = PROCESSED_EVENTS[0].data; - - const eventData = eventManager['augmentCommonData'](EVENTS[0].data); - - expect((eventData.get('idempotence_id') as string).length).toEqual((processedEventData.get('idempotence_id') as string).length); - expect(eventData.get('data_source_type')).toEqual(processedEventData.get('data_source_type')); - expect(eventData.get('data_source')).toEqual(processedEventData.get('data_source')); - expect(eventData.get('data_source_version')).toEqual(processedEventData.get('data_source_version')); - expect(eventData.get('key-1')).toEqual(processedEventData.get('key-1')); - expect(eventData.get('key-2')).toEqual(processedEventData.get('key-2')); - expect(eventData.get('key-3')).toEqual(processedEventData.get('key-3')); - expect(eventData.get('key-4')).toEqual(processedEventData.get('key-4')); - }); - - it('should attempt to flush an empty queue at flush intervals', async () => { - const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, - flushInterval: 100, - }); - const spiedEventManager = spy(eventManager); - - eventManager.start(); - // do not add events to the queue, but allow for... - await pause(400); // at least 3 flush intervals executions (giving a little longer) - - verify(spiedEventManager['processQueue'](anything())).atLeast(3); - }); - - it('should dispatch events in correct number of batches', async () => { - when(mockApiManager.sendEvents(anything(), anything(), anything())).thenResolve(false); - const apiManager = instance(mockApiManager); - const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, - batchSize: 10, // with batch size of 10... - flushInterval: 250, - }); - - eventManager.start(); - for (let i = 0; i < 25; i += 1) { - eventManager.sendEvent(makeEvent(i)); - } - await pause(1500); - - // ...there should be 3 batches: - // batch #1 with 10, batch #2 with 10, and batch #3 (after flushInterval lapsed) with 5 = 25 events - verify(mockApiManager.sendEvents(anything(), anything(), anything())).thrice(); - }); - - it('should dispatch events with correct payload', async () => { - const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, batchSize: 10, flushInterval: 100, - }); - - eventManager.start(); - EVENTS.forEach(event => eventManager.sendEvent(event)); - await pause(1000); - - // sending 1 batch of 2 events after flushInterval since batchSize is 10 - verify(mockApiManager.sendEvents(anything(), anything(), anything())).once(); - const [apiKey, apiHost, events] = capture(mockApiManager.sendEvents).last(); - expect(apiKey).toEqual(API_KEY); - expect(apiHost).toEqual(API_HOST); - expect(events.length).toEqual(2); - expect(events[0].identifiers.size).toEqual(PROCESSED_EVENTS[0].identifiers.size); - expect(events[0].data.size).toEqual(PROCESSED_EVENTS[0].data.size); - expect(events[1].identifiers.size).toEqual(PROCESSED_EVENTS[1].identifiers.size); - expect(events[1].data.size).toEqual(PROCESSED_EVENTS[1].data.size); - }); - - it('should retry failed events', async () => { - // all events should fail ie shouldRetry = true - when(mockApiManager.sendEvents(anything(), anything(), anything())).thenResolve(true); - const apiManager = instance(mockApiManager); - const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, - batchSize: 2, // batch size of 2 - flushInterval: 100, - }); - - eventManager.start(); - // send 4 events - for (let i = 0; i < 4; i += 1) { - eventManager.sendEvent(makeEvent(i)); - } - await pause(1500); - - // retry 3x (default) for 2 batches or 6 calls to attempt to process - verify(mockApiManager.sendEvents(anything(), anything(), anything())).times(6); - }); - - it('should flush all scheduled events before stopping', async () => { - when(mockApiManager.sendEvents(anything(), anything(), anything())).thenResolve(false); - const apiManager = instance(mockApiManager); - const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, - batchSize: 2, // batches of 2 with... - flushInterval: 100, - }); - - eventManager.start(); - // ...25 events should... - for (let i = 0; i < 25; i += 1) { - eventManager.sendEvent(makeEvent(i)); - } - await pause(300); - await eventManager.stop(); - - verify(mockLogger.log(LogLevel.DEBUG, 'Stop requested.')).once(); - verify(mockLogger.log(LogLevel.DEBUG, 'Stopped. Queue Count: %s', 0)).once(); - }); - - it('should prepare correct payload for register VUID', async () => { - const mockRequestHandler: RequestHandler = mock<RequestHandler>(); - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(200, '')); - const apiManager = new RestApiManager(instance(mockRequestHandler), logger); - const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, batchSize: 10, flushInterval: 100, - }); - const vuid = 'vuid_330e05cad15746d9af8a75b8d10'; - - eventManager.start(); - eventManager.registerVuid(vuid); - await pause(1500); - - const [requestUrl, headers, method, data] = capture(mockRequestHandler.makeRequest).last(); - expect(requestUrl).toEqual(`${API_HOST}/v3/events`); - expect(headers['Content-Type']).toEqual('application/json'); - expect(headers['x-api-key']).toEqual('test-api-key'); - expect(method).toEqual('POST'); - const events = JSON.parse(data as string); - const event = events[0]; - expect(event.type).toEqual('fullstack'); - expect(event.action).toEqual('client_initialized'); - expect(event.identifiers).toEqual({ 'vuid': vuid }); - expect(event.data.idempotence_id.length).toBe(36); // uuid length - expect(event.data.data_source_type).toEqual('sdk'); - expect(event.data.data_source).toEqual('javascript-sdk'); - expect(event.data.data_source_version).not.toBeNull(); - }); - - it('should prepare correct payload for identify user', async () => { - const mockRequestHandler: RequestHandler = mock<RequestHandler>(); - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(200, '')); - const apiManager = new RestApiManager(instance(mockRequestHandler), logger); - const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, flushInterval: 100, - }); - const vuid = 'vuid_330e05cad15746d9af8a75b8d10'; - const fsUserId = 'test-fs-user-id'; - - eventManager.start(); - eventManager.identifyUser(fsUserId, vuid); - await pause(1500); - - const [requestUrl, headers, method, data] = capture(mockRequestHandler.makeRequest).last(); - expect(requestUrl).toEqual(`${API_HOST}/v3/events`); - expect(headers['Content-Type']).toEqual('application/json'); - expect(headers['x-api-key']).toEqual('test-api-key'); - expect(method).toEqual('POST'); - const events = JSON.parse(data as string); - const event = events[0]; - expect(event.type).toEqual('fullstack'); - expect(event.action).toEqual('identified'); - expect(event.identifiers).toEqual({ 'vuid': vuid, 'fs_user_id': fsUserId }); - expect(event.data.idempotence_id.length).toBe(36); // uuid length - expect(event.data.data_source_type).toEqual('sdk'); - expect(event.data.data_source).toEqual('javascript-sdk'); - expect(event.data.data_source_version).not.toBeNull(); - }); - - it('should apply updated ODP configuration when available', () => { - const eventManager = new OdpEventManager({ - odpConfig, apiManager, logger, clientEngine, clientVersion, - }); - const apiKey = 'testing-api-key'; - const apiHost = 'https://some.other.example.com/'; - const segmentsToCheck = ['empty-cart', '1-item-cart']; - const differentOdpConfig = new OdpConfig(apiKey, apiHost, segmentsToCheck); - - eventManager.updateSettings(differentOdpConfig); - - expect(eventManager['odpConfig'].apiKey).toEqual(apiKey); - expect(eventManager['odpConfig'].apiHost).toEqual(apiHost); - expect(eventManager['odpConfig'].segmentsToCheck).toContain(segmentsToCheck[0]); - expect(eventManager['odpConfig'].segmentsToCheck).toContain(segmentsToCheck[1]); - }); -}); diff --git a/packages/optimizely-sdk/tests/pendingEventsDispatcher.spec.ts b/packages/optimizely-sdk/tests/pendingEventsDispatcher.spec.ts deleted file mode 100644 index 214b1e939..000000000 --- a/packages/optimizely-sdk/tests/pendingEventsDispatcher.spec.ts +++ /dev/null @@ -1,261 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/// <reference types="jest" /> - -jest.mock('../lib/utils/fns', () => ({ - __esModule: true, - uuid: jest.fn(), - getTimestamp: jest.fn(), - objectValues: jest.requireActual('../lib/utils/fns').objectValues, -})) - -import { - LocalStoragePendingEventsDispatcher, - PendingEventsDispatcher, - DispatcherEntry, -} from '../lib/modules/event_processor/pendingEventsDispatcher' -import { EventDispatcher, EventV1Request } from '../lib/modules/event_processor/eventDispatcher' -import { EventV1 } from '../lib/modules/event_processor/v1/buildEventV1' -import { PendingEventsStore, LocalStorageStore } from '../lib/modules/event_processor/pendingEventsStore' -import { uuid, getTimestamp } from '../lib/utils/fns' - -describe('LocalStoragePendingEventsDispatcher', () => { - let originalEventDispatcher: EventDispatcher - let pendingEventsDispatcher: PendingEventsDispatcher - - beforeEach(() => { - originalEventDispatcher = { - dispatchEvent: jest.fn(), - } - pendingEventsDispatcher = new LocalStoragePendingEventsDispatcher({ - eventDispatcher: originalEventDispatcher, - }) - ;((getTimestamp as unknown) as jest.Mock).mockReturnValue(1) - ;((uuid as unknown) as jest.Mock).mockReturnValue('uuid') - }) - - afterEach(() => { - localStorage.clear() - }) - - it('should properly send the events to the passed in eventDispatcher, when callback statusCode=200', () => { - const callback = jest.fn() - const eventV1Request: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event' } as unknown) as EventV1, - } - - pendingEventsDispatcher.dispatchEvent(eventV1Request, callback) - - expect(callback).not.toHaveBeenCalled() - // manually invoke original eventDispatcher callback - const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) - .mock.calls[0] - internalDispatchCall[1]({ statusCode: 200 }) - - // assert that the original dispatch function was called with the request - expect((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock).toBeCalledTimes(1) - expect(internalDispatchCall[0]).toEqual(eventV1Request) - - // assert that the passed in callback to pendingEventsDispatcher was called - expect(callback).toHaveBeenCalledTimes(1) - expect(callback).toHaveBeenCalledWith({ statusCode: 200 }) - }) - - it('should properly send the events to the passed in eventDispatcher, when callback statusCode=400', () => { - const callback = jest.fn() - const eventV1Request: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event' } as unknown) as EventV1, - } - - pendingEventsDispatcher.dispatchEvent(eventV1Request, callback) - - expect(callback).not.toHaveBeenCalled() - // manually invoke original eventDispatcher callback - const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) - .mock.calls[0] - internalDispatchCall[1]({ statusCode: 400 }) - - // assert that the original dispatch function was called with the request - expect((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock).toBeCalledTimes(1) - expect(internalDispatchCall[0]).toEqual(eventV1Request) - - // assert that the passed in callback to pendingEventsDispatcher was called - expect(callback).toHaveBeenCalledTimes(1) - expect(callback).toHaveBeenCalledWith({ statusCode: 400}) - }) -}) - -describe('PendingEventsDispatcher', () => { - let originalEventDispatcher: EventDispatcher - let pendingEventsDispatcher: PendingEventsDispatcher - let store: PendingEventsStore<DispatcherEntry> - - beforeEach(() => { - originalEventDispatcher = { - dispatchEvent: jest.fn(), - } - store = new LocalStorageStore({ - key: 'test', - maxValues: 3, - }) - pendingEventsDispatcher = new PendingEventsDispatcher({ - store, - eventDispatcher: originalEventDispatcher, - }) - ;((getTimestamp as unknown) as jest.Mock).mockReturnValue(1) - ;((uuid as unknown) as jest.Mock).mockReturnValue('uuid') - }) - - afterEach(() => { - localStorage.clear() - }) - - describe('dispatch', () => { - describe('when the dispatch is successful', () => { - it('should save the pendingEvent to the store and remove it once dispatch is completed', () => { - const callback = jest.fn() - const eventV1Request: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event' } as unknown) as EventV1, - } - - pendingEventsDispatcher.dispatchEvent(eventV1Request, callback) - - expect(store.values()).toHaveLength(1) - expect(store.get('uuid')).toEqual({ - uuid: 'uuid', - timestamp: 1, - request: eventV1Request, - }) - expect(callback).not.toHaveBeenCalled() - - // manually invoke original eventDispatcher callback - const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) - .mock.calls[0] - const internalCallback = internalDispatchCall[1]({ statusCode: 200 }) - - // assert that the original dispatch function was called with the request - expect( - (originalEventDispatcher.dispatchEvent as unknown) as jest.Mock, - ).toBeCalledTimes(1) - expect(internalDispatchCall[0]).toEqual(eventV1Request) - - // assert that the passed in callback to pendingEventsDispatcher was called - expect(callback).toHaveBeenCalledTimes(1) - expect(callback).toHaveBeenCalledWith({ statusCode: 200 }) - - expect(store.values()).toHaveLength(0) - }) - }) - - describe('when the dispatch is unsuccessful', () => { - it('should save the pendingEvent to the store and remove it once dispatch is completed', () => { - const callback = jest.fn() - const eventV1Request: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event' } as unknown) as EventV1, - } - - pendingEventsDispatcher.dispatchEvent(eventV1Request, callback) - - expect(store.values()).toHaveLength(1) - expect(store.get('uuid')).toEqual({ - uuid: 'uuid', - timestamp: 1, - request: eventV1Request, - }) - expect(callback).not.toHaveBeenCalled() - - // manually invoke original eventDispatcher callback - const internalDispatchCall = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) - .mock.calls[0] - internalDispatchCall[1]({ statusCode: 400 }) - - // assert that the original dispatch function was called with the request - expect( - (originalEventDispatcher.dispatchEvent as unknown) as jest.Mock, - ).toBeCalledTimes(1) - expect(internalDispatchCall[0]).toEqual(eventV1Request) - - // assert that the passed in callback to pendingEventsDispatcher was called - expect(callback).toHaveBeenCalledTimes(1) - expect(callback).toHaveBeenCalledWith({ statusCode: 400 }) - - expect(store.values()).toHaveLength(0) - }) - }) - }) - - describe('sendPendingEvents', () => { - describe('when no pending events are in the store', () => { - it('should not invoked dispatch', () => { - expect(store.values()).toHaveLength(0) - - pendingEventsDispatcher.sendPendingEvents() - expect(originalEventDispatcher.dispatchEvent).not.toHaveBeenCalled() - }) - }) - - describe('when there are multiple pending events in the store', () => { - it('should dispatch all of the pending events, and remove them from store', () => { - expect(store.values()).toHaveLength(0) - - const callback = jest.fn() - const eventV1Request1: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event1' } as unknown) as EventV1, - } - - const eventV1Request2: EventV1Request = { - url: 'http://cdn.com/', - httpVerb: 'POST', - params: ({ id: 'event2' } as unknown) as EventV1, - } - - store.set('uuid1', { - uuid: 'uuid1', - timestamp: 1, - request: eventV1Request1, - }) - store.set('uuid2', { - uuid: 'uuid2', - timestamp: 2, - request: eventV1Request2, - }) - - expect(store.values()).toHaveLength(2) - - pendingEventsDispatcher.sendPendingEvents() - expect(originalEventDispatcher.dispatchEvent).toHaveBeenCalledTimes(2) - - // manually invoke original eventDispatcher callback - const internalDispatchCalls = ((originalEventDispatcher.dispatchEvent as unknown) as jest.Mock) - .mock.calls - internalDispatchCalls[0][1]({ statusCode: 200 }) - internalDispatchCalls[1][1]({ statusCode: 200 }) - - expect(store.values()).toHaveLength(0) - }) - }) - }) -}) diff --git a/packages/optimizely-sdk/tests/pendingEventsStore.spec.ts b/packages/optimizely-sdk/tests/pendingEventsStore.spec.ts deleted file mode 100644 index 9e8062429..000000000 --- a/packages/optimizely-sdk/tests/pendingEventsStore.spec.ts +++ /dev/null @@ -1,143 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/// <reference types="jest" /> - -import { LocalStorageStore } from '../lib/modules/event_processor/pendingEventsStore' - -type TestEntry = { - uuid: string - timestamp: number - value: string -} - -describe('LocalStorageStore', () => { - let store: LocalStorageStore<TestEntry> - beforeEach(() => { - store = new LocalStorageStore({ - key: 'test_key', - maxValues: 3, - }) - }) - - afterEach(() => { - localStorage.clear() - }) - - it('should get, set and remove items', () => { - store.set('1', { - uuid: '1', - timestamp: 1, - value: 'first', - }) - - expect(store.get('1')).toEqual({ - uuid: '1', - timestamp: 1, - value: 'first', - }) - - store.set('1', { - uuid: '1', - timestamp: 2, - value: 'second', - }) - - expect(store.get('1')).toEqual({ - uuid: '1', - timestamp: 2, - value: 'second', - }) - - expect(store.values()).toHaveLength(1) - - store.remove('1') - - expect(store.values()).toHaveLength(0) - }) - - it('should allow replacement of the entire map', () => { - store.set('1', { - uuid: '1', - timestamp: 1, - value: 'first', - }) - - store.set('2', { - uuid: '2', - timestamp: 2, - value: 'second', - }) - - store.set('3', { - uuid: '3', - timestamp: 3, - value: 'third', - }) - - expect(store.values()).toEqual([ - { uuid: '1', timestamp: 1, value: 'first' }, - { uuid: '2', timestamp: 2, value: 'second' }, - { uuid: '3', timestamp: 3, value: 'third' }, - ]) - - const newMap: { [key: string]: TestEntry } = {} - store.values().forEach(item => { - newMap[item.uuid] = { - ...item, - value: 'new', - } - }) - store.replace(newMap) - - expect(store.values()).toEqual([ - { uuid: '1', timestamp: 1, value: 'new' }, - { uuid: '2', timestamp: 2, value: 'new' }, - { uuid: '3', timestamp: 3, value: 'new' }, - ]) - }) - - it(`shouldn't allow more than the configured maxValues, using timestamp to remove the oldest entries`, () => { - store.set('2', { - uuid: '2', - timestamp: 2, - value: 'second', - }) - - store.set('3', { - uuid: '3', - timestamp: 3, - value: 'third', - }) - - store.set('1', { - uuid: '1', - timestamp: 1, - value: 'first', - }) - - store.set('4', { - uuid: '4', - timestamp: 4, - value: 'fourth', - }) - - expect(store.values()).toEqual([ - { uuid: '2', timestamp: 2, value: 'second' }, - { uuid: '3', timestamp: 3, value: 'third' }, - { uuid: '4', timestamp: 4, value: 'fourth' }, - ]) - }) -}) diff --git a/packages/optimizely-sdk/tests/reactNativeAsyncStorageCache.spec.ts b/packages/optimizely-sdk/tests/reactNativeAsyncStorageCache.spec.ts deleted file mode 100644 index a7af194c4..000000000 --- a/packages/optimizely-sdk/tests/reactNativeAsyncStorageCache.spec.ts +++ /dev/null @@ -1,90 +0,0 @@ -/** - * Copyright 2020, 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/// <reference types="jest" /> - -import ReactNativeAsyncStorageCache from '../lib/plugins/key_value_cache/reactNativeAsyncStorageCache'; -import AsyncStorage from '../__mocks__/@react-native-async-storage/async-storage'; - -describe('ReactNativeAsyncStorageCache', () => { - const TEST_OBJECT_KEY = 'testObject'; - const testObject = { name: 'An object', with: { some: 2, properties: ['one', 'two'] } }; - let cacheInstance: ReactNativeAsyncStorageCache; - - beforeAll(() => { - cacheInstance = new ReactNativeAsyncStorageCache(); - }); - - beforeEach(() => { - AsyncStorage.clearStore(); - AsyncStorage.setItem(TEST_OBJECT_KEY, JSON.stringify(testObject)); - }); - - describe('contains', () => { - it('should return true if value with key exists', async () => { - const keyWasFound = await cacheInstance.contains(TEST_OBJECT_KEY); - - expect(keyWasFound).toBe(true); - }); - - it('should return false if value with key does not exist', async () => { - const keyWasFound = await cacheInstance.contains('keyThatDoesNotExist'); - - expect(keyWasFound).toBe(false); - }); - }); - - describe('get', () => { - it('should return correct string when item is found in cache', async () => { - const json = await cacheInstance.get(TEST_OBJECT_KEY); - const parsedObject = JSON.parse(json ?? ''); - - expect(parsedObject).toEqual(testObject); - }); - - it('should return empty string if item is not found in cache', async () => { - const json = await cacheInstance.get('keyThatDoesNotExist'); - - expect(json).toBeNull(); - }); - }); - - describe('remove', () => { - it('should return true after removing a found entry', async () => { - const wasSuccessful = await cacheInstance.remove(TEST_OBJECT_KEY); - - expect(wasSuccessful).toBe(true); - }); - - it('should return false after trying to remove an entry that is not found ', async () => { - const wasSuccessful = await cacheInstance.remove('keyThatDoesNotExist'); - - expect(wasSuccessful).toBe(false); - }); - }); - - describe('set', () => { - it('should resolve promise if item was successfully set in the cache', async () => { - const anotherTestStringValue = 'This should be found too.'; - - await cacheInstance.set('anotherTestStringValue', anotherTestStringValue); - - const itemsInReactAsyncStorage = AsyncStorage.dumpItems(); - expect(itemsInReactAsyncStorage['anotherTestStringValue']).toEqual(anotherTestStringValue); - expect(itemsInReactAsyncStorage[TEST_OBJECT_KEY]).toEqual(JSON.stringify(testObject)); - }); - }); -}); diff --git a/packages/optimizely-sdk/tests/reactNativeEventsStore.spec.ts b/packages/optimizely-sdk/tests/reactNativeEventsStore.spec.ts deleted file mode 100644 index 165a9c798..000000000 --- a/packages/optimizely-sdk/tests/reactNativeEventsStore.spec.ts +++ /dev/null @@ -1,212 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/// <reference types="jest" /> - -import { ReactNativeEventsStore } from '../lib/modules/event_processor/reactNativeEventsStore' -import AsyncStorage from '../__mocks__/@react-native-async-storage/async-storage' - -const STORE_KEY = 'test-store' - -describe('ReactNativeEventsStore', () => { - let store: ReactNativeEventsStore<any> - - beforeEach(() => { - store = new ReactNativeEventsStore(5, STORE_KEY) - }) - - describe('set', () => { - it('should store all the events correctly in the store', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - const storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - }) - - it('should store all the events when set asynchronously', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - const storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - }) - }) - - describe('get', () => { - it('should correctly get items', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - expect(await store.get('event1')).toEqual({'name': 'event1'}) - expect(await store.get('event2')).toEqual({'name': 'event2'}) - expect(await store.get('event3')).toEqual({'name': 'event3'}) - expect(await store.get('event4')).toEqual({'name': 'event4'}) - }) - }) - - describe('getEventsMap', () => { - it('should get the whole map correctly', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - const mapResult = await store.getEventsMap() - expect(mapResult).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - }) - }) - - describe('getEventsList', () => { - it('should get all the events as a list', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - const listResult = await store.getEventsList() - expect(listResult).toEqual([ - { "name": "event1" }, - { "name": "event2" }, - { "name": "event3" }, - { "name": "event4" }, - ]) - }) - }) - - describe('remove', () => { - it('should correctly remove items from the store', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - - await store.remove('event1') - storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - - await store.remove('event2') - storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - }) - - it('should correctly remove items from the store when removed asynchronously', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - - const promises = [] - await store.remove('event1') - await store.remove('event2') - await store.remove('event3') - storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ "event4": { "name": "event4" }}) - }) - }) - - describe('clear', () => { - it('should clear the whole store',async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - await store.clear() - storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY] || '{}') - expect(storedPendingEvents).toEqual({}) - }) - }) - - describe('maxSize', () => { - it('should not add anymore events if the store if full', async () => { - await store.set('event1', {'name': 'event1'}) - await store.set('event2', {'name': 'event2'}) - await store.set('event3', {'name': 'event3'}) - await store.set('event4', {'name': 'event4'}) - - let storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - }) - await store.set('event5', {'name': 'event5'}) - - storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - "event5": { "name": "event5" }, - }) - - await store.set('event6', {'name': 'event6'}) - storedPendingEvents = JSON.parse(AsyncStorage.dumpItems()[STORE_KEY]) - expect(storedPendingEvents).toEqual({ - "event1": { "name": "event1" }, - "event2": { "name": "event2" }, - "event3": { "name": "event3" }, - "event4": { "name": "event4" }, - "event5": { "name": "event5" }, - }) - }) - }) -}) diff --git a/packages/optimizely-sdk/tests/requestTracker.spec.ts b/packages/optimizely-sdk/tests/requestTracker.spec.ts deleted file mode 100644 index 70f241500..000000000 --- a/packages/optimizely-sdk/tests/requestTracker.spec.ts +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import RequestTracker from '../lib/modules/event_processor/requestTracker' - -describe('requestTracker', () => { - describe('onRequestsComplete', () => { - it('returns an immediately-fulfilled promise when no requests are in flight', async () => { - const tracker = new RequestTracker() - await tracker.onRequestsComplete() - }) - - it('returns a promise that fulfills after in-flight requests are complete', async () => { - let resolveReq1: () => void - const req1 = new Promise<void>(resolve => { - resolveReq1 = resolve - }) - let resolveReq2: () => void - const req2 = new Promise<void>(resolve => { - resolveReq2 = resolve - }) - let resolveReq3: () => void - const req3 = new Promise<void>(resolve => { - resolveReq3 = resolve - }) - - const tracker = new RequestTracker() - tracker.trackRequest(req1) - tracker.trackRequest(req2) - tracker.trackRequest(req3) - - let reqsComplete = false - const reqsCompletePromise = tracker.onRequestsComplete().then(() => { - reqsComplete = true - }) - - resolveReq1!() - await req1 - expect(reqsComplete).toBe(false) - - resolveReq2!() - await req2 - expect(reqsComplete).toBe(false) - - resolveReq3!() - await req3 - await reqsCompletePromise - expect(reqsComplete).toBe(true) - }) - }) -}) diff --git a/packages/optimizely-sdk/tests/restApiManager.spec.ts b/packages/optimizely-sdk/tests/restApiManager.spec.ts deleted file mode 100644 index 132649da7..000000000 --- a/packages/optimizely-sdk/tests/restApiManager.spec.ts +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/// <reference types="jest" /> - -import { anyString, anything, instance, mock, resetCalls, verify, when } from 'ts-mockito'; -import { LogHandler, LogLevel } from '../lib/modules/logging'; -import { RestApiManager } from '../lib/plugins/odp/rest_api_manager'; -import { OdpEvent } from '../lib/plugins/odp/odp_event'; -import { RequestHandler } from '../lib/utils/http_request_handler/http'; - -const VALID_ODP_PUBLIC_KEY = 'not-real-api-key'; -const ODP_REST_API_HOST = 'https://events.example.com/v2/api'; -const data1 = new Map<string, unknown>(); -data1.set('key11', 'value-1'); -data1.set('key12', true); -data1.set('key12', 3.5); -data1.set('key14', null); -const data2 = new Map<string, unknown>(); -data2.set('key2', 'value-2'); -const ODP_EVENTS = [ - new OdpEvent('t1', 'a1', - new Map([['id-key-1', 'id-value-1']]), - data1), - new OdpEvent('t2', 'a2', - new Map([['id-key-2', 'id-value-2']]), - data2), -]; - -describe('RestApiManager', () => { - let mockLogger: LogHandler; - let mockRequestHandler: RequestHandler; - - beforeAll(() => { - mockLogger = mock<LogHandler>(); - mockRequestHandler = mock<RequestHandler>(); - }); - - beforeEach(() => { - resetCalls(mockLogger); - resetCalls(mockRequestHandler); - }); - - const managerInstance = () => new RestApiManager(instance(mockRequestHandler), instance(mockLogger)); - const abortableRequest = (statusCode: number, body: string) => { - return { - abort: () => { - }, - responsePromise: Promise.resolve({ - statusCode, - body, - headers: {}, - }), - }; - }; - - it('should should send events successfully and not suggest retry', async () => { - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(200, '')); - const manager = managerInstance(); - - const shouldRetry = await manager.sendEvents(VALID_ODP_PUBLIC_KEY, ODP_REST_API_HOST, ODP_EVENTS); - - expect(shouldRetry).toBe(false); - verify(mockLogger.log(anything(), anyString())).never(); - }); - - it('should not suggest a retry for 400 HTTP response', async () => { - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(400, '')); - const manager = managerInstance(); - - const shouldRetry = await manager.sendEvents(VALID_ODP_PUBLIC_KEY, ODP_REST_API_HOST, ODP_EVENTS); - - expect(shouldRetry).toBe(false); - verify(mockLogger.log(LogLevel.ERROR, 'ODP event send failed (400)')).once(); - }); - - it('should suggest a retry for 500 HTTP response', async () => { - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn(abortableRequest(500, '')); - const manager = managerInstance(); - - const shouldRetry = await manager.sendEvents(VALID_ODP_PUBLIC_KEY, ODP_REST_API_HOST, ODP_EVENTS); - - expect(shouldRetry).toBe(true); - verify(mockLogger.log(LogLevel.ERROR, 'ODP event send failed (500)')).once(); - }); - - it('should suggest a retry for network timeout', async () => { - when(mockRequestHandler.makeRequest(anything(), anything(), anything(), anything())).thenReturn({ - abort: () => { - }, - responsePromise: Promise.reject(new Error('Request timed out')), - }); - const manager = managerInstance(); - - const shouldRetry = await manager.sendEvents(VALID_ODP_PUBLIC_KEY, ODP_REST_API_HOST, ODP_EVENTS); - - expect(shouldRetry).toBe(true); - verify(mockLogger.log(LogLevel.ERROR, 'ODP event send failed (Request timed out)')).once(); - }); -}); diff --git a/packages/optimizely-sdk/tests/v1EventProcessor.react_native.spec.ts b/packages/optimizely-sdk/tests/v1EventProcessor.react_native.spec.ts deleted file mode 100644 index 0a989dfd0..000000000 --- a/packages/optimizely-sdk/tests/v1EventProcessor.react_native.spec.ts +++ /dev/null @@ -1,881 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/// <reference types="jest" /> -import { NotificationSender } from '../lib/core/notification_center' -import { NOTIFICATION_TYPES } from '../lib/utils/enums' - -import { LogTierV1EventProcessor } from '../lib/modules/event_processor/v1/v1EventProcessor.react_native' -import { - EventDispatcher, - EventV1Request, - EventDispatcherCallback, -} from '../lib/modules/event_processor/eventDispatcher' -import { EventProcessor, ProcessableEvent } from '../lib/modules/event_processor/eventProcessor' -import { buildImpressionEventV1, makeBatchedEventV1 } from '../lib/modules/event_processor/v1/buildEventV1' -import AsyncStorage from '../__mocks__/@react-native-async-storage/async-storage' -import { triggerInternetState } from '../__mocks__/@react-native-community/netinfo' -import { DefaultEventQueue } from '../lib/modules/event_processor/eventQueue' - -function createImpressionEvent() { - return { - type: 'impression' as 'impression', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: '1', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - layer: { - id: 'layerId', - }, - - experiment: { - id: 'expId', - key: 'expKey', - }, - - variation: { - id: 'varId', - key: 'varKey', - }, - - ruleKey: 'expKey', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: false, - } -} - -function createConversionEvent() { - return { - type: 'conversion' as 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: '1', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - - revenue: 1000, - value: 123, - } -} - -describe('LogTierV1EventProcessorReactNative', () => { - describe('New Events', () => { - let stubDispatcher: EventDispatcher - let dispatchStub: jest.Mock - - beforeEach(() => { - dispatchStub = jest.fn() - - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - callback({ statusCode: 200 }) - }, - } - }) - - afterEach(() => { - jest.resetAllMocks() - AsyncStorage.clearStore() - }) - - describe('stop()', () => { - let localCallback: EventDispatcherCallback - beforeEach(async () => { - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - localCallback = callback - }, - } - }) - - it('should return a resolved promise when there is nothing in queue', async () => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - - await processor.start() - - await processor.stop() - }) - - it('should return a promise that is resolved when the dispatcher callback returns a 200 response', async () => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - await processor.start() - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - await new Promise(resolve => setTimeout(resolve, 150)) - // @ts-ignore - localCallback({ statusCode: 200 }) - }) - - it('should return a promise that is resolved when the dispatcher callback returns a 400 response', async () => { - // This test is saying that even if the request fails to send but - // the `dispatcher` yielded control back, then the `.stop()` promise should be resolved - let localCallback: any - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - localCallback = callback - }, - } - - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - await processor.start() - - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - await new Promise(resolve => setTimeout(resolve, 150)) - - localCallback({ statusCode: 400 }) - }) - - it('should return a promise when multiple event batches are sent', async () => { - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - callback({ statusCode: 200 }) - }, - } - - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - - await processor.start() - - const impressionEvent1 = createImpressionEvent() - const impressionEvent2 = createImpressionEvent() - impressionEvent2.context.revision = '2' - processor.process(impressionEvent1) - processor.process(impressionEvent2) - - await new Promise(resolve => setTimeout(resolve, 150)) - await processor.stop() - expect(dispatchStub).toBeCalledTimes(2) - }) - - it('should stop accepting events after stop is called', async () => { - const dispatcher = { - dispatchEvent: jest.fn((event: EventV1Request, callback: EventDispatcherCallback) => { - setTimeout(() => callback({ statusCode: 204 }), 0) - }) - } - const processor = new LogTierV1EventProcessor({ - dispatcher, - flushInterval: 100, - batchSize: 3, - }) - await processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - await new Promise(resolve => setTimeout(resolve, 150)) - - await processor.stop() - // calling stop should haver flushed the current batch of size 1 - expect(dispatcher.dispatchEvent).toBeCalledTimes(1) - - dispatcher.dispatchEvent.mockClear(); - - // From now on, subsequent events should be ignored. - // Process 3 more, which ordinarily would have triggered - // a flush due to the batch size. - const impressionEvent2 = createImpressionEvent() - processor.process(impressionEvent2) - const impressionEvent3 = createImpressionEvent() - processor.process(impressionEvent3) - const impressionEvent4 = createImpressionEvent() - processor.process(impressionEvent4) - // Since we already stopped the processor, the dispatcher should - // not have been called again. - await new Promise(resolve => setTimeout(resolve, 150)) - expect(dispatcher.dispatchEvent).toBeCalledTimes(0) - }) - }) - - describe('when batchSize = 1', () => { - let processor: EventProcessor - beforeEach(async () => { - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - await processor.start() - }) - - afterEach(async () => { - await processor.stop() - }) - - it('should immediately flush events as they are processed', async () => { - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - await new Promise(resolve => setTimeout(resolve, 50)) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: buildImpressionEventV1(impressionEvent), - }) - }) - }) - - describe('when batchSize = 3, flushInterval = 300', () => { - let processor: EventProcessor - beforeEach(async () => { - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 300, - batchSize: 3, - }) - await processor.start() - }) - - afterEach(async () => { - await processor.stop() - }) - - it('should wait until 3 events to be in the queue before it flushes', async () => { - const impressionEvent1 = createImpressionEvent() - const impressionEvent2 = createImpressionEvent() - const impressionEvent3 = createImpressionEvent() - - processor.process(impressionEvent1) - processor.process(impressionEvent2) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent3) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([ - impressionEvent1, - impressionEvent2, - impressionEvent3, - ]), - }) - }) - - it('should flush the current batch when it receives an event with a different context revision than the current batch', async () => { - const impressionEvent1 = createImpressionEvent() - const conversionEvent = createConversionEvent() - const impressionEvent2 = createImpressionEvent() - - // createImpressionEvent and createConversionEvent create events with revision '1' - // We modify this one's revision to '2' in order to test that the queue is flushed - // when an event with a different revision is processed. - impressionEvent2.context.revision = '2' - - processor.process(impressionEvent1) - processor.process(conversionEvent) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent2) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1, conversionEvent]), - }) - }) - - it('should flush the current batch when it receives an event with a different context projectId than the current batch', async () => { - const impressionEvent1 = createImpressionEvent() - const conversionEvent = createConversionEvent() - const impressionEvent2 = createImpressionEvent() - - impressionEvent2.context.projectId = 'projectId2' - - processor.process(impressionEvent1) - processor.process(conversionEvent) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent2) - - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1, conversionEvent]), - }) - }) - - it('should flush the queue when the flush interval happens', async () => { - const impressionEvent1 = createImpressionEvent() - - processor.process(impressionEvent1) - - expect(dispatchStub).toHaveBeenCalledTimes(0) - - await new Promise(resolve => setTimeout(resolve, 350)) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1]), - }) - - processor.process(createImpressionEvent()) - processor.process(createImpressionEvent()) - // flushing should reset queue, at this point only has two events - expect(dispatchStub).toHaveBeenCalledTimes(1) - }) - }) - - describe('when a notification center is provided', () => { - it('should trigger a notification when the event dispatcher dispatches an event', async () => { - const dispatcher: EventDispatcher = { - dispatchEvent: jest.fn() - } - - const notificationCenter: NotificationSender = { - sendNotifications: jest.fn() - } - - const processor = new LogTierV1EventProcessor({ - dispatcher, - notificationCenter, - batchSize: 1, - }) - await processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - - await new Promise(resolve => setTimeout(resolve, 150)) - expect(notificationCenter.sendNotifications).toBeCalledTimes(1) - const event = (dispatcher.dispatchEvent as jest.Mock).mock.calls[0][0] - expect(notificationCenter.sendNotifications).toBeCalledWith(NOTIFICATION_TYPES.LOG_EVENT, event) - }) - }) - - describe('invalid batchSize', () => { - it('should ignore a batchSize of 0 and use the default', async () => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 30000, - batchSize: 0, - }) - await processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - - await new Promise(resolve => setTimeout(resolve, 150)) - expect(dispatchStub).toHaveBeenCalledTimes(0) - const impressionEvents = [impressionEvent1] - for (let i = 0; i < 9; i++) { - const evt = createImpressionEvent() - processor.process(evt) - impressionEvents.push(evt) - } - - await new Promise(resolve => setTimeout(resolve, 150)) - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1(impressionEvents), - }) - }) - }) - }) - - describe('Pending Events', () => { - let stubDispatcher: EventDispatcher - let dispatchStub: jest.Mock - - beforeEach(() => { - dispatchStub = jest.fn() - }) - - afterEach(() => { - jest.clearAllMocks() - AsyncStorage.clearStore() - }) - - describe('Retry Pending Events', () => { - describe('App start', () => { - it('should dispatch all the pending events in correct order', async () => { - let receivedEvents: EventV1Request[] = [] - - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - callback({ statusCode: 400 }) - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - let event1 = createConversionEvent() - event1.user.id = 'user1' - let event2 = createConversionEvent() - event2.user.id = 'user2' - let event3 = createConversionEvent() - event3.user.id = 'user3' - let event4 = createConversionEvent() - event4.user.id = 'user4' - - processor.process(event1) - processor.process(event2) - processor.process(event3) - processor.process(event4) - - await new Promise(resolve => setTimeout(resolve, 100)) - - expect(dispatchStub).toBeCalledTimes(4) - - await processor.stop() - - jest.clearAllMocks() - - receivedEvents = [] - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - receivedEvents.push(event) - dispatchStub(event) - callback({ statusCode: 200 }) - }, - } - - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - - receivedEvents.forEach((e, i) => { - expect(e.params.visitors[0].visitor_id).toEqual(`user${i+1}`) - }) - - expect(dispatchStub).toBeCalledTimes(4) - - await processor.stop() - }) - - it('should process all the events left in buffer when the app closed last time', async () => { - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - callback({ statusCode: 200 }) - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 1000, - batchSize: 4, - }) - - await processor.start() - let event1 = createConversionEvent() - event1.user.id = 'user1' - event1.uuid = 'user1' - let event2 = createConversionEvent() - event2.user.id = 'user2' - event2.uuid = 'user2' - - processor.process(event1) - processor.process(event2) - - await new Promise(resolve => setTimeout(resolve, 100)) - - // Explicitly stopping the timer to simulate app close - ;(processor.queue as DefaultEventQueue<ProcessableEvent>).timer.stop() - - let receivedEvents: EventV1Request[] = [] - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - receivedEvents.push(event) - dispatchStub(event) - callback({ statusCode: 200 }) - }, - } - - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 4, - }) - - await processor.start() - - await new Promise(resolve => setTimeout(resolve, 150)) - expect(dispatchStub).toBeCalledTimes(1) - expect(receivedEvents.length).toEqual(1) - const receivedEvent = receivedEvents[0] - - receivedEvent.params.visitors.forEach((v, i) => { - expect(v.visitor_id).toEqual(`user${i+1}`) - }) - - await processor.stop() - }) - - it('should dispatch pending events first and then process events in buffer store', async () => { - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - callback({ statusCode: 400 }) - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 300, - batchSize: 3, - }) - - await processor.start() - - for (let i = 0; i < 8; i++) { - let event = createConversionEvent() - event.user.id = `user${i}` - event.uuid = `user${i}` - processor.process(event) - } - - await new Promise(resolve => setTimeout(resolve, 50)) - - expect(dispatchStub).toBeCalledTimes(2) - - ;(processor.queue as DefaultEventQueue<ProcessableEvent>).timer.stop() - - jest.clearAllMocks() - - const visitorIds: string[] = [] - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - event.params.visitors.forEach(visitor => visitorIds.push(visitor.visitor_id)) - callback({ statusCode: 200 }) - }, - } - - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 200, - batchSize: 3, - }) - - await processor.start() - - expect(dispatchStub).toBeCalledTimes(2) - - await new Promise(resolve => setTimeout(resolve, 250)) - expect(visitorIds.length).toEqual(8) - expect(visitorIds).toEqual(['user0', 'user1', 'user2', 'user3', 'user4', 'user5', 'user6', 'user7']) - }) - }) - - describe('When a new event is dispatched', () => { - it('should dispatch all the pending events first and then new event in correct order', async () => { - let receivedVisitorIds: string[] = [] - let dispatchCount = 0 - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - dispatchCount++ - if (dispatchCount > 4) { - event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id)) - callback({ statusCode: 200 }) - } else { - callback({ statusCode: 400 }) - } - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - let event1 = createConversionEvent() - event1.user.id = event1.uuid = 'user1' - let event2 = createConversionEvent() - event2.user.id = event2.uuid = 'user2' - let event3 = createConversionEvent() - event3.user.id = event3.uuid = 'user3' - let event4 = createConversionEvent() - event4.user.id = event4.uuid = 'user4' - - processor.process(event1) - processor.process(event2) - processor.process(event3) - processor.process(event4) - - await new Promise(resolve => setTimeout(resolve, 100)) - - // Four events will return response code 400 which means only the first pending event will be tried each time and rest will be skipped - expect(dispatchStub).toBeCalledTimes(4) - - jest.resetAllMocks() - - let event5 = createConversionEvent() - event5.user.id = event5.uuid = 'user5' - - processor.process(event5) - - await new Promise(resolve => setTimeout(resolve, 100)) - expect(dispatchStub).toBeCalledTimes(5) - expect(receivedVisitorIds).toEqual(['user1', 'user2', 'user3', 'user4', 'user5']) - await processor.stop() - }) - - it('should skip dispatching subsequent events if an event fails to dispatch', async () => { - let receivedVisitorIds: string[] = [] - let dispatchCount = 0 - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - dispatchCount++ - event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id)) - callback({ statusCode: 400 }) - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - let event1 = createConversionEvent() - event1.user.id = event1.uuid = 'user1' - let event2 = createConversionEvent() - event2.user.id = event2.uuid = 'user2' - let event3 = createConversionEvent() - event3.user.id = event3.uuid = 'user3' - let event4 = createConversionEvent() - event4.user.id = event4.uuid = 'user4' - - processor.process(event1) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toBeCalledTimes(1) - - processor.process(event2) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toBeCalledTimes(2) - - processor.process(event3) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toBeCalledTimes(3) - - processor.process(event4) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toBeCalledTimes(4) - - expect(dispatchCount).toEqual(4) - - // subsequent events were skipped with each attempt because of request failure - expect(receivedVisitorIds).toEqual(['user1', 'user1', 'user1', 'user1']) - await processor.stop() - }) - }) - - describe('When internet connection is restored', () => { - it('should dispatch all the pending events in correct order when internet connection is restored', async () => { - let receivedVisitorIds: string[] = [] - let dispatchCount = 0 - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - dispatchCount++ - if (dispatchCount > 4) { - event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id)) - callback({ statusCode: 200 }) - } else { - callback({ statusCode: 400 }) - } - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - triggerInternetState(false) - let event1 = createConversionEvent() - event1.user.id = event1.uuid = 'user1' - let event2 = createConversionEvent() - event2.user.id = event2.uuid = 'user2' - let event3 = createConversionEvent() - event3.user.id = event3.uuid = 'user3' - let event4 = createConversionEvent() - event4.user.id = event4.uuid = 'user4' - - processor.process(event1) - processor.process(event2) - processor.process(event3) - processor.process(event4) - - await new Promise(resolve => setTimeout(resolve, 50)) - - // Four events will return response code 400 which means only the first pending event will be tried each time and rest will be skipped - expect(dispatchStub).toBeCalledTimes(4) - - jest.resetAllMocks() - - triggerInternetState(true) - await new Promise(resolve => setTimeout(resolve, 50)) - expect(dispatchStub).toBeCalledTimes(4) - expect(receivedVisitorIds).toEqual(['user1', 'user2', 'user3', 'user4']) - await processor.stop() - }) - - it('should not dispatch duplicate events if internet is lost and restored twice in a short interval', async () => { - let receivedVisitorIds: string[] = [] - let dispatchCount = 0 - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - dispatchCount++ - if (dispatchCount > 4) { - event.params.visitors.forEach(visitor => receivedVisitorIds.push(visitor.visitor_id)) - callback({ statusCode: 200 }) - } else { - callback({ statusCode: 400 }) - } - }, - } - - let processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - - await processor.start() - triggerInternetState(false) - let event1 = createConversionEvent() - event1.user.id = event1.uuid = 'user1' - let event2 = createConversionEvent() - event2.user.id = event2.uuid = 'user2' - let event3 = createConversionEvent() - event3.user.id = event3.uuid = 'user3' - let event4 = createConversionEvent() - event4.user.id = event4.uuid = 'user4' - - processor.process(event1) - processor.process(event2) - processor.process(event3) - processor.process(event4) - - await new Promise(resolve => setTimeout(resolve, 100)) - - // Four events will return response code 400 which means only the first pending event will be tried each time and rest will be skipped - expect(dispatchStub).toBeCalledTimes(4) - - jest.resetAllMocks() - - triggerInternetState(true) - triggerInternetState(false) - triggerInternetState(true) - triggerInternetState(false) - triggerInternetState(true) - - await new Promise(resolve => setTimeout(resolve, 100)) - expect(dispatchStub).toBeCalledTimes(4) - expect(receivedVisitorIds).toEqual(['user1', 'user2', 'user3', 'user4']) - await processor.stop() - }) - }) - }) - }) -}) diff --git a/packages/optimizely-sdk/tests/v1EventProcessor.spec.ts b/packages/optimizely-sdk/tests/v1EventProcessor.spec.ts deleted file mode 100644 index 59f6cdb79..000000000 --- a/packages/optimizely-sdk/tests/v1EventProcessor.spec.ts +++ /dev/null @@ -1,530 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -/// <reference types="jest" /> - -import { LogTierV1EventProcessor } from '../lib/modules/event_processor/v1/v1EventProcessor' -import { - EventDispatcher, - EventV1Request, - EventDispatcherCallback, -} from '../lib/modules/event_processor/eventDispatcher' -import { EventProcessor } from '../lib/modules/event_processor/eventProcessor' -import { buildImpressionEventV1, makeBatchedEventV1 } from '../lib/modules/event_processor/v1/buildEventV1' -import { NotificationCenter, NotificationSender } from '../lib/core/notification_center' -import { NOTIFICATION_TYPES } from '../lib/utils/enums' - -function createImpressionEvent() { - return { - type: 'impression' as 'impression', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: '1', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - layer: { - id: 'layerId', - }, - - experiment: { - id: 'expId', - key: 'expKey', - }, - - variation: { - id: 'varId', - key: 'varKey', - }, - - ruleKey: 'expKey', - flagKey: 'flagKey1', - ruleType: 'experiment', - enabled: true, - } -} - -function createConversionEvent() { - return { - type: 'conversion' as 'conversion', - timestamp: 69, - uuid: 'uuid', - - context: { - accountId: 'accountId', - projectId: 'projectId', - clientName: 'node-sdk', - clientVersion: '3.0.0', - revision: '1', - botFiltering: true, - anonymizeIP: true, - }, - - user: { - id: 'userId', - attributes: [{ entityId: 'attr1-id', key: 'attr1-key', value: 'attr1-value' }], - }, - - event: { - id: 'event-id', - key: 'event-key', - }, - - tags: { - foo: 'bar', - value: '123', - revenue: '1000', - }, - - revenue: 1000, - value: 123, - } -} - -describe('LogTierV1EventProcessor', () => { - let stubDispatcher: EventDispatcher - let dispatchStub: jest.Mock - // TODO change this to ProjectConfig when js-sdk-models is available - let testProjectConfig: any - - beforeEach(() => { - jest.useFakeTimers() - - testProjectConfig = {} - dispatchStub = jest.fn() - - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - callback({ statusCode: 200 }) - }, - } - }) - - afterEach(() => { - jest.resetAllMocks() - }) - - describe('stop()', () => { - let localCallback: EventDispatcherCallback - beforeEach(() => { - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - localCallback = callback - }, - } - }) - - it('should return a resolved promise when there is nothing in queue', done => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - - processor.stop().then(() => { - done() - }) - }) - - it('should return a promise that is resolved when the dispatcher callback returns a 200 response', done => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - processor.start() - - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - processor.stop().then(() => { - done() - }) - - localCallback({ statusCode: 200 }) - }) - - it('should return a promise that is resolved when the dispatcher callback returns a 400 response', done => { - // This test is saying that even if the request fails to send but - // the `dispatcher` yielded control back, then the `.stop()` promise should be resolved - let localCallback: any - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - localCallback = callback - }, - } - - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - processor.start() - - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - processor.stop().then(() => { - done() - }) - - localCallback({ - statusCode: 400, - }) - }) - - it('should return a promise when multiple event batches are sent', done => { - stubDispatcher = { - dispatchEvent(event: EventV1Request, callback: EventDispatcherCallback): void { - dispatchStub(event) - callback({ statusCode: 200 }) - }, - } - - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 100, - }) - processor.start() - - const impressionEvent1 = createImpressionEvent() - const impressionEvent2 = createImpressionEvent() - impressionEvent2.context.revision = '2' - processor.process(impressionEvent1) - processor.process(impressionEvent2) - - processor.stop().then(() => { - expect(dispatchStub).toBeCalledTimes(2) - done() - }) - }) - - it('should stop accepting events after stop is called', () => { - const dispatcher = { - dispatchEvent: jest.fn((event: EventV1Request, callback: EventDispatcherCallback) => { - setTimeout(() => callback({ statusCode: 204 }), 0) - }) - } - const processor = new LogTierV1EventProcessor({ - dispatcher, - flushInterval: 100, - batchSize: 3, - }) - processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - processor.stop() - // calling stop should haver flushed the current batch of size 1 - expect(dispatcher.dispatchEvent).toBeCalledTimes(1) - - dispatcher.dispatchEvent.mockClear(); - - // From now on, subsequent events should be ignored. - // Process 3 more, which ordinarily would have triggered - // a flush due to the batch size. - const impressionEvent2 = createImpressionEvent() - processor.process(impressionEvent2) - const impressionEvent3 = createImpressionEvent() - processor.process(impressionEvent3) - const impressionEvent4 = createImpressionEvent() - processor.process(impressionEvent4) - // Since we already stopped the processor, the dispatcher should - // not have been called again. - expect(dispatcher.dispatchEvent).toBeCalledTimes(0) - }) - - it('should resolve the stop promise after all dispatcher requests are done', async () => { - const dispatchCbs: Array<EventDispatcherCallback> = [] - const dispatcher = { - dispatchEvent: jest.fn((event: EventV1Request, callback: EventDispatcherCallback) => { - dispatchCbs.push(callback) - }) - } - - const processor = new LogTierV1EventProcessor({ - dispatcher, - flushInterval: 100, - batchSize: 2, - }) - processor.start() - - for (let i = 0; i < 4; i++) { - processor.process(createImpressionEvent()) - } - expect(dispatchCbs.length).toBe(2) - - let stopPromiseResolved = false - const stopPromise = processor.stop().then(() => { - stopPromiseResolved = true - }) - expect(stopPromiseResolved).toBe(false) - - dispatchCbs[0]({ statusCode: 204 }) - jest.advanceTimersByTime(100) - expect(stopPromiseResolved).toBe(false) - dispatchCbs[1]({ statusCode: 204 }) - await stopPromise - expect(stopPromiseResolved).toBe(true) - }) - }) - - describe('when batchSize = 1', () => { - let processor: EventProcessor - beforeEach(() => { - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 1, - }) - processor.start() - }) - - afterEach(() => { - processor.stop() - }) - - it('should immediately flush events as they are processed', () => { - const impressionEvent = createImpressionEvent() - processor.process(impressionEvent) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: buildImpressionEventV1(impressionEvent), - }) - }) - }) - - describe('when batchSize = 3, flushInterval = 100', () => { - let processor: EventProcessor - beforeEach(() => { - processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 100, - batchSize: 3, - }) - processor.start() - }) - - afterEach(() => { - processor.stop() - }) - - it('should wait until 3 events to be in the queue before it flushes', () => { - const impressionEvent1 = createImpressionEvent() - const impressionEvent2 = createImpressionEvent() - const impressionEvent3 = createImpressionEvent() - - processor.process(impressionEvent1) - processor.process(impressionEvent2) - - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent3) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([ - impressionEvent1, - impressionEvent2, - impressionEvent3, - ]), - }) - }) - - it('should flush the current batch when it receives an event with a different context revision than the current batch', async () => { - const impressionEvent1 = createImpressionEvent() - const conversionEvent = createConversionEvent() - const impressionEvent2 = createImpressionEvent() - - // createImpressionEvent and createConversionEvent create events with revision '1' - // We modify this one's revision to '2' in order to test that the queue is flushed - // when an event with a different revision is processed. - impressionEvent2.context.revision = '2' - - processor.process(impressionEvent1) - processor.process(conversionEvent) - - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent2) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1, conversionEvent]), - }) - - await processor.stop() - - expect(dispatchStub).toHaveBeenCalledTimes(2) - - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent2]), - }) - }) - - it('should flush the current batch when it receives an event with a different context projectId than the current batch', async () => { - const impressionEvent1 = createImpressionEvent() - const conversionEvent = createConversionEvent() - const impressionEvent2 = createImpressionEvent() - - impressionEvent2.context.projectId = 'projectId2' - - processor.process(impressionEvent1) - processor.process(conversionEvent) - - expect(dispatchStub).toHaveBeenCalledTimes(0) - - processor.process(impressionEvent2) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1, conversionEvent]), - }) - - await processor.stop() - - expect(dispatchStub).toHaveBeenCalledTimes(2) - - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent2]), - }) - }) - - it('should flush the queue when the flush interval happens', () => { - const impressionEvent1 = createImpressionEvent() - - processor.process(impressionEvent1) - - expect(dispatchStub).toHaveBeenCalledTimes(0) - - jest.advanceTimersByTime(100) - - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1]), - }) - - processor.process(createImpressionEvent()) - processor.process(createImpressionEvent()) - // flushing should reset queue, at this point only has two events - expect(dispatchStub).toHaveBeenCalledTimes(1) - }) - - }) - - describe('when a notification center is provided', () => { - it('should trigger a notification when the event dispatcher dispatches an event', () => { - const dispatcher: EventDispatcher = { - dispatchEvent: jest.fn() - } - - const notificationCenter: NotificationSender = { - sendNotifications: jest.fn() - } - - const processor = new LogTierV1EventProcessor({ - dispatcher, - notificationCenter, - batchSize: 1, - }) - processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - - expect(notificationCenter.sendNotifications).toBeCalledTimes(1) - const event = (dispatcher.dispatchEvent as jest.Mock).mock.calls[0][0] - expect(notificationCenter.sendNotifications).toBeCalledWith(NOTIFICATION_TYPES.LOG_EVENT, event) - }) - }) - - describe('invalid flushInterval or batchSize', () => { - it('should ignore a flushInterval of 0 and use the default', () => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 0, - batchSize: 10, - }) - processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - expect(dispatchStub).toHaveBeenCalledTimes(0) - jest.advanceTimersByTime(30000) - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1([impressionEvent1]), - }) - }) - - it('should ignore a batchSize of 0 and use the default', () => { - const processor = new LogTierV1EventProcessor({ - dispatcher: stubDispatcher, - flushInterval: 30000, - batchSize: 0, - }) - processor.start() - - const impressionEvent1 = createImpressionEvent() - processor.process(impressionEvent1) - expect(dispatchStub).toHaveBeenCalledTimes(0) - const impressionEvents = [impressionEvent1] - for (let i = 0; i < 9; i++) { - const evt = createImpressionEvent() - processor.process(evt) - impressionEvents.push(evt) - } - expect(dispatchStub).toHaveBeenCalledTimes(1) - expect(dispatchStub).toHaveBeenCalledWith({ - url: 'https://logx.optimizely.com/v1/events', - httpVerb: 'POST', - params: makeBatchedEventV1(impressionEvents), - }) - }) - }) -}) diff --git a/packages/optimizely-sdk/tests/vuidManager.spec.ts b/packages/optimizely-sdk/tests/vuidManager.spec.ts deleted file mode 100644 index 9fe746f0a..000000000 --- a/packages/optimizely-sdk/tests/vuidManager.spec.ts +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Copyright 2022, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/// <reference types="jest" /> - -import { VuidManager } from '../lib/plugins/vuid_manager'; -import PersistentKeyValueCache from '../lib/plugins/key_value_cache/persistentKeyValueCache'; -import { anyString, anything, instance, mock, resetCalls, verify, when } from 'ts-mockito'; - -describe('VuidManager', () => { - let mockCache: PersistentKeyValueCache; - - beforeAll(() => { - mockCache = mock<PersistentKeyValueCache>(); - when(mockCache.contains(anyString())).thenResolve(true); - when(mockCache.get(anyString())).thenResolve(''); - when(mockCache.remove(anyString())).thenResolve(true); - when(mockCache.set(anyString(), anything())).thenResolve(); - VuidManager.instance(instance(mockCache)); - }); - - beforeEach(()=>{ - resetCalls(mockCache); - VuidManager['_reset'](); - }) - - it('should make a VUID', async () => { - const manager = await VuidManager.instance(instance(mockCache)); - - const vuid = manager['makeVuid'](); - - expect(vuid.startsWith('vuid_')).toBe(true); - expect(vuid.length).toEqual(32); - expect(vuid).not.toContain('-'); - }); - - it('should test if a VUID is valid', async () => { - const manager = await VuidManager.instance(instance(mockCache)); - - expect(manager['isVuid']('vuid_123')).toBe(true); - expect(manager['isVuid']('vuid-123')).toBe(false); - expect(manager['isVuid']('123')).toBe(false); - }); - - it('should auto-save and auto-load', async () => { - const cache = instance(mockCache); - - await cache.remove('optimizely-odp'); - - const manager1 = await VuidManager.instance(cache); - const vuid1 = manager1.vuid; - - const manager2 = await VuidManager.instance(cache); - const vuid2 = manager2.vuid; - - expect(vuid1).toStrictEqual(vuid2); - expect(manager2['isVuid'](vuid1)).toBe(true); - expect(manager1['isVuid'](vuid2)).toBe(true); - - await cache.remove('optimizely-odp'); - - // should end up being a new instance since we just removed it above - await manager2['load'](cache); - const vuid3 = manager2.vuid; - - expect(vuid3).not.toStrictEqual(vuid1); - expect(manager2['isVuid'](vuid3)).toBe(true); - }); - - it('should handle no valid optimizely-vuid in the cache', async () => { - when(mockCache.get(anyString())).thenResolve(null); - - const manager = await VuidManager.instance(instance(mockCache)); // load() called initially - - verify(mockCache.get(anyString())).once(); - verify(mockCache.set(anyString(), anything())).once(); - expect(manager['isVuid'](manager.vuid)).toBe(true); - }); - - it('should create a new vuid if old VUID from cache is not valid', async () => { - when(mockCache.get(anyString())).thenResolve('vuid-not-valid'); - - const manager = await VuidManager.instance(instance(mockCache)); - - verify(mockCache.get(anyString())).once(); - verify(mockCache.set(anyString(), anything())).once(); - expect(manager['isVuid'](manager.vuid)).toBe(true); - }); -}); - diff --git a/packages/optimizely-sdk/tsconfig.json b/packages/optimizely-sdk/tsconfig.json deleted file mode 100644 index 51dfbda08..000000000 --- a/packages/optimizely-sdk/tsconfig.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "baseUrl": "./", - "paths": { - "*": [ - "./typings/*" - ], - }, - "resolveJsonModule": true, - "allowJs": true, - "declaration": true, - "module": "esnext", - "outDir": "./dist", - "sourceMap": true, - "skipLibCheck": true, - }, - "exclude": [ - "./dist", - "./lib/**/*.tests.js", - "./lib/**/*.tests.ts", - "./lib/**/*.umdtests.js", - "./lib/tests", - "node_modules" - ], - "include": [ - "./lib/**/*.ts", - "./lib/**/*.js", - "./lib/modules/**/*.ts", - "./lib/modules/**/**/*.ts" - ] -} diff --git a/packages/optimizely-sdk/tsconfig.spec.json b/packages/optimizely-sdk/tsconfig.spec.json deleted file mode 100644 index 877e2b462..000000000 --- a/packages/optimizely-sdk/tsconfig.spec.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "types": [ - "jest" - ], - "typeRoots": [ - "./node_modules/@types" - ] - }, - "include": [ - "**/*.spec.ts" - ] -} diff --git a/packages/utils/.gitignore b/packages/utils/.gitignore deleted file mode 100644 index d4a125901..000000000 --- a/packages/utils/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -lib/ -doc/ diff --git a/packages/utils/CHANGELOG.md b/packages/utils/CHANGELOG.md deleted file mode 100644 index 24407c8aa..000000000 --- a/packages/utils/CHANGELOG.md +++ /dev/null @@ -1,37 +0,0 @@ -# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). - -## [Unreleased] -Changes that have landed but are not yet released. - -## [0.4.0] - July 27, 2020 - -- Removed React Native async storage implementation from utils ([#536](https://github.com/optimizely/javascript-sdk/pull/536)) - -## [0.3.2] - June 15, 2020 - -### Bug Fixes -- Wrap `uuid.v4` to disallow passing extra arguments and prevent dependency on `uuid` package types ([#509](https://github.com/optimizely/javascript-sdk/pull/509)) - -## [0.3.1] - June 12, 2020 - -### Bug Fixes -- Fix exports of `PersistentKeyValueCache` and `ReactNativeAsyncStorageCache` to be named, not default ([#506](https://github.com/optimizely/javascript-sdk/pull/506)) - -## [0.3.0] - June 11, 2020 - -### New Features -- Added `PersistentKeyValueCache` interface and its implementation for React Native under `ReactNativeAsyncStorageCache`. - -## [0.2.0] - August 7, 2019 - -### New Features -- Added `objectEntries` -- Added `NOTIFICATION_TYPES` and `NotificationCenter` - -## [0.1.0] - March 1, 2019 - -Initial release diff --git a/packages/utils/LICENSE b/packages/utils/LICENSE deleted file mode 100644 index b9f80c5bd..000000000 --- a/packages/utils/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright 2016-2017, Optimizely, Inc. and contributors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/packages/utils/README.md b/packages/utils/README.md deleted file mode 100644 index d9bb67bbc..000000000 --- a/packages/utils/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# `@optimizely/js-sdk-utils` - -A collection of utility functions shared between components of the Javascript SDK. - -### To test - -``` -npm test -``` \ No newline at end of file diff --git a/packages/utils/__tests__/utils.spec.ts b/packages/utils/__tests__/utils.spec.ts deleted file mode 100644 index 123b2805b..000000000 --- a/packages/utils/__tests__/utils.spec.ts +++ /dev/null @@ -1,123 +0,0 @@ -/// <reference types="jest" /> -import { isValidEnum, groupBy, objectEntries, objectValues, find, keyBy, sprintf } from '../src' - -describe('utils', () => { - describe('isValidEnum', () => { - enum myEnum { - FOO = 0, - BAR = 1, - } - - it('should return false when not valid', () => { - expect(isValidEnum(myEnum, 2)).toBe(false) - }) - - it('should return true when valid', () => { - expect(isValidEnum(myEnum, 1)).toBe(true) - expect(isValidEnum(myEnum, myEnum.FOO)).toBe(true) - }) - }) - - describe('groupBy', () => { - it('should group values by some key function', () => { - const input = [ - { firstName: 'jordan', lastName: 'foo' }, - { firstName: 'jordan', lastName: 'bar' }, - { firstName: 'james', lastName: 'foxy' }, - ] - const result = groupBy(input, item => item.firstName) - - expect(result).toEqual([ - [ - { firstName: 'jordan', lastName: 'foo' }, - { firstName: 'jordan', lastName: 'bar' }, - ], - [{ firstName: 'james', lastName: 'foxy' }], - ]) - }) - }) - - describe('objectEntries', () => { - it('should return object entries', () => { - expect(objectEntries({ foo: 'bar', bar: 123 })).toEqual([['foo', 'bar'], ['bar', 123]]) - }) - }) - - describe('objectValues', () => { - it('should return object values', () => { - expect(objectValues({ foo: 'bar', bar: 123 })).toEqual(['bar', 123]) - }) - // TODO test for enumerable properties only - }) - - describe('find', () => { - it('should return the value if found in an array', () => { - const input = [ - { firstName: 'jordan', lastName: 'foo' }, - { firstName: 'jordan', lastName: 'bar' }, - { firstName: 'james', lastName: 'foxy' }, - ] - - expect(find(input, item => item.firstName === 'jordan')).toEqual({ - firstName: 'jordan', - lastName: 'foo', - }) - }) - - it('should return undefined if NOT found in an array', () => { - const input = [ - { firstName: 'jordan', lastName: 'foo' }, - { firstName: 'jordan', lastName: 'bar' }, - { firstName: 'james', lastName: 'foxy' }, - ] - - expect(find(input, item => item.firstName === 'joe')).toBeUndefined() - }) - }) - - describe('keyBy', () => { - it('return an object with keys generated from the key function', () => { - const input = [ - { key: 'foo', firstName: 'jordan', lastName: 'foo' }, - { key: 'bar', firstName: 'jordan', lastName: 'bar' }, - { key: 'baz', firstName: 'james', lastName: 'foxy' }, - ] - - expect(keyBy(input, item => item.key)).toEqual({ - foo: { key: 'foo', firstName: 'jordan', lastName: 'foo' }, - bar: { key: 'bar', firstName: 'jordan', lastName: 'bar' }, - baz: { key: 'baz', firstName: 'james', lastName: 'foxy' }, - }) - }) - }) - - describe('sprintf', () => { - it('sprintf(msg)', () => { - expect(sprintf('this is my message')).toBe('this is my message') - }) - - it('sprintf(msg, arg1)', () => { - expect(sprintf('hi %s', 'jordan')).toBe('hi jordan') - }) - - it('sprintf(msg, arg1, arg2)', () => { - expect(sprintf('hi %s its %s', 'jordan', 'jon')).toBe('hi jordan its jon') - }) - - it('should print undefined if an argument is missing', () => { - expect(sprintf('hi %s its %s', 'jordan')).toBe('hi jordan its undefined') - }) - - it('should evaluate a function', () => { - expect(sprintf('hi %s its %s', 'jordan', () => 'a function')).toBe('hi jordan its a function') - }) - - it('should work with numbers', () => { - expect(sprintf('hi %s', 123)).toBe('hi 123') - }) - - it('should not error when passed an object', () => { - expect(sprintf('hi %s', { foo: 'bar' })).toBe('hi [object Object]') - }) - }) -}) diff --git a/packages/utils/jest.config.js b/packages/utils/jest.config.js deleted file mode 100644 index 391afe8eb..000000000 --- a/packages/utils/jest.config.js +++ /dev/null @@ -1,17 +0,0 @@ -module.exports = { - // "roots": [ - // "./src" - // ], - "transform": { - "^.+\\.tsx?$": "ts-jest" - }, - "testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.tsx?$", - "moduleFileExtensions": [ - "ts", - "tsx", - "js", - "jsx", - "json", - "node" - ], -} \ No newline at end of file diff --git a/packages/utils/package-lock.json b/packages/utils/package-lock.json deleted file mode 100644 index 1cb04e1d9..000000000 --- a/packages/utils/package-lock.json +++ /dev/null @@ -1,5342 +0,0 @@ -{ - "name": "@optimizely/js-sdk-utils", - "version": "0.4.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", - "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", - "dev": true, - "requires": { - "@babel/highlight": "^7.18.6" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz", - "integrity": "sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==", - "dev": true - }, - "@babel/highlight": { - "version": "7.18.6", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", - "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.18.6", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - } - } - }, - "@types/jest": { - "version": "23.3.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-23.3.14.tgz", - "integrity": "sha512-Q5hTcfdudEL2yOmluA1zaSyPbzWPmJ3XfSWeP3RyoYvS9hnje1ZyagrZOuQ6+1nQC1Gw+7gap3pLNL3xL6UBug==", - "dev": true - }, - "@types/node": { - "version": "11.9.4", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.9.4.tgz", - "integrity": "sha512-Zl8dGvAcEmadgs1tmSPcvwzO1YRsz38bVJQvH1RvRqSR9/5n61Q1ktcDL0ht3FXWR+ZpVmXVwN1LuH4Ax23NsA==", - "dev": true - }, - "@types/uuid": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.4.tgz", - "integrity": "sha512-tPIgT0GUmdJQNSHxp0X2jnpQfBSTfGxUMc/2CXBU2mnyTFVYVa2ojpoQ74w0U2yn2vw3jnC640+77lkFFpdVDw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "abab": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", - "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", - "dev": true - }, - "acorn": { - "version": "5.7.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", - "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", - "dev": true - }, - "acorn-globals": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", - "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", - "dev": true, - "requires": { - "acorn": "^6.0.1", - "acorn-walk": "^6.0.1" - }, - "dependencies": { - "acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "dev": true - } - } - }, - "acorn-walk": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", - "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", - "dev": true - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-escapes": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz", - "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==", - "dev": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - }, - "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 - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - } - } - }, - "append-transform": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", - "integrity": "sha512-Yisb7ew0ZEyDtRYQ+b+26o9KbiYPFxwcsxKzbssigzRRMJ9LpExPVUg6Fos7eP7yP3q7///tzze4nm4lTptPBw==", - "dev": true, - "requires": { - "default-require-extensions": "^1.0.0" - } - }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "requires": { - "sprintf-js": "~1.0.2" - } - }, - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha512-dtXTVMkh6VkEEA7OhXnN1Ecb8aAGFdZ1LFxtOCoqj4qkyOJMt7+qs6Ahdy6p/NQCPYsRSXXivhSB/J5E9jmYKA==", - "dev": true, - "requires": { - "arr-flatten": "^1.0.1" - } - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "dev": true - }, - "array-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha512-H3LU5RLiSsGXPhN+Nipar0iR0IofH+8r89G2y1tBKxQ/agagKyAjhkAFDRBfodP2caPrNKHpAWNIM/c9yeL7uA==", - "dev": true - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha512-G2n5bG5fSUCpnsXz4+8FUkYsGPkNfLn9YvS66U5qbTIXI2Ynnlo4Bi42bWv+omKUCqz+ejzfClwne0alJWJPhg==", - "dev": true - }, - "array.prototype.reduce": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.4.tgz", - "integrity": "sha512-WnM+AjG/DvLRLo4DDl+r+SvCzYtD2Jd9oeBYMcEaI7t3fFrHY9M53/wdLcTvmZNQ70IU6Htj0emFkZ5TS+lrdw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.2", - "es-array-method-boxes-properly": "^1.0.0", - "is-string": "^1.0.7" - } - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", - "dev": true - }, - "asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "dev": true - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true - }, - "async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dev": true, - "requires": { - "lodash": "^4.17.14" - } - }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true - }, - "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", - "dev": true - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true - } - } - }, - "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" - } - }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "dev": true, - "requires": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" - } - }, - "babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha512-n7pFrqQm44TCYvrCDb0MqabAF+JUBq+ijBvNMUxpkLjJaAu32faIexewMumrH5KLLJ1HDyT0PTEqRyAe/GwwuQ==", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-jest": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-23.6.0.tgz", - "integrity": "sha512-lqKGG6LYXYu+DQh/slrQ8nxXQkEkhugdXsU6St7GmhVS7Ilc/22ArwqXNJrf0QaOBjZB0360qZMwXqDYQHXaew==", - "dev": true, - "requires": { - "babel-plugin-istanbul": "^4.1.6", - "babel-preset-jest": "^23.2.0" - } - }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha512-Bl3ZiA+LjqaMtNYopA9TYE9HP1tQ+E5dLxE0XrAzcIJeK2UqF0/EaqXwBn9esd4UmTfEab+P+UYQ1GnioFIb/w==", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-istanbul": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", - "integrity": "sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==", - "dev": true, - "requires": { - "babel-plugin-syntax-object-rest-spread": "^6.13.0", - "find-up": "^2.1.0", - "istanbul-lib-instrument": "^1.10.1", - "test-exclude": "^4.2.1" - } - }, - "babel-plugin-jest-hoist": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-23.2.0.tgz", - "integrity": "sha512-N0MlMjZtahXK0yb0K3V9hWPrq5e7tThbghvDr0k3X75UuOOqwsWW6mk8XHD2QvEC0Ca9dLIfTgNU36TeJD6Hnw==", - "dev": true - }, - "babel-plugin-syntax-object-rest-spread": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", - "integrity": "sha512-C4Aq+GaAj83pRQ0EFgTvw5YO6T3Qz2KGrNRwIj9mSoNHVvdZY4KO2uA6HNtNXCw993iSZnckY1aLW8nOi8i4+w==", - "dev": true - }, - "babel-preset-jest": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-23.2.0.tgz", - "integrity": "sha512-AdfWwc0PYvDtwr009yyVNh72Ev68os7SsPmOFVX7zSA+STXuk5CV2iMVazZU01bEoHCSwTkgv4E4HOOcODPkPg==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^23.2.0", - "babel-plugin-syntax-object-rest-spread": "^6.13.0" - } - }, - "babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha512-veliHlHX06wjaeY8xNITbveXSiI+ASFnOqvne/LaIJIqOWi2Ogmj91KOugEz/hoh/fwMhXNBJPCv8Xaz5CyM4A==", - "dev": true, - "requires": { - "babel-core": "^6.26.0", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "home-or-tmp": "^2.0.0", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "source-map-support": "^0.4.15" - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha512-PCOcLFW7/eazGUKIoqH97sO9A2UYMahsn/yRQ7uOk37iutwjq7ODtcTNF+iFDSHNfkctqsLRjLP7URnOx0T1fg==", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha512-iSxeXx7apsjCHe9c7n8VtRXGzI2Bk1rBSOJgCCjfyXb6v1aCqE1KSEpq/8SXuVN8Ka/Rh1WDTF0MDzkvTA4MIA==", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - }, - "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 - } - } - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha512-xU7bpz2ytJl1bH9cgIurjpg/n8Gohy9GTw81heDYLJQ4RU60dlyJsa+atVF2pI0yMMvKxI9HkKwjePCj5XI1hw==", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } - }, - "browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "browser-resolve": { - "version": "1.11.3", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", - "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", - "dev": true, - "requires": { - "resolve": "1.1.7" - }, - "dependencies": { - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==", - "dev": true - } - } - }, - "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, - "requires": { - "fast-json-stable-stringify": "2.x" - } - }, - "bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - } - } - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==", - "dev": true - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - }, - "capture-exit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-1.2.0.tgz", - "integrity": "sha512-IS4lTgp57lUcpXzyCaiUQcRZBxZAkzl+jNXrMUXZjdnr2yujpKUMG9OYeYL29i6fL66ihypvVJ/MeX0B+9pWOg==", - "dev": true, - "requires": { - "rsvp": "^3.3.3" - } - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "ci-info": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.6.0.tgz", - "integrity": "sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A==", - "dev": true - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - } - } - }, - "cliui": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", - "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", - "dev": true, - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - } - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==", - "dev": true - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", - "dev": true - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", - "dev": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true - }, - "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", - "dev": true - }, - "core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", - "dev": true - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - }, - "cssstyle": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", - "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", - "dev": true, - "requires": { - "cssom": "0.3.x" - } - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "data-urls": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", - "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", - "dev": true, - "requires": { - "abab": "^2.0.0", - "whatwg-mimetype": "^2.2.0", - "whatwg-url": "^7.0.0" - }, - "dependencies": { - "whatwg-url": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", - "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - } - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dev": true - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==", - "dev": true - }, - "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 - }, - "default-require-extensions": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", - "integrity": "sha512-Dn2eAftOqXhNXs5f/Xjn7QTZ6kDYkx7u0EXQInN1oyYwsZysu11q7oTtaKcbzLxZRJiDHa8VmwpWmb4lY5FqgA==", - "dev": true, - "requires": { - "strip-bom": "^2.0.0" - } - }, - "define-properties": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", - "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", - "dev": true, - "requires": { - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - } - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - }, - "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 - } - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true - }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha512-BDKtmHlOzwI7iRuEkhzsnPoi5ypEhWAJB5RvHWe1kMr06js3uK5B3734i3ui5Yd+wOJV1cpE4JnivPD283GU/A==", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, - "detect-newline": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha512-CwffZFvlJffUg9zZA0uqrjQayUTC8ob94pnr5sFwaVv3IOmkfUHcWH+jXaQK3askE51Cqe8/9Ql/0uXNwqZ8Zg==", - "dev": true - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "domexception": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", - "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", - "dev": true, - "requires": { - "webidl-conversions": "^4.0.2" - } - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", - "dev": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "es-abstract": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.1.tgz", - "integrity": "sha512-WEm2oBhfoI2sImeM4OF2zE2V3BYdSF+KnSi9Sidz51fQHd7+JuF8Xgcj9/0o+OWeIeIS/MiuNnlruQrJf16GQA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "function.prototype.name": "^1.1.5", - "get-intrinsic": "^1.1.1", - "get-symbol-description": "^1.0.0", - "has": "^1.0.3", - "has-property-descriptors": "^1.0.0", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.3", - "is-callable": "^1.2.4", - "is-negative-zero": "^2.0.2", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "is-string": "^1.0.7", - "is-weakref": "^1.0.2", - "object-inspect": "^1.12.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "regexp.prototype.flags": "^1.4.3", - "string.prototype.trimend": "^1.0.5", - "string.prototype.trimstart": "^1.0.5", - "unbox-primitive": "^1.0.2" - } - }, - "es-array-method-boxes-properly": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", - "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==", - "dev": true - }, - "es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", - "dev": true, - "requires": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true - }, - "escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "dev": true, - "requires": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "optional": true - } - } - }, - "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 - }, - "estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "exec-sh": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.2.tgz", - "integrity": "sha512-FIUCJz1RbuS0FKTdaAafAByGS0CPvU3R0MeHxgtl+djzCc//F8HakL8GzmVNZanasTbTAY/3DRFA0KpVqj/eAw==", - "dev": true, - "requires": { - "merge": "^1.2.0" - } - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", - "dev": true - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha512-hxx03P2dJxss6ceIeri9cmYOT4SRs3Zk3afZwWpOsRqLqprhTR8u++SlC+sFGsQr7WGFPdMF7Gjc1njDLDK6UA==", - "dev": true, - "requires": { - "is-posix-bracket": "^0.1.0" - } - }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha512-AFASGfIlnIbkKPQwX1yHaDjFvh/1gyKJODme52V6IORh69uEYgZp0o9C+qsIGNVEiuuhQU0CSSl++Rlegg1qvA==", - "dev": true, - "requires": { - "fill-range": "^2.1.0" - } - }, - "expect": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-23.6.0.tgz", - "integrity": "sha512-dgSoOHgmtn/aDGRVFWclQyPDKl2CQRq0hmIEoUAuQs/2rn2NcvCWcSCovm6BLeuB/7EZuLGu2QfnR+qRt5OM4w==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "jest-diff": "^23.6.0", - "jest-get-type": "^22.1.0", - "jest-matcher-utils": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-regex-util": "^23.3.0" - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", - "dev": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha512-1FOj1LOwn42TMrruOHGt18HemVnbwAmAak7krWk+wa93KXxGbK+2jpezm+ytJYDaBX0/SPLZFHKM7m+tKobWGg==", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", - "dev": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true - }, - "fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, - "requires": { - "bser": "2.1.1" - } - }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "dev": true, - "optional": true - }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha512-BTCqyBaWBTsauvnHiE8i562+EdJj+oUpkqWp2R1iCoR8f6oo8STRu3of7WJJ0TqWtxN50a5YFpzYK4Jj9esYfQ==", - "dev": true - }, - "fileset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", - "integrity": "sha512-UxowFKnAFIwtmSxgKjWAVgjE3Fk7MQJT0ZIyl0NwIFZTrx4913rLaonGJ84V+x/2+w/pe4ULHRns+GZPs1TVuw==", - "dev": true, - "requires": { - "glob": "^7.0.3", - "minimatch": "^3.0.3" - } - }, - "fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", - "dev": true, - "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" - } - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", - "dev": true, - "requires": { - "locate-path": "^2.0.0" - } - }, - "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, - "requires": { - "is-callable": "^1.1.3" - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", - "dev": true - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", - "dev": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", - "dev": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "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 - }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "dev": true, - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "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, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "es-abstract": "^1.19.0", - "functions-have-names": "^1.2.2" - } - }, - "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 - }, - "get-caller-file": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz", - "integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.3" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, - "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, - "requires": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.1" - } - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "requires": { - "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" - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha512-ab1S1g1EbO7YzauaJLkgLp7DZVAqj9M/dvKlTt8DkXA2tiOIcSMrlVI2J1RZyB5iJVccEscjGn+kpOG9788MHA==", - "dev": true, - "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - } - }, - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha512-JDYOvfxio/t42HKdxkAYaCiBN7oYiuxykOxKxdaUW5Qn0zaYN3gRQWolrwdnf0shM9/EP0ebuuTmyoXNr1cC5w==", - "dev": true, - "requires": { - "is-glob": "^2.0.0" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true - }, - "growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw==", - "dev": true - }, - "handlebars": { - "version": "4.7.7", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz", - "integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==", - "dev": true, - "requires": { - "minimist": "^1.2.5", - "neo-async": "^2.6.0", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4", - "wordwrap": "^1.0.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", - "dev": true - }, - "har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "dev": true, - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "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 - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "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, - "requires": { - "get-intrinsic": "^1.1.1" - } - }, - "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 - }, - "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, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - } - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha512-ycURW7oUxE2sNiPVw1HVEFsW+ecOpJ5zaj7eC0RlwhibhRBod20muUN8qu/gzx956YrLolVvs1MTXwKgC2rVEg==", - "dev": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.1" - } - }, - "hosted-git-info": { - "version": "2.8.9", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", - "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", - "dev": true - }, - "html-encoding-sniffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", - "dev": true, - "requires": { - "whatwg-encoding": "^1.0.1" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "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, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "import-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", - "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", - "dev": true, - "requires": { - "pkg-dir": "^2.0.0", - "resolve-cwd": "^2.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true - }, - "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, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "internal-slot": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz", - "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==", - "dev": true, - "requires": { - "get-intrinsic": "^1.1.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - } - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, - "invert-kv": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "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 - }, - "is-bigint": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", - "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", - "dev": true, - "requires": { - "has-bigints": "^1.0.1" - } - }, - "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, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-callable": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.4.tgz", - "integrity": "sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w==", - "dev": true - }, - "is-ci": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.2.1.tgz", - "integrity": "sha512-s6tfsaQaQi3JNciBH6shVqEDvhGut0SUXr31ag8Pd8BBbVVlcGfWhpPmEOoM6RJ5TFhbypvf5yyRw/VXW1IiWg==", - "dev": true, - "requires": { - "ci-info": "^1.5.0" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "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, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha512-9YclgOGtN/f8zx0Pr4FQYMdibBiTaH3sn52vjYip4ZSf6C4/6RfTEZ+MR4GvKhCxdPh21Bg42/WL55f6KSnKpg==", - "dev": true - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha512-0EygVC5qPvIyb+gSz7zdD5/AAoS6Qrx1e//6N4yv4oNm30kqvdmG66oZFWVlQHUWe5OjP08FuTw2IdT0EOTcYA==", - "dev": true, - "requires": { - "is-primitive": "^2.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha512-7Q+VbVafe6x2T+Tu6NcOf6sRklazEPmBoB3IWk3WdGZM2iGUwU/Oe3Wtq5lSEkDTTlpp8yx+5t4pzO/i9Ty1ww==", - "dev": true - }, - "is-finite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dev": true - }, - "is-generator-fn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-1.0.0.tgz", - "integrity": "sha512-95jJZX6O/gdekidH2usRBr9WdRw4LU56CttPstXFxvG0r3QUE9eaIdz2p2Y7zrm6jxz7SjByAo1AtzwGlRvfOg==", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha512-a1dBeB19NXsf/E0+FHqkagizel/LQw2DjSQpvQrj3zT+jYPpaUCryPnrQajXKFLCMuf4I6FhRpaGtw4lPrG6Eg==", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "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 - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha512-QUzH43Gfb9+5yckcrSA0VBDwEtDUchrk4F6tfJZQuNzDJbEDB9cZNzSfXGQ1jqmdDY/kl41lUOWM9syA8z8jlg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "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, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "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, - "requires": { - "isobject": "^3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - } - } - }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha512-Yu68oeXJ7LeWNmZ3Zov/xg/oDBnBK2RNxwYY1ilNJX+tKKZqgPK+qOn/Gs9jEu66KDY9Netf5XLKNGzas/vPfQ==", - "dev": true - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha512-N3w1tFaRfk3UrPfqeRyD+GYDASU3W5VinKhlORy8EWVf/sIdDL9GAcew85XmktCfH+ngG7SRXEVDoO18WMdB/Q==", - "dev": true - }, - "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, - "requires": { - "call-bind": "^1.0.2", - "has-tostringtag": "^1.0.0" - } - }, - "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, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", - "dev": true - }, - "is-string": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", - "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", - "dev": true, - "requires": { - "has-tostringtag": "^1.0.0" - } - }, - "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, - "requires": { - "has-symbols": "^1.0.2" - } - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", - "dev": true - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", - "dev": true - }, - "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, - "requires": { - "call-bind": "^1.0.2" - } - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", - "dev": true - }, - "istanbul-api": { - "version": "1.3.7", - "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.7.tgz", - "integrity": "sha512-4/ApBnMVeEPG3EkSzcw25wDe4N66wxwn+KKn6b47vyek8Xb3NBAcg4xfuQbS7BqcZuTX4wxfD5lVagdggR3gyA==", - "dev": true, - "requires": { - "async": "^2.1.4", - "fileset": "^2.0.2", - "istanbul-lib-coverage": "^1.2.1", - "istanbul-lib-hook": "^1.2.2", - "istanbul-lib-instrument": "^1.10.2", - "istanbul-lib-report": "^1.1.5", - "istanbul-lib-source-maps": "^1.2.6", - "istanbul-reports": "^1.5.1", - "js-yaml": "^3.7.0", - "mkdirp": "^0.5.1", - "once": "^1.4.0" - } - }, - "istanbul-lib-coverage": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.1.tgz", - "integrity": "sha512-PzITeunAgyGbtY1ibVIUiV679EFChHjoMNRibEIobvmrCRaIgwLxNucOSimtNWUhEib/oO7QY2imD75JVgCJWQ==", - "dev": true - }, - "istanbul-lib-hook": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.2.tgz", - "integrity": "sha512-/Jmq7Y1VeHnZEQ3TL10VHyb564mn6VrQXHchON9Jf/AEcmQ3ZIiyD1BVzNOKTZf/G3gE+kiGK6SmpF9y3qGPLw==", - "dev": true, - "requires": { - "append-transform": "^0.4.0" - } - }, - "istanbul-lib-instrument": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.2.tgz", - "integrity": "sha512-aWHxfxDqvh/ZlxR8BBaEPVSWDPUkGD63VjGQn3jcw8jCp7sHEMKcrj4xfJn/ABzdMEHiQNyvDQhqm5o8+SQg7A==", - "dev": true, - "requires": { - "babel-generator": "^6.18.0", - "babel-template": "^6.16.0", - "babel-traverse": "^6.18.0", - "babel-types": "^6.18.0", - "babylon": "^6.18.0", - "istanbul-lib-coverage": "^1.2.1", - "semver": "^5.3.0" - } - }, - "istanbul-lib-report": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.5.tgz", - "integrity": "sha512-UsYfRMoi6QO/doUshYNqcKJqVmFe9w51GZz8BS3WB0lYxAllQYklka2wP9+dGZeHYaWIdcXUx8JGdbqaoXRXzw==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^1.2.1", - "mkdirp": "^0.5.1", - "path-parse": "^1.0.5", - "supports-color": "^3.1.2" - }, - "dependencies": { - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==", - "dev": true - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, - "istanbul-lib-source-maps": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.6.tgz", - "integrity": "sha512-TtbsY5GIHgbMsMiRw35YBHGpZ1DVFEO19vxxeiDMYaeOFOCzfnYVxvl6pOUIZR4dtPhAGpSMup8OyF8ubsaqEg==", - "dev": true, - "requires": { - "debug": "^3.1.0", - "istanbul-lib-coverage": "^1.2.1", - "mkdirp": "^0.5.1", - "rimraf": "^2.6.1", - "source-map": "^0.5.3" - }, - "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - } - } - }, - "istanbul-reports": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.5.1.tgz", - "integrity": "sha512-+cfoZ0UXzWjhAdzosCPP3AN8vvef8XDkWtTfgaN+7L3YTpNYITnCaEkceo5SEYy644VkHka/P1FvkWvrG/rrJw==", - "dev": true, - "requires": { - "handlebars": "^4.0.3" - } - }, - "jest": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-23.6.0.tgz", - "integrity": "sha512-lWzcd+HSiqeuxyhG+EnZds6iO3Y3ZEnMrfZq/OTGvF/C+Z4fPMCdhWTGSAiO2Oym9rbEXfwddHhh6jqrTF3+Lw==", - "dev": true, - "requires": { - "import-local": "^1.0.0", - "jest-cli": "^23.6.0" - }, - "dependencies": { - "jest-cli": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-23.6.0.tgz", - "integrity": "sha512-hgeD1zRUp1E1zsiyOXjEn4LzRLWdJBV//ukAHGlx6s5mfCNJTbhbHjgxnDUXA8fsKWN/HqFFF6X5XcCwC/IvYQ==", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.1", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "import-local": "^1.0.0", - "is-ci": "^1.0.10", - "istanbul-api": "^1.3.1", - "istanbul-lib-coverage": "^1.2.0", - "istanbul-lib-instrument": "^1.10.1", - "istanbul-lib-source-maps": "^1.2.4", - "jest-changed-files": "^23.4.2", - "jest-config": "^23.6.0", - "jest-environment-jsdom": "^23.4.0", - "jest-get-type": "^22.1.0", - "jest-haste-map": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-regex-util": "^23.3.0", - "jest-resolve-dependencies": "^23.6.0", - "jest-runner": "^23.6.0", - "jest-runtime": "^23.6.0", - "jest-snapshot": "^23.6.0", - "jest-util": "^23.4.0", - "jest-validate": "^23.6.0", - "jest-watcher": "^23.4.0", - "jest-worker": "^23.2.0", - "micromatch": "^2.3.11", - "node-notifier": "^5.2.1", - "prompts": "^0.1.9", - "realpath-native": "^1.0.0", - "rimraf": "^2.5.4", - "slash": "^1.0.0", - "string-length": "^2.0.0", - "strip-ansi": "^4.0.0", - "which": "^1.2.12", - "yargs": "^11.0.0" - } - } - } - }, - "jest-changed-files": { - "version": "23.4.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-23.4.2.tgz", - "integrity": "sha512-EyNhTAUWEfwnK0Is/09LxoqNDOn7mU7S3EHskG52djOFS/z+IT0jT3h3Ql61+dklcG7bJJitIWEMB4Sp1piHmA==", - "dev": true, - "requires": { - "throat": "^4.0.0" - } - }, - "jest-config": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-23.6.0.tgz", - "integrity": "sha512-i8V7z9BeDXab1+VNo78WM0AtWpBRXJLnkT+lyT+Slx/cbP5sZJ0+NDuLcmBE5hXAoK0aUp7vI+MOxR+R4d8SRQ==", - "dev": true, - "requires": { - "babel-core": "^6.0.0", - "babel-jest": "^23.6.0", - "chalk": "^2.0.1", - "glob": "^7.1.1", - "jest-environment-jsdom": "^23.4.0", - "jest-environment-node": "^23.4.0", - "jest-get-type": "^22.1.0", - "jest-jasmine2": "^23.6.0", - "jest-regex-util": "^23.3.0", - "jest-resolve": "^23.6.0", - "jest-util": "^23.4.0", - "jest-validate": "^23.6.0", - "micromatch": "^2.3.11", - "pretty-format": "^23.6.0" - } - }, - "jest-diff": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-23.6.0.tgz", - "integrity": "sha512-Gz9l5Ov+X3aL5L37IT+8hoCUsof1CVYBb2QEkOupK64XyRR3h+uRpYIm97K7sY8diFxowR8pIGEdyfMKTixo3g==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "diff": "^3.2.0", - "jest-get-type": "^22.1.0", - "pretty-format": "^23.6.0" - } - }, - "jest-docblock": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-23.2.0.tgz", - "integrity": "sha512-CB8MdScYLkzQ0Q/I4FYlt2UBkG9tFzi+ngSPVhSBB70nifaC+5iWz6GEfa/lB4T2KCqGy+DLzi1v34r9R1XzuA==", - "dev": true, - "requires": { - "detect-newline": "^2.1.0" - } - }, - "jest-each": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-23.6.0.tgz", - "integrity": "sha512-x7V6M/WGJo6/kLoissORuvLIeAoyo2YqLOoCDkohgJ4XOXSqOtyvr8FbInlAWS77ojBsZrafbozWoKVRdtxFCg==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "pretty-format": "^23.6.0" - } - }, - "jest-environment-jsdom": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-23.4.0.tgz", - "integrity": "sha512-UIXe32cMl/+DtyNHC15X+aFZMh04wx7PjWFBfz+nwoLgsIN2loKoNiKGSzUhMW/fVwbHrk8Qopglb7V4XB4EfQ==", - "dev": true, - "requires": { - "jest-mock": "^23.2.0", - "jest-util": "^23.4.0", - "jsdom": "^11.5.1" - } - }, - "jest-environment-node": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-23.4.0.tgz", - "integrity": "sha512-bk8qScgIfkb+EdwJ0JZ9xGvN7N3m6Qok73G8hi6tzvNadpe4kOxxuGmK2cJzAM3tPC/HBulzrOeNHEvaThQFrQ==", - "dev": true, - "requires": { - "jest-mock": "^23.2.0", - "jest-util": "^23.4.0" - } - }, - "jest-get-type": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", - "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", - "dev": true - }, - "jest-haste-map": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-23.6.0.tgz", - "integrity": "sha512-uyNhMyl6dr6HaXGHp8VF7cK6KpC6G9z9LiMNsst+rJIZ8l7wY0tk8qwjPmEghczojZ2/ZhtEdIabZ0OQRJSGGg==", - "dev": true, - "requires": { - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.1.11", - "invariant": "^2.2.4", - "jest-docblock": "^23.2.0", - "jest-serializer": "^23.0.1", - "jest-worker": "^23.2.0", - "micromatch": "^2.3.11", - "sane": "^2.0.0" - } - }, - "jest-jasmine2": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-23.6.0.tgz", - "integrity": "sha512-pe2Ytgs1nyCs8IvsEJRiRTPC0eVYd8L/dXJGU08GFuBwZ4sYH/lmFDdOL3ZmvJR8QKqV9MFuwlsAi/EWkFUbsQ==", - "dev": true, - "requires": { - "babel-traverse": "^6.0.0", - "chalk": "^2.0.1", - "co": "^4.6.0", - "expect": "^23.6.0", - "is-generator-fn": "^1.0.0", - "jest-diff": "^23.6.0", - "jest-each": "^23.6.0", - "jest-matcher-utils": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-snapshot": "^23.6.0", - "jest-util": "^23.4.0", - "pretty-format": "^23.6.0" - } - }, - "jest-leak-detector": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-23.6.0.tgz", - "integrity": "sha512-f/8zA04rsl1Nzj10HIyEsXvYlMpMPcy0QkQilVZDFOaPbv2ur71X5u2+C4ZQJGyV/xvVXtCCZ3wQ99IgQxftCg==", - "dev": true, - "requires": { - "pretty-format": "^23.6.0" - } - }, - "jest-matcher-utils": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-23.6.0.tgz", - "integrity": "sha512-rosyCHQfBcol4NsckTn01cdelzWLU9Cq7aaigDf8VwwpIRvWE/9zLgX2bON+FkEW69/0UuYslUe22SOdEf2nog==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-get-type": "^22.1.0", - "pretty-format": "^23.6.0" - } - }, - "jest-message-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-23.4.0.tgz", - "integrity": "sha512-Tjqy7T8jHhPgV4Gsi+pKMMfaz3uP5DPtMGnm8RWNWUHIk2igqxQ3/9rud3JkINCvZDGqlpJVuFGIDXbltG4xLA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0-beta.35", - "chalk": "^2.0.1", - "micromatch": "^2.3.11", - "slash": "^1.0.0", - "stack-utils": "^1.0.1" - } - }, - "jest-mock": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-23.2.0.tgz", - "integrity": "sha512-lz+Rf6dwRNDVowuGCXm93ib8hMyPntl1GGVt9PuZfBAmTjP5yKYgK14IASiEjs7XoMo4i/R7+dkrJY3eESwTJg==", - "dev": true - }, - "jest-regex-util": { - "version": "23.3.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-23.3.0.tgz", - "integrity": "sha512-pNilf1tXhv5z0qjJy2Hl6Ar6dsi+XX2zpCAuzxRs4qoputI0Bm9rU7pa2ErrFTfiHYe8VboTR7WATPZXqzpQ/g==", - "dev": true - }, - "jest-resolve": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-23.6.0.tgz", - "integrity": "sha512-XyoRxNtO7YGpQDmtQCmZjum1MljDqUCob7XlZ6jy9gsMugHdN2hY4+Acz9Qvjz2mSsOnPSH7skBmDYCHXVZqkA==", - "dev": true, - "requires": { - "browser-resolve": "^1.11.3", - "chalk": "^2.0.1", - "realpath-native": "^1.0.0" - } - }, - "jest-resolve-dependencies": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-23.6.0.tgz", - "integrity": "sha512-EkQWkFWjGKwRtRyIwRwI6rtPAEyPWlUC2MpzHissYnzJeHcyCn1Hc8j7Nn1xUVrS5C6W5+ZL37XTem4D4pLZdA==", - "dev": true, - "requires": { - "jest-regex-util": "^23.3.0", - "jest-snapshot": "^23.6.0" - } - }, - "jest-runner": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-23.6.0.tgz", - "integrity": "sha512-kw0+uj710dzSJKU6ygri851CObtCD9cN8aNkg8jWJf4ewFyEa6kwmiH/r/M1Ec5IL/6VFa0wnAk6w+gzUtjJzA==", - "dev": true, - "requires": { - "exit": "^0.1.2", - "graceful-fs": "^4.1.11", - "jest-config": "^23.6.0", - "jest-docblock": "^23.2.0", - "jest-haste-map": "^23.6.0", - "jest-jasmine2": "^23.6.0", - "jest-leak-detector": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-runtime": "^23.6.0", - "jest-util": "^23.4.0", - "jest-worker": "^23.2.0", - "source-map-support": "^0.5.6", - "throat": "^4.0.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - } - } - }, - "jest-runtime": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-23.6.0.tgz", - "integrity": "sha512-ycnLTNPT2Gv+TRhnAYAQ0B3SryEXhhRj1kA6hBPSeZaNQkJ7GbZsxOLUkwg6YmvWGdX3BB3PYKFLDQCAE1zNOw==", - "dev": true, - "requires": { - "babel-core": "^6.0.0", - "babel-plugin-istanbul": "^4.1.6", - "chalk": "^2.0.1", - "convert-source-map": "^1.4.0", - "exit": "^0.1.2", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.1.11", - "jest-config": "^23.6.0", - "jest-haste-map": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-regex-util": "^23.3.0", - "jest-resolve": "^23.6.0", - "jest-snapshot": "^23.6.0", - "jest-util": "^23.4.0", - "jest-validate": "^23.6.0", - "micromatch": "^2.3.11", - "realpath-native": "^1.0.0", - "slash": "^1.0.0", - "strip-bom": "3.0.0", - "write-file-atomic": "^2.1.0", - "yargs": "^11.0.0" - }, - "dependencies": { - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", - "dev": true - } - } - }, - "jest-serializer": { - "version": "23.0.1", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-23.0.1.tgz", - "integrity": "sha512-l6cPuiGEQI72H4+qMePF62E+URkZscnAqdHBYHkMrhKJOwU08AHvGmftXdosUzfCGhh/Ih4Xk1VgxnJSwrvQvQ==", - "dev": true - }, - "jest-snapshot": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-23.6.0.tgz", - "integrity": "sha512-tM7/Bprftun6Cvj2Awh/ikS7zV3pVwjRYU2qNYS51VZHgaAMBs5l4o/69AiDHhQrj5+LA2Lq4VIvK7zYk/bswg==", - "dev": true, - "requires": { - "babel-types": "^6.0.0", - "chalk": "^2.0.1", - "jest-diff": "^23.6.0", - "jest-matcher-utils": "^23.6.0", - "jest-message-util": "^23.4.0", - "jest-resolve": "^23.6.0", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "pretty-format": "^23.6.0", - "semver": "^5.5.0" - } - }, - "jest-util": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-23.4.0.tgz", - "integrity": "sha512-OS1/0QSbbMF9N93MxF1hUmK93EF3NGQGbbaTBZZk95aytWtWmzxsFWwt/UXIIkfHbPCK1fXTrPklbL+ohuFFOA==", - "dev": true, - "requires": { - "callsites": "^2.0.0", - "chalk": "^2.0.1", - "graceful-fs": "^4.1.11", - "is-ci": "^1.0.10", - "jest-message-util": "^23.4.0", - "mkdirp": "^0.5.1", - "slash": "^1.0.0", - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "jest-validate": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-23.6.0.tgz", - "integrity": "sha512-OFKapYxe72yz7agrDAWi8v2WL8GIfVqcbKRCLbRG9PAxtzF9b1SEDdTpytNDN12z2fJynoBwpMpvj2R39plI2A==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-get-type": "^22.1.0", - "leven": "^2.1.0", - "pretty-format": "^23.6.0" - } - }, - "jest-watcher": { - "version": "23.4.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-23.4.0.tgz", - "integrity": "sha512-BZGZYXnte/vazfnmkD4ERByi2O2mW+C++W8Sb7dvOnwcSccvCKNQgmcz1L+9hxVD7HWtqymPctIY7v5ZbQGNyg==", - "dev": true, - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.1", - "string-length": "^2.0.0" - } - }, - "jest-worker": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-23.2.0.tgz", - "integrity": "sha512-zx0uwPCDxToGfYyQiSHh7T/sKIxQFnQqT6Uug7Y/L7PzEkFITPaufjQe6yaf1OXSnGvKC5Fwol1hIym0zDzyvw==", - "dev": true, - "requires": { - "merge-stream": "^1.0.1" - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==", - "dev": true - }, - "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==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true - }, - "jsdom": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", - "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", - "dev": true, - "requires": { - "abab": "^2.0.0", - "acorn": "^5.5.3", - "acorn-globals": "^4.1.0", - "array-equal": "^1.0.0", - "cssom": ">= 0.3.2 < 0.4.0", - "cssstyle": "^1.0.0", - "data-urls": "^1.0.0", - "domexception": "^1.0.1", - "escodegen": "^1.9.1", - "html-encoding-sniffer": "^1.0.2", - "left-pad": "^1.3.0", - "nwsapi": "^2.0.7", - "parse5": "4.0.0", - "pn": "^1.1.0", - "request": "^2.87.0", - "request-promise-native": "^1.0.5", - "sax": "^1.2.4", - "symbol-tree": "^3.2.2", - "tough-cookie": "^2.3.4", - "w3c-hr-time": "^1.0.1", - "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.3", - "whatwg-mimetype": "^2.1.0", - "whatwg-url": "^6.4.1", - "ws": "^5.2.0", - "xml-name-validator": "^3.0.0" - } - }, - "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha512-Mke0DA0QjUWuJlhsE0ZPPhYiJkRap642SmI/4ztCFaUs6V2AiH1sfecc+57NgaryfAA2VR3v6O+CSjC1jZJKOA==", - "dev": true - }, - "json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "dev": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "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 - }, - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw==", - "dev": true - }, - "jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "dev": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - }, - "kleur": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-2.0.2.tgz", - "integrity": "sha512-77XF9iTllATmG9lSlIv0qdQ2BQ/h9t0bJllHlbvsQ0zUWfU7Yi0S8L5JXzPZgkefIiajLmBJJ4BsMJmqcf7oxQ==", - "dev": true - }, - "lcid": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", - "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", - "dev": true, - "requires": { - "invert-kv": "^2.0.0" - } - }, - "left-pad": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", - "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", - "dev": true - }, - "leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", - "dev": true, - "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", - "dev": true - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "make-error": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", - "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", - "dev": true - }, - "makeerror": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", - "integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==", - "dev": true, - "requires": { - "tmpl": "1.0.5" - } - }, - "map-age-cleaner": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", - "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", - "dev": true, - "requires": { - "p-defer": "^1.0.0" - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==", - "dev": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", - "dev": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "math-random": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", - "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==", - "dev": true - }, - "mem": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", - "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", - "dev": true, - "requires": { - "map-age-cleaner": "^0.1.1", - "mimic-fn": "^2.0.0", - "p-is-promise": "^2.0.0" - } - }, - "merge": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz", - "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", - "dev": true - }, - "merge-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", - "integrity": "sha512-e6RM36aegd4f+r8BZCcYXlO2P3H6xbUM6ktL2Xmf45GAOit9bI4z6/3VU7JwllVO1L7u0UDSg/EhzQ5lmMLolA==", - "dev": true, - "requires": { - "readable-stream": "^2.0.1" - } - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha512-LnU2XFEk9xxSJ6rfgAry/ty5qwUTyHYOBU0g4R6tIw5ljwgGIBmiKhRWLw5NpMOnrgUNcDJ4WMp8rl3sYVHLNA==", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - } - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "requires": { - "mime-db": "1.52.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "nan": { - "version": "2.16.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.16.0.tgz", - "integrity": "sha512-UdAqHyFngu7TfQKsCBgAA6pWDkT8MAO7d0jyOecVhN5354xbLqdn8mV9Tat9gepAupm0bt2DbeaSC8vS52MuFA==", - "dev": true, - "optional": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "dev": true - }, - "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 - } - } - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true - }, - "neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "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-notifier": { - "version": "5.4.5", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.4.5.tgz", - "integrity": "sha512-tVbHs7DyTLtzOiN78izLA85zRqB9NvEXkAf014Vx3jtSvn/xBl6bR8ZYifj+dFcFrKI21huSQgJZ6ZtL3B4HfQ==", - "dev": true, - "requires": { - "growly": "^1.3.0", - "is-wsl": "^1.1.0", - "semver": "^5.5.0", - "shellwords": "^0.1.1", - "which": "^1.3.0" - } - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==", - "dev": true - }, - "nwsapi": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.1.tgz", - "integrity": "sha512-JYOWTeFoS0Z93587vRJgASD5Ut11fYl5NyihP3KrYBvMe1FRRs6RN7m20SA/16GM4P6hTnZjT+UmDOt38UeXNg==", - "dev": true - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true - }, - "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 - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "object-inspect": { - "version": "1.12.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz", - "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", - "dev": true, - "requires": { - "isobject": "^3.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - } - } - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.4.tgz", - "integrity": "sha512-sccv3L/pMModT6dJAYF3fzGMVcb38ysQ0tEE6ixv2yXJDtEIPph268OlAdJj5/qZMZDq2g/jqvwppt36uS/uQQ==", - "dev": true, - "requires": { - "array.prototype.reduce": "^1.0.4", - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.1" - } - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha512-UiAM5mhmIuKLsOvrL+B0U2d1hXHF3bFYWIuH1LMpuV2EJEHG1Ntz06PgLEHjm6VFd87NpH8rastvPoyv6UW2fA==", - "dev": true, - "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - } - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", - "dev": true - }, - "os-locale": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", - "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", - "dev": true, - "requires": { - "execa": "^1.0.0", - "lcid": "^2.0.0", - "mem": "^4.0.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true - }, - "p-defer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==", - "dev": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", - "dev": true - }, - "p-is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", - "dev": true - }, - "p-limit": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", - "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, - "requires": { - "p-try": "^1.0.0" - } - }, - "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", - "dev": true, - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", - "dev": true - }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha512-FC5TeK0AwXzq3tUBFtH74naWkPQCEWs4K+xMxWZBlKDWu0bVHXGZa+KKqxKidd7xwhdZ19ZNuF2uO1M/r196HA==", - "dev": true, - "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "parse5": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", - "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", - "dev": true - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==", - "dev": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "dev": true - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", - "dev": true - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", - "dev": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", - "dev": true, - "requires": { - "pinkie": "^2.0.0" - } - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha512-ojakdnUgL5pzJYWw2AIDEupaQCX5OPbM688ZevubICjdIX01PRSYKqm33fJoCOJBRseYCTUlQRnBNX+Pchaejw==", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - }, - "pn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", - "dev": true - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "dev": true - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha512-s/46sYeylUfHNjI+sA/78FAHlmIuKqI9wNnzEOGehAlUUYeObv5C2mOinXBjyUyWmJ2SfcS2/ydApH4hTF4WXQ==", - "dev": true - }, - "pretty-format": { - "version": "23.6.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-23.6.0.tgz", - "integrity": "sha512-zf9NV1NSlDLDjycnwm6hpFATCGl/K1lt0R/GdkAK2O5LN/rwJoB+Mh93gGJjut4YbmecbfgLWVGSTCr0Ewvvbw==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true - } - } - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "prompts": { - "version": "0.1.14", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-0.1.14.tgz", - "integrity": "sha512-rxkyiE9YH6zAz/rZpywySLKkpaj0NMVyNw1qhsubdbjjSgcayjTShDreZGlFMcGSu5sab3bAKPfFk78PB90+8w==", - "dev": true, - "requires": { - "kleur": "^2.0.1", - "sisteransi": "^0.1.1" - } - }, - "psl": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", - "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", - "dev": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true - }, - "qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "dev": true - }, - "randomatic": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", - "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", - "dev": true, - "requires": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - }, - "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 - } - } - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", - "dev": true, - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", - "dev": true, - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - }, - "dependencies": { - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - } - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "realpath-native": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.1.0.tgz", - "integrity": "sha512-wlgPA6cCIIg9gKz0fgAPjnzh4yR/LnXovwuo9hvyGvx3h8nX4+/iLZplfUWasXpqD8BdnGnP5njOFjkUwPzvjA==", - "dev": true, - "requires": { - "util.promisify": "^1.0.0" - } - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "dev": true, - "requires": { - "is-equal-shallow": "^0.1.3" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "regexp.prototype.flags": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", - "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.3", - "functions-have-names": "^1.2.2" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==", - "dev": true - }, - "repeat-element": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", - "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "dev": true - }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "dev": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - } - }, - "request-promise-core": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", - "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", - "dev": true, - "requires": { - "lodash": "^4.17.19" - } - }, - "request-promise-native": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", - "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", - "dev": true, - "requires": { - "request-promise-core": "1.1.4", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha512-IqSUtOVP4ksd1C/ej5zeEh/BIP2ajqpn8c5x+q99gvcIG/Qf0cud5raVnE/Dwd0ua9TXYDoDc0RE5hBSdz22Ug==", - "dev": true - }, - "resolve": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", - "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", - "dev": true, - "requires": { - "path-parse": "^1.0.6" - } - }, - "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha512-ccu8zQTrzVr954472aUVPLEcB3YpKSYR3cg/3lo1okzobPBM+1INXBbBZlDbnI/hbEocnf8j0QVo43hQKrbchg==", - "dev": true, - "requires": { - "resolve-from": "^3.0.0" - } - }, - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", - "dev": true - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", - "dev": true - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - }, - "rsvp": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz", - "integrity": "sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==", - "dev": true - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", - "dev": true, - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "sane": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/sane/-/sane-2.5.2.tgz", - "integrity": "sha512-OuZwD1QJ2R9Dbnhd7Ur8zzD8l+oADp9npyxK63Q9nZ4AjhB2QwDQcQlD8iuUsGm5AZZqtEuCaJvK1rxGRxyQ1Q==", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "capture-exit": "^1.2.0", - "exec-sh": "^0.2.0", - "fb-watchman": "^2.0.0", - "fsevents": "^1.2.3", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5", - "watch": "~0.18.0" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - }, - "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 - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - } - } - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true - }, - "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", - "dev": true - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dev": true, - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", - "dev": true - }, - "shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true - }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "sisteransi": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-0.1.1.tgz", - "integrity": "sha512-PmGOd02bM9YO5ifxpw36nrNMBTptEtfRl4qUYl9SndkolplkrZZOW7PGHjrZL53QvMVj9nQ+TKqUnRsw4tJa4g==", - "dev": true - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==", - "dev": true - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - }, - "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 - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "requires": { - "kind-of": "^3.2.0" - } - }, - "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 - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "^0.5.6" - } - }, - "source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "dev": true - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", - "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==", - "dev": true - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", - "dev": true - }, - "sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "dev": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "stack-utils": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.5.tgz", - "integrity": "sha512-KZiTzuV3CnSnSvgMRrARVCj+Ht7rMbauGDK0LdVFRGyenwdylpajAp4Q0i6SX8rEmbTpMMf6ryq2gb8pPq2WgQ==", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "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==", - "dev": true - } - } - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha512-72E9+uLc27Mt718pMHt9VMNiAL4LMsmDbBva8mxWUCkT07fSzEGMYUCk0XWY6lp0j6RBAG4cJ3mWuZv2OE3s0g==", - "dev": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha512-ZnWpYnYugiOVEY5GkcuJK1io5V8QmNYChG62gSit9pQVGErXtrKuPC55ITaVSukmMta5qpMU7vqLt2Lnni4f/g==", - "dev": true - }, - "string-length": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", - "integrity": "sha512-Qka42GGrS8Mm3SZ+7cH8UXiIWI867/b/Z/feQSpQx/rbfB8UGknGEZVaUQMOUVj+soY6NpWAxily63HI1OckVQ==", - "dev": true, - "requires": { - "astral-regex": "^1.0.0", - "strip-ansi": "^4.0.0" - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "string.prototype.trimend": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz", - "integrity": "sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - } - }, - "string.prototype.trimstart": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.5.tgz", - "integrity": "sha512-THx16TJCGlsN0o6dl2o6ncWUsdgnLRSA23rRE5pyGBw/mLr3Ej/R2LaqCtgP8VNMGZsvMWnf9ooZPyY2bHvUFg==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.19.5" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true - } - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "test-exclude": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.3.tgz", - "integrity": "sha512-SYbXgY64PT+4GAL2ocI3HwPa4Q4TBKm0cwAVeKOt/Aoc0gSpNRjJX8w0pA1LMKZ3LBmd8pYBqApFNQLII9kavA==", - "dev": true, - "requires": { - "arrify": "^1.0.1", - "micromatch": "^2.3.11", - "object-assign": "^4.1.0", - "read-pkg-up": "^1.0.1", - "require-main-filename": "^1.0.1" - } - }, - "throat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", - "integrity": "sha512-wCVxLDcFxw7ujDxaeJC6nfl2XfHJNYs8yUYJnvMgtPEFlttP9tHSfRUv2vBe6C4hkVFPWoP1P6ZccbYjmSEkKA==", - "dev": true - }, - "tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha512-lxrWP8ejsq+7E3nNjwYmUBMAgjMTZoTI+sdBOpvNyijeDLa29LUn9QaoXAHv4+Z578hbmHHJKZknzxVtvo77og==", - "dev": true - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha512-9mWHdnGRuh3onocaHzukyvCZhzvr6tiflAy/JRFXcJX0TjgfWA9pk9t8CMbzmBE4Jfw58pXbkngtBtqYxzNEyg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha512-ZZWNfCjUokXXDGXFpZehJIkZqq91BcULFq/Pi7M5i4JnxXdhMKAK682z8bCW3o8Hj1wuuzoKcW3DfVzaP6VuNg==", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - } - } - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==", - "dev": true - }, - "ts-jest": { - "version": "23.10.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-23.10.5.tgz", - "integrity": "sha512-MRCs9qnGoyKgFc8adDEntAOP64fWK1vZKnOYU1o2HxaqjdJvGqmkLCPCnVq1/If4zkUmEjKPnCiUisTrlX2p2A==", - "dev": true, - "requires": { - "bs-logger": "0.x", - "buffer-from": "1.x", - "fast-json-stable-stringify": "2.x", - "json5": "2.x", - "make-error": "1.x", - "mkdirp": "0.x", - "resolve": "1.x", - "semver": "^5.5", - "yargs-parser": "10.x" - }, - "dependencies": { - "json5": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", - "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", - "dev": true, - "requires": { - "minimist": "^1.2.0" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "yargs-parser": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-10.1.0.tgz", - "integrity": "sha512-VCIyR1wJoEBZUqk5PA+oOBF6ypbwh5aNB3I50guxAL/quggdfs4TtNHQrSazFA3fYZ+tEqfs0zIGlv0c/rgjbQ==", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - } - } - } - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", - "dev": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", - "dev": true - }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", - "dev": true - }, - "uglify-js": { - "version": "3.16.3", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.16.3.tgz", - "integrity": "sha512-uVbFqx9vvLhQg0iBaau9Z75AxWJ8tqM9AV890dIZCLApF4rTcyHwmAvLeEdYRs+BzYWu8Iw81F79ah0EfTXbaw==", - "dev": true, - "optional": true - }, - "unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" - } - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha512-PcA2tsuGSF9cnySLHTLSh2qrQiJ70mn+r+Glzxv2TWZblxsxCC52BDlZoPCsz7STd9pN7EZetkWZBAvk4cgZdQ==", - "dev": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", - "dev": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", - "dev": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true - } - } - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha512-Am1ousAhSLBeB9cG/7k7r2R0zj50uDRlZHPGbazid5s9rlF1F/QKYObEKSIunSjIOkJZqwRRLpvewjEkM7pSqg==", - "dev": true - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true - }, - "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 - }, - "util.promisify": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.1.1.tgz", - "integrity": "sha512-/s3UsZUrIfa6xDhr7zZhnE9SLQ5RIXyYfiVnMMyMDzOc8WhWN4Nbh36H842OyurKbCDAesZOJaVyvmSl6fhGQw==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "for-each": "^0.3.3", - "has-symbols": "^1.0.1", - "object.getownpropertydescriptors": "^2.1.1" - } - }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==" - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "requires": { - "browser-process-hrtime": "^1.0.0" - } - }, - "walker": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", - "integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==", - "dev": true, - "requires": { - "makeerror": "1.0.12" - } - }, - "watch": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/watch/-/watch-0.18.0.tgz", - "integrity": "sha512-oUcoHFG3UF2pBlHcMORAojsN09BfqSfWYWlR3eSSjUFR7eBEx53WT2HX/vZeVTTIVCGShcazb+t6IcBRCNXqvA==", - "dev": true, - "requires": { - "exec-sh": "^0.2.0", - "minimist": "^1.2.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - } - } - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - }, - "whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.24" - } - }, - "whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "whatwg-url": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", - "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.1", - "webidl-conversions": "^4.0.2" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "which-boxed-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", - "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", - "dev": true, - "requires": { - "is-bigint": "^1.0.1", - "is-boolean-object": "^1.1.0", - "is-number-object": "^1.0.4", - "is-string": "^1.0.5", - "is-symbol": "^1.0.3" - } - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", - "dev": true - }, - "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 - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", - "dev": true - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha512-vAaEaDM946gbNpH5pLVNR+vX2ht6n0Bt3GXwVB1AuAqZosOvHNF3P7wDnh8KLkSqgUh0uh77le7Owgoz+Z9XBw==", - "dev": true, - "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha512-1pqUqRjkhPJ9miNq9SwMfdvi6lBJcd6eFxvfaivQhaH3SgisfiuudvFntdKOmxuee/77l+FPjKrQjWvmPjWrRw==", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha512-0XsVpQLnVCXHJfyEs8tC0zpTVIr5PKKsQtkT29IwupnPTjtPmQ3xT/4yCREF9hYkV/3M3kzcUTSAZT6a6h81tw==", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true - }, - "write-file-atomic": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", - "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "ws": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.3.tgz", - "integrity": "sha512-jZArVERrMsKUatIdnLzqvcfydI85dvd/Fp1u/VOpfdDWQ4c9qWXe+VIeAbQ5FrDwciAkr+lzofXLz3Kuf26AOA==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0" - } - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "y18n": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", - "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==", - "dev": true - }, - "yargs": { - "version": "11.1.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.1.tgz", - "integrity": "sha512-PRU7gJrJaXv3q3yQZ/+/X6KBswZiaQ+zOmdprZcouPYtQgvNU35i+68M4b1ZHLZtYFT5QObFLV+ZkmJYcwKdiw==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", - "get-caller-file": "^1.0.1", - "os-locale": "^3.1.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^9.0.2" - } - }, - "yargs-parser": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-9.0.2.tgz", - "integrity": "sha512-CswCfdOgCr4MMsT1GzbEJ7Z2uYudWyrGX8Bgh/0eyCzj/DXWdKq6a/ADufkzI1WAOIW6jYaXJvRyLhDO0kfqBw==", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - } - } - } -} diff --git a/packages/utils/package.json b/packages/utils/package.json deleted file mode 100644 index 92bcaaf1b..000000000 --- a/packages/utils/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "@optimizely/js-sdk-utils", - "version": "0.4.0", - "description": "Optimizely Full Stack Utils", - "author": "jordangarcia <jordan@optimizely.com>", - "homepage": "https://github.com/optimizely/javascript-sdk/tree/master/packages/utils", - "license": "Apache-2.0", - "main": "lib/index.js", - "types": "lib/index.d.ts", - "directories": { - "lib": "lib", - "test": "test" - }, - "files": [ - "lib" - ], - "scripts": { - "clean": "rm -rf lib", - "prebuild": "npm run clean", - "build": "tsc", - "test": "jest", - "prepare": "npm run build", - "prepublishOnly": "npm test" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/optimizely/javascript-sdk.git", - "directory": "packages/utils" - }, - "keywords": [ - "optimizely" - ], - "bugs": { - "url": "https://github.com/optimizely/javascript-sdk/issues" - }, - "publishConfig": { - "access": "public" - }, - "dependencies": { - "uuid": "^3.3.2" - }, - "devDependencies": { - "@types/jest": "^23.3.12", - "@types/uuid": "^3.4.4", - "jest": "^23.6.0", - "ts-jest": "^23.10.5", - "typescript": "^4.7.4" - } -} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts deleted file mode 100644 index cd7dec342..000000000 --- a/packages/utils/src/index.ts +++ /dev/null @@ -1,157 +0,0 @@ -/** - * Copyright 2019, Optimizely - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import { v4 } from 'uuid'; - -export function generateUUID(): string { - return v4() -} - -export type Omit<T, K> = Pick<T, Exclude<keyof T, K>> - -export function getTimestamp(): number { - return new Date().getTime() -} - -/** - * Validates a value is a valid TypeScript enum - * - * @export - * @param {object} enumToCheck - * @param {*} value - * @returns {boolean} - */ -export function isValidEnum(enumToCheck: { [key: string]: any }, value: any): boolean { - let found = false - - const keys = Object.keys(enumToCheck) - for (let index = 0; index < keys.length; index++) { - if (value === enumToCheck[keys[index]]) { - found = true - break - } - } - return found -} - -export function groupBy<K>(arr: K[], grouperFn: (item: K) => string): Array<K[]> { - const grouper: { [key: string]: K[] } = {} - - arr.forEach(item => { - const key = grouperFn(item) - grouper[key] = grouper[key] || [] - grouper[key].push(item) - }) - - return objectValues(grouper) -} - -export function objectValues<K>(obj: { [key: string]: K }): K[] { - return Object.keys(obj).map(key => obj[key]) -} - -export function objectEntries<K>(obj: { [key: string]: K }): [string, K][] { - return Object.keys(obj).map(key => [key, obj[key]]) -} - -export function find<K>(arr: K[], cond: (arg: K) => boolean): K | undefined { - let found - - for (let item of arr) { - if (cond(item)) { - found = item - break - } - } - - return found -} - -export function keyBy<K>(arr: K[], keyByFn: (item: K) => string): { [key: string]: K } { - let map: { [key: string]: K } = {} - arr.forEach(item => { - const key = keyByFn(item) - map[key] = item - }) - return map -} - -export function sprintf(format: string, ...args: any[]): string { - var i = 0 - return format.replace(/%s/g, function() { - const arg = args[i++] - const type = typeof arg - if (type === 'function') { - return arg() - } else if (type === 'string') { - return arg - } else { - return String(arg) - } - }) -} -/* - * Notification types for use with NotificationCenter - * Format is EVENT: <list of parameters to callback> - * - * SDK consumers can use these to register callbacks with the notification center. - * - * @deprecated since 3.1.0 - * ACTIVATE: An impression event will be sent to Optimizely - * Callbacks will receive an object argument with the following properties: - * - experiment {Object} - * - userId {string} - * - attributes {Object|undefined} - * - variation {Object} - * - logEvent {Object} - * - * DECISION: A decision is made in the system. i.e. user activation, - * feature access or feature-variable value retrieval - * Callbacks will receive an object argument with the following properties: - * - type {string} - * - userId {string} - * - attributes {Object|undefined} - * - decisionInfo {Object|undefined} - * - * LOG_EVENT: A batch of events, which could contain impressions and/or conversions, - * will be sent to Optimizely - * Callbacks will receive an object argument with the following properties: - * - url {string} - * - httpVerb {string} - * - params {Object} - * - * OPTIMIZELY_CONFIG_UPDATE: This Optimizely instance has been updated with a new - * config - * - * TRACK: A conversion event will be sent to Optimizely - * Callbacks will receive the an object argument with the following properties: - * - eventKey {string} - * - userId {string} - * - attributes {Object|undefined} - * - eventTags {Object|undefined} - * - logEvent {Object} - * - */ -export enum NOTIFICATION_TYPES { - ACTIVATE = 'ACTIVATE:experiment, user_id,attributes, variation, event', - DECISION = 'DECISION:type, userId, attributes, decisionInfo', - LOG_EVENT = 'LOG_EVENT:logEvent', - OPTIMIZELY_CONFIG_UPDATE = 'OPTIMIZELY_CONFIG_UPDATE', - TRACK = 'TRACK:event_key, user_id, attributes, event_tags, event', -} - -export interface NotificationCenter { - sendNotifications(notificationType: NOTIFICATION_TYPES, notificationData?: any): void -} diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json deleted file mode 100644 index 408b0e9a6..000000000 --- a/packages/utils/tsconfig.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "compilerOptions": { - "outDir": "./lib" - }, - "include": [ - "./src" - ], - "exclude": [ - "./lib", - "./src/**/*.spec.ts" - ] -} \ No newline at end of file diff --git a/rollup.config.js b/rollup.config.js new file mode 100644 index 000000000..f7cc6c247 --- /dev/null +++ b/rollup.config.js @@ -0,0 +1,216 @@ +/** + * Copyright 2020-2022 Optimizely + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import commonjs from '@rollup/plugin-commonjs'; +import { terser } from 'rollup-plugin-terser'; +import resolve from '@rollup/plugin-node-resolve'; +import { dependencies, peerDependencies } from './package.json'; +import typescript from 'rollup-plugin-typescript2'; + +const typescriptPluginOptions = { + allowJs: true, + exclude: [ + './dist', + './lib/**/*.tests.js', + './lib/**/*.tests.ts', + './lib/**/*.umdtests.js', + './lib/tests', + 'node_modules', + ], + include: ['./lib/**/*.ts', './lib/**/*.js'], + tsconfigOverride: { + compilerOptions: { + paths: { + "*": [ + "./typings/*" + ], + "error_message": [ + "./lib/message/error_message.gen" + ], + "log_message": [ + "./lib/message/log_message.gen" + ], + } + } + } +}; + +const cjsBundleFor = (platform, opt = {}) => { + const { minify, ext } = { + minify: true, + ext: '.js', + ...opt, + }; + + const min = minify ? '.min' : ''; + + return { + plugins: [resolve(), commonjs(), typescript(typescriptPluginOptions)], + external: ['https', 'http', 'url'].concat(Object.keys({ ...dependencies, ...peerDependencies } || {})), + input: `lib/index.${platform}.ts`, + output: { + exports: 'named', + format: 'cjs', + file: `dist/index.${platform}${min}${ext}`, + plugins: minify ? [terser()] : undefined, + sourcemap: true, + }, + } +}; + +const esmBundleFor = (platform, opt) => { + const { minify, ext } = { + minify: true, + ext: '.js', + ...opt, + }; + + const min = minify ? '.min' : ''; + + return { + ...cjsBundleFor(platform), + output: [ + { + format: 'es', + file: `dist/index.${platform}.es${min}${ext}`, + plugins: minify ? [terser()] : undefined, + sourcemap: true, + }, + ], + } +}; + +const cjsBundleForUAParser = (opt = {}) => { + const { minify, ext } = { + minify: true, + ext: '.js', + ...opt, + }; + + const min = minify ? '.min' : ''; + + return { + plugins: [resolve(), commonjs(), typescript(typescriptPluginOptions)], + external: ['https', 'http', 'url'].concat(Object.keys({ ...dependencies, ...peerDependencies } || {})), + input: `lib/odp/ua_parser/ua_parser.ts`, + output: { + exports: 'named', + format: 'cjs', + file: `dist/ua_parser${min}${ext}`, + plugins: minify ? [terser()] : undefined, + sourcemap: true, + }, + }; +}; + +const esmBundleForUAParser = (opt = {}) => { + const { minify, ext } = { + minify: true, + ext: '.js', + ...opt, + }; + + const min = minify ? '.min' : ''; + + return { + ...cjsBundleForUAParser(), + output: [ + { + format: 'es', + file: `dist/ua_parser.es${min}${ext}`, + plugins: minify ? [terser()] : undefined, + sourcemap: true, + }, + ], + }; +}; + +const umdBundle = { + plugins: [ + resolve({ browser: true }), + commonjs({ + namedExports: { + '@optimizely/js-sdk-event-processor': ['LogTierV1EventProcessor', 'LocalStoragePendingEventsDispatcher'], + 'json-schema': ['validate'], + }, + }), + typescript(typescriptPluginOptions), + ], + input: 'lib/index.browser.ts', + output: [ + { + name: 'optimizelySdk', + format: 'umd', + file: 'dist/optimizely.browser.umd.js', + exports: 'named', + }, + { + name: 'optimizelySdk', + format: 'umd', + file: 'dist/optimizely.browser.umd.min.js', + exports: 'named', + plugins: [terser()], + sourcemap: true, + }, + ], +}; + +// A separate bundle for json schema validator. +const jsonSchemaBundle = { + plugins: [resolve(), commonjs(), typescript(typescriptPluginOptions)], + external: ['json-schema', 'uuid'], + input: 'lib/utils/json_schema_validator/index.ts', + output: { + exports: 'named', + format: 'cjs', + file: 'dist/optimizely.json_schema_validator.min.js', + plugins: [terser()], + sourcemap: true, + }, +}; + +const bundles = { + 'cjs-node-min': cjsBundleFor('node'), + 'cjs-browser-min': cjsBundleFor('browser'), + 'cjs-react-native-min': cjsBundleFor('react_native'), + 'cjs-universal': cjsBundleFor('universal'), + 'esm-browser-min': esmBundleFor('browser'), + 'esm-node-min': esmBundleFor('node', { ext: '.mjs' }), + 'esm-react-native-min': esmBundleFor('react_native'), + 'esm-universal': esmBundleFor('universal'), + 'json-schema': jsonSchemaBundle, + 'cjs-ua-parser-min': cjsBundleForUAParser(), + 'esm-ua-parser-min': esmBundleForUAParser(), + umd: umdBundle, +}; + +// Collect all --config-* options and return the matching bundle configs +// Builds all bundles if no --config-* option given +// --config-cjs will build all three cjs-* bundles +// --config-umd will build only the umd bundle +// --config-umd --config-json will build both umd and the json-schema bundles +export default args => { + const patterns = Object.keys(args) + .filter(arg => arg.startsWith('config-')) + .map(arg => arg.replace(/config-/, '')); + + // default to matching all bundles + if (!patterns.length) patterns.push(/.*/); + + return Object.entries(bundles) + .filter(([name, config]) => patterns.some(pattern => name.match(pattern))) + .map(([name, config]) => config); +}; diff --git a/packages/optimizely-sdk/srcclr.yml b/srcclr.yml similarity index 100% rename from packages/optimizely-sdk/srcclr.yml rename to srcclr.yml diff --git a/tsconfig.json b/tsconfig.json index 1073836e9..e70a7ce62 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,63 +1,49 @@ { "compilerOptions": { - /* Basic Options */ - "target": "es5" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */, - "module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */, + "target": "ES6", + "module": "ESNext", "lib": [ - "es2015", - "dom" - ] /* Specify library files to be included in the compilation. */, - // "allowJs": true, /* Allow javascript files to be compiled. */ - // "checkJs": true, /* Report errors in .js files. */ - // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ - "declaration": true /* Generates corresponding '.d.ts' file. */, - // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ - // "sourceMap": true, /* Generates corresponding '.map' file. */ - // "outFile": "./", /* Concatenate and emit output to single file. */ - // "outDir": "./", /* Redirect output structure to the directory. */ - // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ - // "composite": true, /* Enable project compilation */ - // "removeComments": true, /* Do not emit comments to output. */ - // "noEmit": true, /* Do not emit outputs. */ - // "importHelpers": true /* Import emit helpers from 'tslib'. */, - // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ - // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ - - /* Strict Type-Checking Options */ - "strict": true /* Enable all strict type-checking options. */, - // "noImplicitAny": true /* Raise error on expressions and declarations with an implied 'any' type. */, - // "strictNullChecks": true /* Enable strict null checks. */, - // "strictFunctionTypes": true, /* Enable strict checking of function types. */ - // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ - "noImplicitThis": true /* Raise error on 'this' expressions with an implied 'any' type. */, - // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ - - /* Additional Checks */ - // "noUnusedLocals": true, /* Report errors on unused locals. */ - // "noUnusedParameters": true, /* Report errors on unused parameters. */ - "noImplicitReturns": true /* Report error when not all code paths in function return a value. */, - // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ - - /* Module Resolution Options */ - "moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */, - // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ - // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ - // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ - // "typeRoots": [], /* List of folders to include type definitions from. */ - // "types": [], /* Type declaration files to be included in compilation. */ - // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ - "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ - // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ - - /* Source Map Options */ - // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ - // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ - - /* Experimental Options */ - // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ - // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + "ES6", + "DOM", + ], + "declaration": true, + "strict": true, + "noImplicitThis": true, + "noImplicitReturns": true, + "moduleResolution": "node", + "esModuleInterop": true, + "baseUrl": "./", + "paths": { + "*": [ + "./typings/*" + ], + "error_message": [ + "./lib/message/error_message" + ], + "log_message": [ + "./lib/message/log_message" + ], + }, + "resolveJsonModule": true, + "allowJs": true, + "outDir": "./dist", + "sourceMap": true, + "skipLibCheck": true, + "useUnknownInCatchVariables": false }, - "exclude": ["node_modules", "src/**/*.spec.ts"] + "exclude": [ + "./dist", + "./lib/**/*.tests.js", + "./lib/**/*.tests.ts", + "./lib/**/*.spec.ts", + "./lib/**/*.umdtests.js", + "./lib/tests", + "node_modules" + ], + "include": [ + "./lib/**/*.ts", + "./lib/**/*.js", + "./lib/modules/**/*.ts", + "./lib/modules/**/**/*.ts" + ] } diff --git a/tsconfig.spec.json b/tsconfig.spec.json new file mode 100644 index 000000000..f61c713df --- /dev/null +++ b/tsconfig.spec.json @@ -0,0 +1,27 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "types": [ + "vitest/jsdom" + ], + "typeRoots": [ + "./node_modules/@types" + ], + "target": "ESNext" + }, + "exclude": [ + "./dist", + "./lib/**/*.tests.js", + "./lib/**/*.tests.ts", + "./lib/**/*.umdtests.js", + "node_modules" + ], + "include": [ + "./lib/**/*.ts", + "./lib/**/*.js", + "./lib/modules/**/*.ts", + "./lib/modules/**/**/*.ts", + "tests/**/*.ts", + "**/*.spec.ts" + ] +} diff --git a/packages/optimizely-sdk/typings/murmurhash.d.ts b/typings/murmurhash.d.ts similarity index 100% rename from packages/optimizely-sdk/typings/murmurhash.d.ts rename to typings/murmurhash.d.ts diff --git a/packages/datafile-manager/src/index.node.ts b/vitest.config.mts similarity index 51% rename from packages/datafile-manager/src/index.node.ts rename to vitest.config.mts index 7f75e1b25..1bce36eb0 100644 --- a/packages/datafile-manager/src/index.node.ts +++ b/vitest.config.mts @@ -1,5 +1,5 @@ /** - * Copyright 2019-2020, Optimizely + * Copyright 2024-2025, Optimizely * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,6 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import path from 'path'; +import { defineConfig } from 'vitest/config' -export * from './datafileManager'; -export { default as HttpPollingDatafileManager } from './nodeDatafileManager'; +export default defineConfig({ + resolve: { + alias: { + 'error_message': path.resolve(__dirname, './lib/message/error_message'), + 'log_message': path.resolve(__dirname, './lib/message/log_message'), + }, + }, + test: { + onConsoleLog: () => true, + environment: 'happy-dom', + include: ['**/*.spec.ts'], + typecheck: { + enabled: true, + tsconfig: 'tsconfig.spec.json', + }, + }, +});