diff --git a/react-native/.editorconfig b/react-native/.editorconfig new file mode 100644 index 00000000..65365be6 --- /dev/null +++ b/react-native/.editorconfig @@ -0,0 +1,15 @@ +# EditorConfig helps developers define and maintain consistent +# coding styles between different editors and IDEs +# editorconfig.org + +root = true + +[*] + +indent_style = space +indent_size = 2 + +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/react-native/.gitignore b/react-native/.gitignore new file mode 100644 index 00000000..43318057 --- /dev/null +++ b/react-native/.gitignore @@ -0,0 +1,105 @@ +# OSX +# +.DS_Store + +# VSCode +.vscode/settings.json + +# Xcode +# +build/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +ios/.xcode.env.local + +# Android/IntelliJ +# +build/ +.idea +.gradle +local.properties +*.iml +*.hprof +.cxx/ +*.keystore +!debug.keystore +upload-keystore.jks +.kotlin/ + +# node.js +# +node_modules/ +.yarn/ +npm-debug.log +yarn-error.log +pnpm-debug.log + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/ + +**/fastlane/report.xml +**/fastlane/Preview.html +**/fastlane/screenshots +**/fastlane/test_output + +# Bundle artifact +*.jsbundle + +# Ruby / CocoaPods +/**/Pods/ +/vendor/bundle/ + +# Temporary files created by Metro to check the health of the file watcher +.metro-health-check* + +# testing +/coverage + +# Env +.env +.env.local +.env.development.local +.env.test.local +.env.production.local +**/.xcode.env.local + +# Build +.build +lib +dist +.turbo + +# Module +modules/@shopify/checkout-sheet-kit/android/gradle/wrapper/gradle-wrapper.jar +modules/@shopify/checkout-sheet-kit/android/gradle/wrapper/gradle-wrapper.properties +modules/@shopify/checkout-sheet-kit/android/gradlew +modules/@shopify/checkout-sheet-kit/android/gradlew.bat + +# Sample bundle +**/index.android.bundle + +# Local gems +sample/vendor + +# Sample app +sample/**/AndroidManifest.xml + +.claude/ +CLAUDE.md diff --git a/react-native/.npmrc b/react-native/.npmrc new file mode 100644 index 00000000..43451e6d --- /dev/null +++ b/react-native/.npmrc @@ -0,0 +1,2 @@ +node-linker=hoisted +frozen-lockfile=true diff --git a/react-native/.ruby-version b/react-native/.ruby-version new file mode 100644 index 00000000..9c25013d --- /dev/null +++ b/react-native/.ruby-version @@ -0,0 +1 @@ +3.3.6 diff --git a/react-native/.swiftformat b/react-native/.swiftformat new file mode 100644 index 00000000..2c7479e3 --- /dev/null +++ b/react-native/.swiftformat @@ -0,0 +1,11 @@ +--indent 4 +--linebreaks lf +--wraparguments before-first +--wrapcollections before-first +--commas inline +--allman false +--semicolons inline +--trimwhitespace always +--disable redundantReturn,hoistAwait,preferKeyPath,redundantInternal,redundantPublic +--swiftversion 5.7.1 +--extensionacl on-declarations diff --git a/react-native/.swiftlint.yml b/react-native/.swiftlint.yml new file mode 100644 index 00000000..969e64a8 --- /dev/null +++ b/react-native/.swiftlint.yml @@ -0,0 +1,39 @@ +disabled_rules: + - line_length + - file_length + - non_optional_string_data_conversion + - type_body_length + - type_name + - identifier_name + - opening_brace + - function_body_length + - trailing_comma + - function_parameter_count + +opt_in_rules: + - array_init + - collection_alignment + - comma_inheritance + - convenience_type + - closure_spacing + - direct_return + - empty_count + - empty_string + - fatal_error_message + - first_where + - identical_operands + - indentation_width + - joined_default_parameter + - modifier_order + - overridden_super_call + - toggle_bool + - switch_case_alignment: + indented_cases: false + - fallthrough + - explicit_acl + +nesting: + type_level: + warning: 2 + +reporter: "xcode" diff --git a/react-native/.vscode/extensions.json b/react-native/.vscode/extensions.json new file mode 100644 index 00000000..137ee5de --- /dev/null +++ b/react-native/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "esbenp.prettier-vscode", + "editorconfig.editorconfig", + "msjsdiag.vscode-react-native", + "yzhang.markdown-all-in-one" + ] +} diff --git a/react-native/.watchmanconfig b/react-native/.watchmanconfig new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/react-native/.watchmanconfig @@ -0,0 +1 @@ +{} diff --git a/react-native/CONTRIBUTING.md b/react-native/CONTRIBUTING.md new file mode 100644 index 00000000..6f328987 --- /dev/null +++ b/react-native/CONTRIBUTING.md @@ -0,0 +1,170 @@ +# Contributing + +We welcome code contributions, feature requests, and reporting of issues. Please +see [guidelines and instructions](.github/CONTRIBUTING.md). + +--- + +This repo is subdivided into 3 parts using pnpm workspaces: + +- The base repo (workspace name = `checkout-sheet-kit-react-native`) +- The `@shopify/checkout-sheet-kit` Native Module (workspace name = `module`) +- The sample application (workspace name = `sample`) + +Each of the workspaces contains a separate `package.json` to manage tasks +specific to each workspace. + +## Getting started + +If you've cloned the repo and want to run the sample app, you will first need to: + +1. Install the NPM dependencies + + ```sh + pnpm install + ``` + +2. Install iOS dependencies. (N.b. Android dependencies are automatically installed by Gradle) + + ```sh + pnpm pod-install sample/ios + ``` + +3. Build the Native Module + + ```sh + pnpm module build + ``` + +4. Start the Metro server + + ```sh + pnpm sample start + ``` + +5. Run the sample application (in a new terminal / tab) + + ```sh + pnpm sample ios + # or + pnpm sample android + ``` + +## Optional: Speed up builds with sccache + +For faster native compilation (especially on incremental builds), you can install [sccache](https://github.com/mozilla/sccache), a shared compilation cache: + +```sh +# macOS (using Homebrew) +brew install sccache + +# Ubuntu/Debian +cargo install sccache + +# Other systems: see https://github.com/mozilla/sccache#installation +``` + +The build scripts will automatically detect and use sccache if available. On Android, React Native's CMake files look for a command named `ccache`, so the sample Android scripts put an sccache-backed compatibility command first on `PATH`. If you encounter any build issues, you can temporarily disable it: + +```sh +# Disable sccache for a single build +SCCACHE=false pnpm sample ios +SCCACHE=false pnpm sample android +``` + +## Making changes to the Native Module + +If your intentions are to modify the TS code for the Native Module under +`modules/@shopify/checkout-sheet-kit`, note that you will not need to rebuild to +observe your changes in the sample app. This is because the sample app is +importing the TS files directly from the module directory (through symlinking). + +However, if you're running the iOS/Android tests against the module, you will +first need to run `pnpm module build` each time you change the TS code. + +## Cleaning the workspaces + +There are a handful of commands to clean the individual workspaces. + +```sh +# Clear the current directory from watchman +pnpm clean + +# Removes the "sample/node_modules" directory +# Removes "ios/pods" directory +# Removes "ios/build" directory +pnpm sample clean + +# Removes the "lib" directory for the Native Module +pnpm module clean +``` + +## Linting the code + +Linting the codespaces will (1) compile the code with TypeScript and (2) run +eslint over the source code. + +```sh +# Lint the Native Module TS code +pnpm module lint + +# Lint the Sample App TS code +pnpm sample lint +``` + +## Testing + +There are 3 types of tests in this repo: Typescript, Swift and Java - each for +testing the Native Module. + +```sh +# Run Jest tests for "modules/@shopify/checkout-sheet-kit/src/**/*.tsx" +pnpm test + +# Run swift tests for the Native Module +pnpm sample test:ios + +# Run Java tests for the Native Module +pnpm sample test:android +``` + +## Running the sample app + +To run the sample app in this repo, first clone the repo and run the following +commands at the root of the project directory. + +### Install NPM dependencies + +```sh +pnpm install +``` + +### Install Cocoapods + +```sh +pnpm pod-install sample/ios +``` + +### Build the local module + +```sh +pnpm module build +``` + +### Update the dotenv file + +Replace the details in the `sample/.env.example` file and rename it to +`sample/.env` + +``` +# Storefront Details +STOREFRONT_DOMAIN="YOUR_STORE.myshopify.com" +STOREFRONT_ACCESS_TOKEN="YOUR_PUBLIC_STOREFRONT_ACCESS_TOKEN" +STOREFRONT_VERSION="2025-07" +``` + +### Start the sample app + +```sh +pnpm sample start +``` diff --git a/react-native/LICENSE b/react-native/LICENSE new file mode 100644 index 00000000..d42681cf --- /dev/null +++ b/react-native/LICENSE @@ -0,0 +1,20 @@ +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/react-native/README.md b/react-native/README.md index 3305c6bf..b09bc76b 100644 --- a/react-native/README.md +++ b/react-native/README.md @@ -1,3 +1,1025 @@ -# Checkout Kit — React Native +# Shopify Checkout Kit - React Native -Placeholder. The React Native wrapper will be folded into this directory in a follow-up. Until then, see the existing repo at `Shopify/checkout-sheet-kit-react-native`. +[![GitHub license](https://img.shields.io/badge/license-MIT-lightgrey.svg?style=flat)](https://github.com/Shopify/checkout-sheet-kit-react-native/blob/main/LICENSE) +[![GitHub Release](https://img.shields.io/github/release/shopify/checkout-sheet-kit-react-native.svg?style=flat)]() + +gradients + +**Shopify Checkout Kit** is a Native Module that enables React Native apps +to provide the world’s highest converting, customizable, one-page checkout +within the app. The presented experience is a fully-featured checkout that +preserves all of the store customizations: Checkout UI extensions, Functions, +branding, and more. It also provides platform idiomatic defaults such as support +for light and dark mode, and convenient developer APIs to embed, customize, and +follow the lifecycle of the checkout experience. + +Note: We're in the process of renaming "Checkout Sheet Kit" to "Checkout Kit." The dev docs and README already use the new name, while the package itself will be updated in an upcoming version. + +Check out our blog to +[learn how and why we built the Shopify Checkout Kit](https://www.shopify.com/partners/blog/mobile-checkout-sdks-for-ios-and-android). + +The React Native SDK is part of +[Shopify's Mobile Kit](https://shopify.dev/docs/custom-storefronts/mobile-kit) +which enables developers to delivery best-in-class iOS and Android commerce +experiences. + +- [Platform Requirements](#platform-requirements) +- [Getting Started](#getting-started) + - [1. Installation](#1-installation) + - [2. Minimum Android requirements](#2-minimum-android-requirements) + - [3. Minimum iOS requirements](#3-minimum-ios-requirements) +- [Basic Usage](#basic-usage) +- [Programmatic Usage](#programmatic-usage) +- [Usage with the Shopify Storefront API](#usage-with-the-shopify-storefront-api) +- [Configuration](#configuration) + - [Colors](#colors) + - [Localization](#localization) + - [Checkout Sheet title](#checkout-sheet-title) + - [iOS - Localization](#ios---localization) + - [Android - Localization](#android---localization) + - [Currency](#currency) + - [Language](#language) +- [Preloading](#preloading) + - [Important considerations](#important-considerations) + - [Flash Sales](#flash-sales) + - [When to preload](#when-to-preload) + - [Cache invalidation](#cache-invalidation) +- [Checkout lifecycle](#checkout-lifecycle) + - [`addEventListener(eventName, callback)`](#addeventlistenereventname-callback) + - [`removeEventListeners(eventName)`](#removeeventlistenerseventname) +- [Behavioral data - Web pixels](#behavioral-data---web-pixels) +- [Identity \& customer accounts](#identity--customer-accounts) + - [Cart: buyer bag, identity, and preferences](#cart-buyer-bag-identity-and-preferences) + - [Multipass](#multipass) + - [Shop Pay](#shop-pay) + - [Customer Account API](#customer-account-api) +- [Offsite Payments](#offsite-payments) + - [Universal Links - iOS](#universal-links---ios) +- [Pickup points / Pickup in store](#pickup-points--pickup-in-store) + - [Geolocation - iOS](#geolocation---ios) + - [Geolocation - Android](#geolocation---android) + - [Opting out of the default behavior](#opting-out-of-the-default-behavior) +- [Contributing](#contributing) +- [License](#license) + +## Platform Requirements + +- **React Native** - Minimum version `0.70` +- **iOS** - Minimum version iOS 13 +- **Android** - Minimum Java 11 & Android SDK version `23` + +## Getting Started + +Shopify Checkout Kit is an open-source NPM package. + +Use the following steps to get started with adding it to your React Native +application: + +### 1. Installation + +Install the Shopify Checkout Kit package dependency: + +```sh +pnpm add @shopify/checkout-sheet-kit + +# or using yarn +yarn add @shopify/checkout-sheet-kit + +# or using npm +npm install @shopify/checkout-sheet-kit +``` + +### 2. Minimum Android requirements + +Check the `minSdkVersion` property in your `android/build.gradle` file is at +least `23`. + +```diff +// android/build.gradle +buildscript { + ext { + buildToolsVersion = "33.0.0" +- minSdkVersion = 21 ++ minSdkVersion = 23 + compileSdkVersion = 33 + targetSdkVersion = 33 + } + // ... +} +``` + +### 3. Minimum iOS requirements + +Check the `platform :ios` property of your `ios/Podfile` to ensure that the +minimum version number is at least `13`. + +```diff +# ios/Podfile +- platform :ios, min_ios_version_supported ++ platform :ios, 13 +``` + +## Basic Usage + +Once the SDK has been added as a package dependency and the minimum platform +requirements have been checked, you can begin by importing the library in your +application code: + +```tsx +import {ShopifyCheckoutSheetProvider} from '@shopify/checkout-sheet-kit'; + +function AppWithContext() { + return ( + + + + ); +} +``` + +Doing so will now allow you to access the Native Module anywhere in your +application using React hooks: + +```tsx +import {useShopifyCheckoutSheet} from '@shopify/checkout-sheet-kit'; + +function App() { + const shopifyCheckout = useShopifyCheckoutSheet(); + + // Present the checkout + shopifyCheckout.present(checkoutUrl); +} +``` + +See [usage with the Storefront API](#usage-with-the-storefront-api) below for details on how +to obtain a checkout URL to pass to the kit. + +> [!NOTE] +> The recommended usage of the library is through a +> `ShopifyCheckoutSheetProvider` Context provider, but see +> [Programmatic usage](#programamatic-usage) below for details on how to use the +> library without React context. + +## Programmatic Usage + +To use the library without React context, import the `ShopifyCheckoutSheet` +class from the package and instantiate it. We recommend to instantiating the +class at a relatively high level in your application, and exporting it for use +throughout your app. + +```tsx +// shopify.ts +import {ShopifyCheckoutSheet} from '@shopify/checkout-sheet-kit'; + +export const shopifyCheckout = new ShopifyCheckoutSheet({ + // optional configuration +}); +``` + +Similar to the context approach, you can consume the instance as you would using +hooks. + +```tsx +import {shopifyCheckout} from './shopify.ts'; + +shopifyCheckout.present(checkoutUrl); +``` + +## Usage with the Shopify Storefront API + +To present a checkout to the buyer, your application must first obtain a +checkout URL. The most common way is to use the +[Storefront GraphQL API](https://shopify.dev/docs/api/storefront), to create a +cart, add line items, and retrieve a +[checkoutUrl](https://shopify.dev/docs/api/storefront/2023-10/objects/Cart#field-cart-checkouturl) +value. Alternatively, a [cart permalink](https://help.shopify.com/en/manual/products/details/cart-permalink) can be provided. + +You can use any GraphQL client to accomplish this - but as an example, our +[sample app](./sample) uses Apollo. + +Here's an example of how to get started with Apollo: + +```tsx +import {ApolloClient, gql, ApolloProvider} from '@apollo/client'; +import {STOREFRONT_NAME, STOREFRONT_ACCESS_TOKEN} from '@env'; + +// Create a new instance of the ApolloClient +const client = new ApolloClient({ + uri: `https://${STOREFRONT_NAME}.myshopify.com/api/2025-07/graphql.json`, + headers: { + 'X-Shopify-Storefront-Access-Token': STOREFRONT_ACCESS_TOKEN, + }, +}); + +// Create Cart Mutation +const createCartMutation = gql` + mutation CreateCart { + cartCreate { + cart { + id + checkoutUrl + } + } + } +`; + +// Add to Cart Mutation +const addToCartMutation = gql` + mutation AddToCart($cartId: ID!, $lines: [CartLineInput!]!) { + cartLinesAdd(cartId: $cartId, lines: $lines) { + cart { + id + checkoutUrl + } + } + } +`; + +function YourReactNativeApp() { + return ( + + + + ); +} +``` + +The `checkoutUrl` object is a standard web checkout URL that can be opened in +any browser. To present a native checkout sheet in your application, provide the +`checkoutUrl` alongside optional runtime configuration settings to the +`present(checkoutUrl)` function provided by the SDK: + +```tsx +function App() { + const [createCart] = useMutation(createCartMutation) + const [addToCart] = useMutation(addToCartMutation) + + return ( + // React native app code + ) +} +``` + +The `checkoutUrl` value is a standard web checkout URL that can be opened in any +browser. To present a native checkout sheet in your application, provide the +`checkoutUrl` to the `present(checkoutUrl)` function provided by the SDK: + +```tsx +function App() { + const shopifyCheckout = useShopifyCheckoutSheet() + const checkoutUrl = useRef(null) + const [createCart] = useMutation(createCartMutation) + const [addToCart] = useMutation(addToCartMutation) + + const handleAddToCart = useCallback((merchandiseId) => { + // Create a cart + const {data: cartCreateResponse} = await createCart() + // Add an item to the cart + const {data: addToCartResponse} = await addToCart({ + variables: { + cartId: cartCreateResponse.cartCreate.cart.id, + lines: [{quantity: 1, merchandiseId}] + } + }) + // Retrieve checkoutUrl from the Storefront response + checkoutUrl.current = addToCartResponse.cartLinesAdd.cart.checkoutUrl + + // Preload the checkout in the background for faster presentation + shopifyCheckout.preload(checkoutUrl.current) + }, []); + + const handleCheckout = useCallback(() => { + if (checkoutURL.current) { + // Present the checkout to the buyer + shopifyCheckout.present(checkoutURL.current) + } + }, []) + + return ( + + + + + ) +} +``` + +> [!TIP] +> To help optimize and deliver the best experience the SDK also provides +> a [preloading API](#preloading) that can be used to initialize the checkout +> session in the background and ahead of time. + +## Configuration + +The SDK provides a way to customize the presented checkout experience through a +`configuration` object in the Context Provider or a `setConfig` method on an +instance of the `ShopifyCheckoutSheet` class. + +| Name | Required | Default | Description | +| ------------- | -------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `colorScheme` | | `automatic` | Sets the color scheme for the checkout. | +| `preloading` | | `true` | Enable/disable [preloading](#preloading). | +| `colors` | | `{}` | An object with `ios` and `android` properties to override the colors for iOS and Android platforms individually. See [`colors`](#colors) for more information. | +| `logLevel` | | `error` | Sets the log level for the native SDK. Use `LogLevel.debug` for verbose logging during development, or `LogLevel.error` for production. | + +Here's an example of how a fully customized configuration object might look: + +```tsx +import { + ColorScheme, + Configuration, + LogLevel, + ShopifyCheckoutSheetProvider, +} from '@shopify/checkout-sheet-kit'; + +const config: Configuration = { + colorScheme: ColorScheme.web, + preloading: true, + logLevel: LogLevel.error, + colors: { + ios: { + backgroundColor: '#f0f0e8', + tintColor: '#2d2a38', + }, + android: { + backgroundColor: '#f0f0e8', + progressIndicator: '#2d2a38', + headerBackgroundColor: '#f0f0e8', + headerTextColor: '#2d2a38', + }, + }, +}; + +// If using React Context +function AppWithContext() { + return ( + + + + ); +} + +// If using ShopifyCheckoutSheet directly +const shopifyCheckout = new ShopifyCheckoutSheet(config); +``` + +### Colors + +The SDK defaults to the `automatic` color scheme option, will switches between +idiomatic `light` and `dark` themes depending on the users preference. This +behavior can be customized via the `colorScheme` property: + +| Name | Default | Description | +| ----------- | ------- | ------------------------------------------------------------------------------------------------ | +| `automatic` | ✔ | Alternates between an idiomatic light and dark theme - depending on the users device preference. | +| `light` | | Force the idomatic light theme. | +| `dark` | | Force the idomatic dark theme. | +| `web` | | Force your storefront web theme, as rendered by a mobile browser. | + +The `colors` configuration property can be used to provide overrides for iOS and +Android applications separately. + +```tsx +const config: Configuration = { + colorScheme: ColorScheme.light, + colors: { + ios: { + backgroundColor: '#ffffff', + tintColor: '#000000', + closeButtonColor: '#333333', + }, + android: { + backgroundColor: '#ffffff', + progressIndicator: '#2d2a38', + headerBackgroundColor: '#ffffff', + headerTextColor: '#000000', + closeButtonColor: '#333333', + }, + }, +}; +``` + +Note that when using the `automatic` option, the `colors.android` interface is +slightly different, as you can specify different overrides for `light` and +`dark` modes: + +```tsx +import { + ColorScheme, + Configuration, + ShopifyCheckoutSheetProvider, +} from '@shopify/checkout-sheet-kit'; + +const config: Configuration = { + colorScheme: ColorScheme.automatic, + colors: { + // Custom light/dark overrides for Android + android: { + light: { + backgroundColor: '#ffffff', + progressIndicator: '#2d2a38', + headerBackgroundColor: '#ffffff', + headerTextColor: '#000000', + closeButtonColor: '#000000', + }, + dark: { + backgroundColor: '#000000', + progressIndicator: '#0087ff', + headerBackgroundColor: '#000000', + headerTextColor: '#ffffff', + closeButtonColor: '#ffffff', + }, + }, + }, +}; + +function AppWithContext() { + return ( + + + + ); +} +``` + +### Localization + +#### Checkout Sheet title + +##### iOS - Localization + +On iOS, you can set a localized value on the `title` attribute of the +configuration. + +Alternatively, use a Localizable.xcstrings file in your app by doing the +following: + +1. Create a `Localizable.xcstrings` file under "ios/{YourApplicationName}" +2. Add an entry for the key `"shopify_checkout_sheet_title"` + +##### Android - Localization + +On Android, you can add a string entry for the key `"checkout_web_view_title"` +to the "android/app/src/res/values/strings.xml" file for your application. + +```diff + + Your App Name ++ Checkout + +``` + +> [!IMPORTANT] +> The `title` configuration attribute will only affect iOS. For Android you **must** use +> `res/values/strings.xml`. + +#### Currency + +To set an appropriate currency for a given cart, the Storefront API offers an +`@inContext(country)` directive which will ensure the correct currency is +presented. + +```tsx +const CREATE_CART_MUTATION = gql` + mutation CreateCart($input: CartInput, $country: CountryCode = CA) + @inContext(country: $country) { + cartCreate(input: $input) { + cart { + id + checkoutUrl + } + } + } +`; +``` + +See [Storefront Directives](https://shopify.dev/docs/api/storefront#directives) +for more information. + +#### Language + +Similarly to currency, you can use an `@inContext(language)` directive to set +the language for your checkout. + +```tsx +const CREATE_CART_MUTATION = gql` + mutation CreateCart($input: CartInput, $language: Language = EN) + @inContext(language: $language) { + cartCreate(input: $input) { + cart { + id + checkoutUrl + } + } + } +`; +``` + +See [Storefront Directives](https://shopify.dev/docs/api/storefront#directives) +for more information. + +## Preloading + +Initializing a checkout session requires communicating with Shopify servers, +thus depending on the network quality and bandwidth available to the buyer can +result in undesirable waiting time for the buyer. To help optimize and deliver +the best experience, the SDK provides a `preloading` "hint" that allows +developers to signal that the checkout session should be initialized in the +background, ahead of time. + +Preloading is an advanced feature that can be disabled by setting the +`preloading` configuration value to `false`. It is enabled by default. + +Once enabled, preloading a checkout is as simple as calling +`preload(checkoutUrl)` with a valid `checkoutUrl`. + +```tsx +// using hooks +const shopifyCheckout = useShopifyCheckoutSheet(); +ShopifyCheckout.preload(checkoutUrl); + +// using a class instance +const shopifyCheckout = new ShopifyCheckoutSheet(); +shopifyCheckout.preload(checkoutUrl); +``` + +### Important considerations + +1. Initiating preload results in background network requests and additional + CPU/memory utilization for the client, and should be used when there is a + high likelihood that the buyer will soon request to checkout—e.g. when the + buyer navigates to the cart overview or a similar app-specific experience. +2. A preloaded checkout session reflects the cart contents at the time when + `preload` is called. If the cart is updated after `preload` is called, the + application needs to call `preload` again to reflect the updated checkout + session. +3. Calling `preload(checkoutUrl)` is a hint, **not a guarantee**: the library + may debounce or ignore calls to this API depending on various conditions; the + preload may not complete before `present(checkoutUrl)` is called, in which + case the buyer may still see a spinner while the checkout session is + finalized. + +### Flash Sales + +It is important to note that during Flash Sales or periods of high amounts of traffic, buyers may be entered into a queue system. + +**Calls to preload which result in a buyer being enqueued will be rejected.** This means that a buyer will never enter the queue without their knowledge. + +### When to preload + +Calling `preload()` each time an item is added to a buyer's cart can put significant strain on Shopify systems, which in return can result in rejected requests. Rejected requests will not result in a visual error shown to users, but will degrade the experience since they will need to load checkout from scratch. + +Instead, a better approach is to call `preload()` when you have a strong enough signal that the buyer intends to check out. In some cases this might mean a buyer has navigated to a "cart" screen. + +### Cache invalidation + +Should you wish to manually clear the preload cache, there is a `ShopifyCheckoutSheetKit.invalidate()` helper function to do so. + +## Checkout lifecycle + +There are currently 3 checkout events exposed through the Native Module. You can +subscribe to these events using `addEventListener` and `removeEventListeners` +methods - available on both the context provider as well as the class instance. + +| Name | Callback | Description | +| ----------- | ----------------------------------------- | ------------------------------------------------------------ | +| `close` | `() => void` | Fired when the checkout has been closed. | +| `completed` | `(event: CheckoutCompletedEvent) => void` | Fired when the checkout has been successfully completed. | +| `error` | `(error: {message: string}) => void` | Fired when a checkout exception has been raised. | +| `pixel` | `(event: PixelEvent) => void` | Fired when a Web Pixel event has been relayed from checkout. | + +### `addEventListener(eventName, callback)` + +Subscribing to an event returns an `EmitterSubscription` object, which contains +a `remove()` function to unsubscribe. Here's an example of how you might create +an event listener in a React `useEffect`, ensuring to remove it on unmount. + +```tsx +// Using hooks +const shopifyCheckout = useShopifyCheckoutSheet(); + +useEffect(() => { + const close = shopifyCheckout.addEventListener('close', () => { + // Do something on checkout close + }); + + const completed = shopifyCheckout.addEventListener( + 'completed', + (event: CheckoutCompletedEvent) => { + // Lookup order on checkout completion + const orderId = event.orderDetails.id; + }, + ); + + const error = shopifyCheckout.addEventListener( + 'error', + (error: CheckoutError) => { + // Do something on checkout error + // console.log(error.message) + }, + ); + + const pixel = shopifyCheckout.addEventListener( + 'pixel', + (event: PixelEvent) => { + // Dispatch web pixel events to third-party services + if (hasPermissionToTrack) { + sendEventToAnalyticsProvider(event); + } + }, + ); + + return () => { + // It is important to clear the subscription on unmount to prevent memory leaks + close?.remove(); + completed?.remove(); + error?.remove(); + pixel?.remove(); + }; +}, [shopifyCheckout]); +``` + +### `removeEventListeners(eventName)` + +On the rare occasion that you want to remove all event listeners for a given +`eventName`, you can use the `removeEventListeners(eventName)` method. + +## Behavioral data - Web pixels + +App developers can use +[lifecycle events](#checkout-lifecycle) to monitor +and log the status of a checkout session. + +For behavioural monitoring, +["standard"](https://shopify.dev/docs/api/web-pixels-api/standard-events) and +["custom"](https://shopify.dev/docs/api/web-pixels-api#custom-web-pixels) Web +Pixel events will be relayed back to your application through the `"pixel"` +event listener. App developers should only subscribe to pixel events if they have proper levels of consent from merchants/buyers and are responsible for adherence to Apple's privacy policy and local regulations like GDPR and +ePrivacy directive before disseminating these events to first-party and +third-party systems. + +> [!NOTE] +> You will likely need to augment these events with customer/session information derived from app state. + +> [!NOTE] +> The `customData` attribute of CustomPixelEvent can take on any shape. As such, this attribute will be returned as a String. Client applications should define a custom data type and deserialize the customData string into that type. + +## Identity & customer accounts + +Buyer-aware checkout experience reduces friction and increases conversion. +Depending on the context of the buyer (guest or signed-in), knowledge of buyer +preferences, or account/identity system, the application can use one of the +following methods to initialize a personalized and contextualized buyer +experience. + +### Cart: buyer bag, identity, and preferences + +In addition to specifying the line items, the Cart can include buyer identity +(name, email, address, etc.), and delivery and payment preferences: see +[guide](https://shopify.dev/docs/custom-storefronts/building-with-the-storefront-api/cart/manage). +Included information will be used to present pre-filled and pre-selected choices +to the buyer within checkout. + +#### Multipass + +[Shopify Plus](https://help.shopify.com/en/manual/intro-to-shopify/pricing-plans/plans-features/shopify-plus-plan) +merchants using +[Classic Customer Accounts](https://help.shopify.com/en/manual/customers/customer-accounts/classic-customer-accounts) +can use [Multipass](https://shopify.dev/docs/api/multipass) +([API documentation](https://shopify.dev/docs/api/multipass)) to integrate an +external identity system and initialize a buyer-aware checkout session. + +```json +{ + "email": "", + "created_at": "", + "remote_ip": "", + "return_to": "" +} +``` + +1. Follow the [Multipass documentation](https://shopify.dev/docs/api/multipass) + to create a Multipass URL and set `return_to` to be the obtained + `checkoutUrl` +2. Provide the Multipass URL to `present(checkoutUrl)` + +> [!IMPORTANT] +> The above JSON omits useful customer attributes that should be +> provided where possible and encryption and signing should be done server-side +> to ensure Multipass keys are kept secret. + +#### Shop Pay + +To initialize accelerated Shop Pay checkout, the cart can set a +[walletPreference](https://shopify.dev/docs/api/storefront/latest/mutations/cartBuyerIdentityUpdate#field-cartbuyeridentityinput-walletpreferences) +to 'shop_pay'. The sign-in state of the buyer is app-local. The buyer will be +prompted to sign in to their Shop account on their first checkout, and their +sign-in state will be remembered for future checkout sessions. + +#### Customer Account API + +We are working on a library to provide buyer sign-in and authentication powered +by the +[new Customer Account API](https://www.shopify.com/partners/blog/introducing-customer-account-api-for-headless-stores)—stay +tuned. + +## Offsite Payments + +Certain payment providers finalize transactions by redirecting customers to +external banking apps. To enhance the user experience for your buyers, you can +set up your storefront to support Universal Links on iOS and App links on +Android, allowing customers to be redirected back to your app once the payment +is completed. + +### Universal Links - iOS + +See the +[Universal Links guide](https://github.com/Shopify/checkout-sheet-kit-react-native/blob/main/documentation/universal_links_ios.md) +for information on how to get started with adding support for Offsite Payments +in your app. + +It is crucial for your app to be configured to handle URL clicks during the +checkout process effectively. By default, the kit includes the following +delegate method to manage these interactions. This code ensures that external +links, such as HTTPS and deep-links, are opened correctly by iOS. + +```swift +public func checkoutDidClickLink(url: URL) { + if UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url) + } +} +``` + +## Pickup points / Pickup in store + +### Geolocation - iOS + +Geolocation permission requests are handled out of the box by iOS, provided you've added the required location usage description to your `Info.plist` file: + +```xml +NSLocationWhenInUseUsageDescription +Your location is required to locate pickup points near you. +``` + +> [!TIP] +> Consider also adding `NSLocationAlwaysAndWhenInUseUsageDescription` if your app needs background location access for other features. + +### Geolocation - Android + +Android differs to iOS in that permission requests must be handled in two places: +(1) in your `AndroidManifest.xml` and (2) at runtime. + +```xml + + +``` + +The Checkout Kit native module will emit a `geolocationRequest` event when the webview requests geolocation +information. By default, the kit will listen for this event and request access to both coarse and fine access when +invoked. + +The geolocation request flow follows this sequence: + +1. When checkout needs location data (e.g., to show nearby pickup points), it triggers a geolocation request. +2. The native module emits a `geolocationRequest` event. +3. If using default behavior, the module automatically handles the Android runtime permission request. +4. The result is passed back to checkout, which then proceeds to show relevant pickup points if permission was granted. + +> [!NOTE] +> If the user denies location permissions, the checkout will still function but will not be able to show nearby pickup points. Users can manually enter their location instead. + +#### Opting out of the default behavior + +> [!NOTE] +> This section is only applicable for Android. + +In order to opt-out of the default permission handling, you can set `features.handleGeolocationRequests` to `false` +when you instantiate the `ShopifyCheckoutSheet` class. + +If you're using the sheet programmatically, you can do so by specifying a `features` object as the second argument: + +```tsx +const checkoutSheetKit = new ShopifyCheckoutSheet(config, {handleGeolocationRequests: false}); +``` + +If you're using the context provider, you can pass the same `features` object as a prop to the `ShopifyCheckoutSheetProvider` component: + +```tsx + + {children} + +``` + +When opting out, you'll need to implement your own permission handling logic and communicate the result back to the checkout sheet. This can be useful if you want to: + +- Customize the permission request UI/UX +- Coordinate location permissions with other app features +- Implement custom fallback behavior when permissions are denied + +The steps here to implement your own logic are to: + +1. Listen for the `geolocationRequest` +2. Request the desired permissions +3. Invoke the native callback by calling `initiateGeolocationRequest` with the permission status + +```tsx +// Listen for "geolocationRequest" events +shopify.addEventListener('geolocationRequest', async (event: GeolocationRequestEvent) => { + const coarse = 'android.permission.ACCESS_COARSE_LOCATION'; + const fine = 'android.permission.ACCESS_FINE_LOCATION'; + + // Request one or many permissions at once + const results = await PermissionsAndroid.requestMultiple([coarse, fine]); + + // Check the permission status results + const permissionGranted = results[coarse] === 'granted' || results[fine] === 'granted'; + + // Dispatch an event to the native module to invoke the native callback with the permission status + shopify.initiateGeolocationRequest(permissionGranted); +}) +``` + +--- + +## Accelerated Checkouts + +Accelerated checkout buttons surface Apple Pay and Shop Pay options earlier in the buyer journey so more orders complete without leaving your app. + +### Prerequisites + +- iOS 16 or later +- The `write_cart_wallet_payments` access scope ([request access](https://www.appsheet.com/start/1ff317b6-2da1-4f39-b041-c01cfada6098)) +- Apple Pay payment processing certificates ([setup guide](https://shopify.dev/docs/storefronts/mobile/create-apple-payment-processing-certificates)) +- A device configured for Apple Pay ([Apple setup instructions](https://developer.apple.com/documentation/passkit/setting-up-apple-pay)) + +### Configure the integration + +Pass an `acceleratedCheckouts` configuration when setting up the provider or `ShopifyCheckoutSheet` instance. This connects the accelerated checkout buttons to your storefront. + +```tsx +import {ShopifyCheckoutSheetProvider} from '@shopify/checkout-sheet-kit'; + +const config = { + acceleratedCheckouts: { + storefrontDomain: 'your-shop.myshopify.com', + storefrontAccessToken: 'your-storefront-access-token', + customer: { + // For authenticated customers + accessToken: 'customer-access-token', + // OR for guest customers + // email: 'customer@example.com', + // phoneNumber: '0123456789', + }, + wallets: { + applePay: { + merchantIdentifier: 'merchant.com.yourcompany', + contactFields: ['email', 'phone'], + // Optionally restrict shipping countries (ISO 3166-1 alpha-2) + // supportedShippingCountries: ['US', 'CA'], + }, + }, + }, +}; + +function App() { + return ( + + + + ); +} +``` + +> [!WARNING] +> Do not provide both `accessToken` and `email`/`phoneNumber` together. For authenticated customers, email and phone are fetched automatically from the Shopify account. + +### Render accelerated checkout buttons + +Use `AcceleratedCheckoutButtons` to attach accelerated checkout calls-to-action to product or cart surfaces once you have a valid cart ID or product variant ID from the Storefront API. + +```tsx +import { + AcceleratedCheckoutButtons, + AcceleratedCheckoutWallet, +} from '@shopify/checkout-sheet-kit'; + +function CartFooter({cartId}: {cartId: string}) { + return ( + + ); +} +``` + +You can also render buttons for a single product variant: + +```tsx + +``` + +#### Customize wallet options + +Accelerated checkout buttons display every available wallet by default. Use `wallets` to show a subset or adjust the order. + +```tsx +// Display only Shop Pay + + +// Display Shop Pay first, then Apple Pay + +``` + +#### Modify the Apple Pay button label + +Use `applePayLabel` to map to the native `PayWithApplePayButtonLabel` values. The default is `plain`. + +```tsx +import {ApplePayLabel} from '@shopify/checkout-sheet-kit'; + + +``` + +#### Customize the Apple Pay button style + +Use `applePayStyle` to set the color style of the Apple Pay button. The default is `automatic`, which adapts to the current appearance (light/dark mode). + +```tsx +import {ApplePayStyle} from '@shopify/checkout-sheet-kit'; + + +``` + +Available styles: `automatic`, `black`, `white`, `whiteOutline`. + +#### Customize button corners + +The `cornerRadius` prop lets you match the buttons to other calls-to-action in your app. Buttons default to an 8pt radius. + +```tsx +// Pill-shaped buttons + + +// Square buttons + +``` + +### Handle loading, errors, and lifecycle events + +Attach lifecycle handlers to respond when buyers finish, cancel, or encounter an error. + +```tsx + { + // Clear cart after successful checkout + clearCart(); + }} + onFail={(error) => { + console.error('Accelerated checkout failed:', error); + }} + onCancel={() => { + analytics.track('accelerated_checkout_cancelled'); + }} + onRenderStateChange={(event) => { + // event.state: 'loading' | 'rendered' | 'error' + setRenderState(event.state); + }} + onWebPixelEvent={(event) => { + analytics.track(event); + }} + onClickLink={(url) => { + Linking.openURL(url); + }} +/> +``` + +--- + +## Contributing + +See the [contributing documentation](CONTRIBUTING.md) for details on how to get started. + +## License + +Shopify's Checkout Kit is provided under an [MIT License](LICENSE). diff --git a/react-native/__mocks__/react-native-config.ts b/react-native/__mocks__/react-native-config.ts new file mode 100644 index 00000000..31dd0e60 --- /dev/null +++ b/react-native/__mocks__/react-native-config.ts @@ -0,0 +1,7 @@ +module.exports = { + __esModule: true, + default: { + CUSTOMER_ACCOUNT_API_SHOP_ID: 'test-shop-123', + CUSTOMER_ACCOUNT_API_CLIENT_ID: 'test-client-456', + }, +}; diff --git a/react-native/__mocks__/react-native-encrypted-storage.ts b/react-native/__mocks__/react-native-encrypted-storage.ts new file mode 100644 index 00000000..b8162965 --- /dev/null +++ b/react-native/__mocks__/react-native-encrypted-storage.ts @@ -0,0 +1,21 @@ +const store: Record = {}; + +const EncryptedStorage = { + setItem: jest.fn(async (key: string, value: string) => { + store[key] = value; + }), + getItem: jest.fn(async (key: string) => { + return store[key] ?? null; + }), + removeItem: jest.fn(async (key: string) => { + delete store[key]; + }), + clear: jest.fn(async () => { + Object.keys(store).forEach(key => delete store[key]); + }), +}; + +module.exports = { + __esModule: true, + default: EncryptedStorage, +}; diff --git a/react-native/__mocks__/react-native-quick-crypto.ts b/react-native/__mocks__/react-native-quick-crypto.ts new file mode 100644 index 00000000..6e0cb9e7 --- /dev/null +++ b/react-native/__mocks__/react-native-quick-crypto.ts @@ -0,0 +1,40 @@ +let callCount = 0; + +function randomBytes(size: number) { + callCount++; + const buffer = new ArrayBuffer(size); + const view = new Uint8Array(buffer); + for (let i = 0; i < size; i++) { + view[i] = (i + callCount) % 256; + } + return {buffer}; +} + +function createHash(_algorithm: string) { + return { + update(_data: string) { + return { + digest() { + const buffer = new ArrayBuffer(32); + const view = new Uint8Array(buffer); + for (let i = 0; i < 32; i++) { + view[i] = (i * 7) % 256; + } + return {buffer}; + }, + }; + }, + }; +} + +function resetCallCount() { + callCount = 0; +} + +module.exports = { + __esModule: true, + default: {randomBytes, createHash}, + randomBytes, + createHash, + resetCallCount, +}; diff --git a/react-native/__mocks__/react-native.ts b/react-native/__mocks__/react-native.ts new file mode 100644 index 00000000..69210799 --- /dev/null +++ b/react-native/__mocks__/react-native.ts @@ -0,0 +1,92 @@ +type Listener = {eventName: string; callback: (data: any) => void}; + +function createMockEmitter() { + let listeners: Listener[] = []; + + const addListener = jest.fn( + (eventName: string, callback: (data: any) => void) => { + listeners.push({eventName, callback}); + return {remove: jest.fn()}; + }, + ); + + const removeAllListeners = jest.fn((eventName?: string) => { + if (eventName) { + listeners = listeners.filter(l => l.eventName !== eventName); + } else { + listeners = []; + } + }); + + const emit = jest.fn((eventName: string, data: any) => { + const callbacks = listeners + .filter(l => l.eventName === eventName) + .map(l => l.callback); + callbacks.forEach(cb => cb(data)); + // Clear listeners after emit to avoid cross-test leakage + listeners = []; + }); + + return {addListener, removeAllListeners, emit}; +} + +const requireNativeComponent = (..._args: any[]) => { + const React = require('react'); + return (props: any) => + React.createElement('View', { + ...props, + testID: props?.testID ?? 'accelerated-checkout-buttons', + }); +}; + +const codegenNativeComponent = requireNativeComponent; + +const StyleSheet = { + flatten: jest.fn(style => style), +}; + +const exampleConfig = {preloading: true}; + +const ShopifyCheckoutSheetKit = { + version: '0.7.0', + getConstants: jest.fn(() => ({version: '0.7.0'})), + preload: jest.fn(), + present: jest.fn(), + dismiss: jest.fn(), + invalidateCache: jest.fn(), + getConfig: jest.fn(async () => exampleConfig), + setConfig: jest.fn(), + addEventListener: jest.fn(), + removeEventListeners: jest.fn(), + initiateGeolocationRequest: jest.fn(), + configureAcceleratedCheckouts: jest.fn(), + isAcceleratedCheckoutAvailable: jest.fn(), + addListener: jest.fn(), + removeListeners: jest.fn(), +}; + +// CommonJS export for Jest manual mock resolution +module.exports = { + Platform: {OS: 'ios'}, + PermissionsAndroid: { + requestMultiple: jest.fn(async () => ({})), + }, + NativeEventEmitter: jest.fn(() => createMockEmitter()), + requireNativeComponent, + codegenNativeComponent, + TurboModuleRegistry: { + getEnforcing: jest.fn((name: string) => { + if (name === 'ShopifyCheckoutSheetKit') { + return ShopifyCheckoutSheetKit; + } + return null; + }), + }, + NativeModules: { + ShopifyCheckoutSheetKit: { + ...ShopifyCheckoutSheetKit, + eventEmitter: createMockEmitter(), + }, + }, + StyleSheet, +}; diff --git a/react-native/babel.config.js b/react-native/babel.config.js new file mode 100644 index 00000000..f7b3da3b --- /dev/null +++ b/react-native/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: ['module:@react-native/babel-preset'], +}; diff --git a/react-native/dev.yml b/react-native/dev.yml new file mode 100644 index 00000000..fdd13738 --- /dev/null +++ b/react-native/dev.yml @@ -0,0 +1,83 @@ +name: checkout-sheet-kit-react-native + +dev.edition: 2024 + +type: node + +up: + - packages: + - swiftlint + - sccache + - ruby + - xcode: + version: "26.2" + runtimes: + ios: + - version: 23C54 # 26.2 + architecture_variant: arm64 + # Node / pnpm versions are mirrored from package.json (engines.node, + # packageManager). dev validates engines.node matches the version below on + # every `dev up`, so drift is caught automatically. If you bump one, bump + # both. + - node: + version: v22.14.0 + package_manager: pnpm@10.33.1 + - custom: + name: Remove stale Yarn artifacts + met?: test ! -d .yarn + meet: | + rm -rf .yarn + - custom: + name: Install NPM dependencies + met?: ls -l | grep node_modules + meet: | + pnpm install + - custom: + name: Install gems + met?: (cd sample/ios && bundle check) + meet: | + (cd sample/ios && bundle install) + - custom: + name: Install pods + met?: (cd sample/ios && bundle exec pod check --ignore-dev-pods) + meet: | + pnpm run pod-install + - custom: + name: Build node module + met?: (cd modules/@shopify/checkout-sheet-kit && ls -l | grep lib) + meet: | + pnpm module build + +check: + lint_swift: ./scripts/lint_swift + lint_module: pnpm module lint + lint_sample: pnpm sample lint + +commands: + server: + desc: 'Start development server' + run: pnpm sample start --reset-cache + + ios: + desc: 'Run iOS simulator' + run: pnpm sample ios + + android: + desc: 'Run Android emulator' + run: pnpm sample android + + clean: + desc: 'rm -rf all generated directories' + run: | + pnpm module clean + pnpm sample clean + pnpm clean + if command -v sccache >/dev/null 2>&1; then + sccache --stop-server 2>/dev/null || true + fi + echo "✅ Cleaned root, module and sample workspaces" + + fix: + desc: Fix linting + run: | + ./scripts/lint_swift fix diff --git a/react-native/docs/contributing/release.md b/react-native/docs/contributing/release.md new file mode 100644 index 00000000..3e478044 --- /dev/null +++ b/react-native/docs/contributing/release.md @@ -0,0 +1,21 @@ +# Release + +The `@shopify/checkout-sheet-kit` module is published to the NPM package +registry with public access. + +In order to publish a new version of the package, you must complete the +following steps: + +1. Bump the version in `modules/@shopify/checkout-sheet-kit/package.json` to an + appropriate value. +2. Add a [Changelog](./CHANGELOG.md) entry. +3. Merge your PR to `main`. +4. Create a [Release](/releases) for your new version. + +Creating and publishing a Github release with begin the automated process of +publishing the latest version of the package to NPM. It will clean the module +folder, build a new version, run `npm pack --dry-run` to verify the contents and +publish to the NPM registry. + +You can follow the release action process via +https://github.com/Shopify/checkout-sheet-kit-react-native/actions/workflows/publish.yml. diff --git a/react-native/documentation/media/ios_configuration.png b/react-native/documentation/media/ios_configuration.png new file mode 100644 index 00000000..1bcc10fd Binary files /dev/null and b/react-native/documentation/media/ios_configuration.png differ diff --git a/react-native/documentation/media/settings_developer_universal_links.jpg b/react-native/documentation/media/settings_developer_universal_links.jpg new file mode 100644 index 00000000..92db043e Binary files /dev/null and b/react-native/documentation/media/settings_developer_universal_links.jpg differ diff --git a/react-native/documentation/media/settings_diagnostics.jpg b/react-native/documentation/media/settings_diagnostics.jpg new file mode 100644 index 00000000..03f7e44a Binary files /dev/null and b/react-native/documentation/media/settings_diagnostics.jpg differ diff --git a/react-native/documentation/media/settings_diagnostics_configured.jpg b/react-native/documentation/media/settings_diagnostics_configured.jpg new file mode 100644 index 00000000..f750ba2b Binary files /dev/null and b/react-native/documentation/media/settings_diagnostics_configured.jpg differ diff --git a/react-native/documentation/media/settings_search_universal_links.jpg b/react-native/documentation/media/settings_search_universal_links.jpg new file mode 100644 index 00000000..3f31b017 Binary files /dev/null and b/react-native/documentation/media/settings_search_universal_links.jpg differ diff --git a/react-native/documentation/universal_links_ios.md b/react-native/documentation/universal_links_ios.md new file mode 100644 index 00000000..12262e4c --- /dev/null +++ b/react-native/documentation/universal_links_ios.md @@ -0,0 +1,230 @@ +# Universal Links + +- [What are Universal Links?](#what-are-universal-links) +- [Setting Up an Entitlements File for Universal Links](#setting-up-an-entitlements-file-for-universal-links) + - [Steps to Configure the Entitlements File](#steps-to-configure-the-entitlements-file) + - [Points to Consider](#points-to-consider) +- [Configuring Universal Links for your Storefront](#configuring-universal-links-for-your-storefront) +- [Important: The catch-all route `*`](#important-the-catch-all-route-) +- [Handling Universal Links in your app](#handling-universal-links-in-your-app) +- [Security Considerations](#security-considerations) +- [Testing](#testing) +- [Testing](#testing-1) + - [1. Validate Associated Domains](#1-validate-associated-domains) + - [2. Trigger a Universal Link from terminal](#2-trigger-a-universal-link-from-terminal) + - [3. Universal Links Diagnostics (real device only)](#3-universal-links-diagnostics-real-device-only) +- [Further Reading and Resources](#further-reading-and-resources) + +## What are Universal Links? + +Universal Links are an iOS feature which allow apps to open links directly in an +app rather than sending the user to a browser. This feature provides a seamless +user experience by directly engaging app users with relevant content. + +## Setting Up an Entitlements File for Universal Links + +To enable Universal Links in your iOS app, you'll need to configure an +entitlements file that specifies the associated domains your app is allowed to +handle. Here’s how you can set it up: + +### Steps to Configure the Entitlements File + +1. **Open Your Xcode Project**: + + - Launch Xcode and open your project or workspace file. + +2. **Navigate to Your App Target's Capabilities**: + + - Click on your project in the Project Navigator. + - Select your app target from the list of targets. + - Click on the "Capabilities" tab at the top of the editor. + +3. **Enable Associated Domains**: + + - In the Capabilities section, find "Associated Domains" and toggle the + switch to enable it. + - A new section should appear to configure your associated domains. + +4. **Add Your Associated Domains**: + + - Add each domain you want to associate with your app in the format: + `applinks:yourdomain.com` + - For example, `applinks:example.com` if your domain is `example.com`. + +5. **Verify the Entitlements File**: + + - Xcode will automatically add an entitlements file to your project if it + doesn't exist, or update your existing one. You can verify this by checking + for a file named `YourApp.entitlements` in your project. + - Open this file to ensure the associated domains are listed correctly under + the `com.apple.developer.associated-domains` entry. + +6. **Build and Run Your App**: + - Make sure your app's provisioning profile includes support for the + associated domains capability. + - Build and run your app on a device to test the setup. + +### Points to Consider + +- **HTTPS Requirement**: Ensure all of your associated domains are served over + HTTPS. +- **Multiple Domains**: You can list multiple associated domains by adding each + one on a new line in the Associated Domains section within Xcode. +- **Testing**: After setting up, test your Universal Links by opening valid URLs + on a device to ensure they correctly redirect to your app. + +> [!IMPORTANT] +> +> `.well-known/apple-app-site-association` files will **not** be served from +> "\*.myshopify.com" domains. You must connect a non-myshopify domain under +> https://admin.shopify.com/settings/domains to serve these files. + +## Configuring Universal Links for your Storefront + +iOS Buy SDK Configuration + +- Start by navigating to your storefront's Admin panel at + https://admin.shopify.com/settings/apps/development. +- If an app is already set up, select the appropriate one. If not, proceed to + create a new app. +- Within your app's "Configuration" tab, click "Edit" on the "Storefront API" + section. +- In the "iOS Buy SDK" section, enter your Apple App ID, activate "Use iOS + Universal Links," and upload your Apple app certificate. + +> [!NOTE] Your Apple app certificate will ensure the integrity of your +> apple-app-site-association file. + +The result of configuring Universal Links for your store will be a +`.well-known/apple-app-site-association` JSON file served from your public +storefront domain. This file informs iOS about the routes that should open +directly in your app instead of in a web browser. Apple will periodically fetch +this file to configure app routing on buyer's devices. This process is essential +for ensuring a seamless app experience when users navigate URLs associated with +your app. + +```json +{ + "applinks": { + "apps": [], + "details": [ + { + "appID": "{APPLE_TEAM_ID}.{BUNDLE_IDENTIFIER}", + "paths": [ + "NOT /admin/*", + "NOT /*/amazon_payments/callback", + "NOT /*/checkouts/*/express/callback", + "/*" + ] + } + ] + } +} +``` + +## Important: The catch-all route `*` + +The /\* catch-all route in the configuration indicates that iOS should open any +URL from your domain in your app, except for those paths specifically excluded +by "NOT" rules. + +- **Exclusions**: URLs like admin paths and payment callbacks are excluded by + "NOT" rules to prevent them from opening in the app. +- **Email Links**: Links in emails, such as abandoned carts, will open in your + app when clicked. +- **Shared Links**: General web links from your domain will open in your app + unless they're specifically excluded. +- **Webview Links**: Links opened in a webview won't trigger the app unless it's + set up to support them. +- **Excluded Paths**: URLs like /admin/\* and payment callbacks will open + outside the app as specified. + +--- + +## Handling Universal Links in your app + +See https://github.com/Shopify/checkout-sheet-kit-react-native/blob/main/sample/src/App.tsx for sample code to implement Universal Links in your app. + +## Security Considerations + +- **Data Privacy**: + + - Universal Links can pass sensitive information through URLs. Ensure any + personal or sensitive data is encrypted or tokenized to protect user + privacy. + +- **URL Handling**: + + - Rigorously validate and sanitize any data received from Universal Links to + prevent URL injection attacks. Only allow expected URL formats and handle + unexpected URLs gracefully. + +- **App Update and Expiry**: + + - Keep your app updated to handle links correctly, and ensure that any links + requiring special app capabilities are managed. If a link cannot be handled + by the currently installed app, consider providing users with a fallback + option or gracefully notifying them. + +- **Testing and Monitoring**: + + - Regularly test the functionality of Universal Links to ensure they are + correctly pointing to your app and not opening other apps or browsers with + potentially harmful content. + - Monitor your app for any attempts at misuse or abuse of links. + +- **Certificate Management**: + + - Maintain secure and appropriate certificate management practices for your + app to prevent unauthorized access or configuration changes related to + Universal Links. + +## Testing + +With Universal Links set up, you can test the implementation in the simulator by +triggering URLs manually with: + +```sh +xcrun simctl openurl booted https://www.example.com/cart +``` + +## Testing + +### 1. Validate Associated Domains + +Verify that your app's entitlements file includes the correct associated domains. + +Ensure that the `.well-known/apple-app-site-association` file is accessible by going to `"https://{your_storefront_domain}/.well-known/apple-app-site-association"`. Remember that this file will only be accesible on a custom hosted domain, and not a "*.myshopify.com" domain. + +### 2. Trigger a Universal Link from terminal + +With Universal Links set up, you can test the implementation in the simulator by triggering URLs manually with: + +```sh +xcrun simctl openurl booted https://www.example.com/cart +``` + +### 3. Universal Links Diagnostics (real device only) + +Open "Settings" on a real device and search "Universal links": + + + +Go to "Diagnostics": + + + +Enter the URL for your store: + + + +Hit "Go" to ensure the well known files have been configured correctly. + + + +## Further Reading and Resources + +For more comprehensive information, consider visiting: + +- [Apple's Official Universal Links Documentation](https://developer.apple.com/documentation/xcode/supporting-universal-links-in-your-app) +- [Testing Universal Links](https://developer.apple.com/documentation/technotes/tn3155-debugging-universal-links#Test-universal-links-behavior) diff --git a/react-native/jest.config.js b/react-native/jest.config.js new file mode 100644 index 00000000..6763de59 --- /dev/null +++ b/react-native/jest.config.js @@ -0,0 +1,16 @@ +module.exports = { + preset: 'react-native', + modulePathIgnorePatterns: ['modules/@shopify/checkout-sheet-kit/lib'], + modulePaths: ['/sample/node_modules'], + setupFiles: ['/jest.setup.ts'], + transform: { + '\\.[jt]sx?$': 'babel-jest', + }, + globals: { + 'ts-jest': { + tsConfig: { + importHelpers: true, + }, + }, + }, +}; diff --git a/react-native/jest.setup.ts b/react-native/jest.setup.ts new file mode 100644 index 00000000..d195ddb1 --- /dev/null +++ b/react-native/jest.setup.ts @@ -0,0 +1,8 @@ +/** + * Ensure Jest picks up manual mocks written in TypeScript. + * Jest resolves __mocks__/react-native automatically when a test calls jest.mock('react-native') + * or when the module is required and a manual mock exists. No runtime code needed here. + * This file exists to ensure TypeScript is part of Jest's setupFiles and compiled. + */ + +export {}; diff --git a/react-native/metro.config.js b/react-native/metro.config.js new file mode 100644 index 00000000..e7eb3f45 --- /dev/null +++ b/react-native/metro.config.js @@ -0,0 +1,62 @@ +const path = require('path'); +const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config'); + +const root = path.resolve(__dirname); +const sample = path.resolve(root, 'sample'); + +/** + * Metro configuration + * https://reactnative.dev/docs/metro + * + * @type {import('@react-native/metro-config').MetroConfig} + */ +const config = mergeConfig(getDefaultConfig(__dirname), { + projectRoot: sample, + + watchFolders: [root], + + resolver: { + resolveRequest: (context, moduleName, platform) => { + if ( + moduleName === '@shopify/checkout-sheet-kit' || + moduleName.startsWith('@shopify/checkout-sheet-kit/') + ) { + const sub = moduleName.replace('@shopify/checkout-sheet-kit', ''); + const target = path.resolve( + root, + 'modules', + '@shopify/checkout-sheet-kit', + 'src', + sub ? sub.replace(/^\//, '') : 'index.ts', + ); + return {type: 'sourceFile', filePath: target}; + } + return context.resolveRequest(context, moduleName, platform); + }, + extraNodeModules: { + react: path.resolve(sample, 'node_modules', 'react'), + 'react-native': path.resolve(sample, 'node_modules', 'react-native'), + 'react-native-gesture-handler': path.resolve( + root, + 'node_modules', + 'react-native-gesture-handler', + ), + '@shopify/checkout-sheet-kit': path.resolve( + root, + 'modules', + '@shopify/checkout-sheet-kit', + ), + }, + }, + + transformer: { + getTransformOptions: async () => ({ + transform: { + experimentalImportSupport: false, + inlineRequires: true, + }, + }), + }, +}); + +module.exports = config; diff --git a/react-native/modules/@shopify/checkout-sheet-kit/LICENSE b/react-native/modules/@shopify/checkout-sheet-kit/LICENSE new file mode 100644 index 00000000..d42681cf --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/LICENSE @@ -0,0 +1,20 @@ +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/react-native/modules/@shopify/checkout-sheet-kit/RNShopifyCheckoutSheetKit.podspec b/react-native/modules/@shopify/checkout-sheet-kit/RNShopifyCheckoutSheetKit.podspec new file mode 100644 index 00000000..092b4913 --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/RNShopifyCheckoutSheetKit.podspec @@ -0,0 +1,47 @@ +require "json" + +package = JSON.parse(File.read(File.join(__dir__, "package.json"))) + +folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32' + +fabric_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1' + +Pod::Spec.new do |s| + s.name = "RNShopifyCheckoutSheetKit" + s.version = package["version"] + s.summary = package["description"] + s.homepage = package["homepage"] + s.license = package["license"] + s.authors = package["author"] + + s.platforms = { :ios => "13.0" } + s.source = { :git => "https://github.com/Shopify/checkout-sheet-kit-react-native.git", :tag => "#{s.version}" } + + s.source_files = "ios/*.{h,m,mm,swift}" + + s.dependency "React-Core" + s.dependency "ShopifyCheckoutSheetKit", "~> 3.8.0" + s.dependency "ShopifyCheckoutSheetKit/AcceleratedCheckouts", "~> 3.8.0" + + if fabric_enabled + # Use React Native's helper if available, otherwise add dependencies directly + if defined?(install_modules_dependencies) + install_modules_dependencies(s) + else + # Fallback: manually specify dependencies for New Architecture + s.dependency "React-Codegen" + s.dependency "RCT-Folly", :modular_headers => true + s.dependency "RCTRequired" + s.dependency "RCTTypeSafety" + s.dependency "ReactCommon/turbomodule/core" + end + + s.compiler_flags = folly_compiler_flags + " -DRCT_NEW_ARCH_ENABLED=1" + + s.pod_target_xcconfig = { + "HEADER_SEARCH_PATHS" => "\"$(PODS_ROOT)/boost\"", + "OTHER_CPLUSPLUSFLAGS" => "-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1", + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17" + } + end +end diff --git a/react-native/modules/@shopify/checkout-sheet-kit/android/build.gradle b/react-native/modules/@shopify/checkout-sheet-kit/android/build.gradle new file mode 100644 index 00000000..82b9a034 --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/android/build.gradle @@ -0,0 +1,103 @@ +buildscript { + repositories { + google() + mavenCentral() + } + + dependencies { + classpath "com.android.tools.build:gradle:8.11.0" + } +} + +def isNewArchitectureEnabled() { + return rootProject.hasProperty("newArchEnabled") && rootProject.getProperty("newArchEnabled") == "true" +} + +apply plugin: "com.android.library" + +if (isNewArchitectureEnabled()) { + apply plugin: "com.facebook.react" +} + +def getExtOrIntegerDefault(name) { + return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties[name]).toInteger() +} + +static def supportsNamespace() { + def parsed = com.android.Version.ANDROID_GRADLE_PLUGIN_VERSION.tokenize('.') + def major = parsed[0].toInteger() + def minor = parsed[1].toInteger() + + // Namespace support was added in 7.3.0 + return (major == 7 && minor >= 3) || major >= 8 +} + +buildToolsVersion = "35.0.0" +minSdkVersion = 23 +compileSdkVersion = 36 +targetSdkVersion = 35 + +// We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP. +ndkVersion = "23.1.7779620" + +android { + if (supportsNamespace()) { + namespace "com.shopify.reactnative.checkoutsheetkit" + + sourceSets { + main { + manifest.srcFile "src/main/AndroidManifestNew.xml" + } + } + } + + compileSdkVersion getExtOrIntegerDefault("compileSdkVersion") + + defaultConfig { + minSdkVersion getExtOrIntegerDefault("minSdkVersion") + targetSdkVersion getExtOrIntegerDefault("targetSdkVersion") + + } + + buildTypes { + debug { + minifyEnabled false + } + + release { + minifyEnabled false + + // Rules in consumerProguardFiles are automatically included in the ProGuard configuration of any application that uses the library + consumerProguardFiles 'proguard-rules.pro' + } + } + + lintOptions { + disable "GradleCompatible" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +repositories { + mavenCentral() + google() +} + + +dependencies { + // For < 0.71, this will be from the local maven repo + // For > 0.71, this will be replaced by `com.facebook.react:react-android:$version` by react gradle plugin + //noinspection GradleDynamicVersion + implementation "com.facebook.react:react-native:+" + // Uncomment to install dependencies for local development + // implementation("com.facebook.react:react-android:+") + + implementation("com.shopify:checkout-sheet-kit:${SHOPIFY_CHECKOUT_SDK_VERSION}") + implementation("com.fasterxml.jackson.core:jackson-databind:2.12.5") + debugImplementation("com.shopify:checkout-sheet-kit:${SHOPIFY_CHECKOUT_SDK_VERSION}") +} + diff --git a/react-native/modules/@shopify/checkout-sheet-kit/android/gradle.properties b/react-native/modules/@shopify/checkout-sheet-kit/android/gradle.properties new file mode 100644 index 00000000..8445938b --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/android/gradle.properties @@ -0,0 +1,8 @@ +minSdkVersion=23 +targetSdkVersion=35 +compileSdkVersion=36 +ndkVersion=23.1.7779620 +buildToolsVersion = "35.0.0" + +# Version of Shopify Checkout SDK to use with React Native +SHOPIFY_CHECKOUT_SDK_VERSION=3.5.3 diff --git a/react-native/modules/@shopify/checkout-sheet-kit/android/proguard-rules.pro b/react-native/modules/@shopify/checkout-sheet-kit/android/proguard-rules.pro new file mode 100644 index 00000000..afeede94 --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/android/proguard-rules.pro @@ -0,0 +1,2 @@ +# Keep Checkout Sheet Kit classes +-keep class com.shopify.checkoutsheetkit.** { *; } diff --git a/react-native/modules/@shopify/checkout-sheet-kit/android/src/main/AndroidManifest.xml b/react-native/modules/@shopify/checkout-sheet-kit/android/src/main/AndroidManifest.xml new file mode 100644 index 00000000..a5caf509 --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/android/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + diff --git a/react-native/modules/@shopify/checkout-sheet-kit/android/src/main/AndroidManifestNew.xml b/react-native/modules/@shopify/checkout-sheet-kit/android/src/main/AndroidManifestNew.xml new file mode 100644 index 00000000..a2f47b60 --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/android/src/main/AndroidManifestNew.xml @@ -0,0 +1,2 @@ + + diff --git a/react-native/modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/CustomCheckoutEventProcessor.java b/react-native/modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/CustomCheckoutEventProcessor.java new file mode 100644 index 00000000..f1a194a2 --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/CustomCheckoutEventProcessor.java @@ -0,0 +1,188 @@ +/* +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +package com.shopify.reactnative.checkoutsheetkit; + +import android.content.Context; +import android.util.Log; +import android.webkit.GeolocationPermissions; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.shopify.checkoutsheetkit.*; +import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.facebook.react.bridge.WritableNativeMap; +import com.facebook.react.bridge.ReactApplicationContext; +import com.shopify.checkoutsheetkit.pixelevents.PixelEvent; +import com.shopify.checkoutsheetkit.lifecycleevents.CheckoutCompletedEvent; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class CustomCheckoutEventProcessor extends DefaultCheckoutEventProcessor { + private final ReactApplicationContext reactContext; + private final ObjectMapper mapper = new ObjectMapper(); + + // Geolocation-specific variables + + private String geolocationOrigin; + private GeolocationPermissions.Callback geolocationCallback; + + public CustomCheckoutEventProcessor(Context context, ReactApplicationContext reactContext) { + super(context); + this.reactContext = reactContext; + } + + // Public methods + + public void invokeGeolocationCallback(boolean allow) { + if (geolocationCallback != null) { + boolean retainGeolocationForFutureRequests = false; + geolocationCallback.invoke(geolocationOrigin, allow, retainGeolocationForFutureRequests); + geolocationCallback = null; + } + } + + // Lifecycle events + + /** + * This method is called when the checkout sheet webpage requests geolocation + * permissions. + * + * Since the app needs to request permissions first before granting, we store + * the callback and origin in memory and emit a "geolocationRequest" event to + * the app. The app will then request the necessary geolocation permissions + * and invoke the native callback with the result. + * + * @param origin - The origin of the request + * @param callback - The callback to invoke when the app requests permissions + */ + @Override + public void onGeolocationPermissionsShowPrompt(@NonNull String origin, + @NonNull GeolocationPermissions.Callback callback) { + + // Store the callback and origin in memory. The kit will wait for the app to + // request permissions first before granting. + this.geolocationCallback = callback; + this.geolocationOrigin = origin; + + // Emit a "geolocationRequest" event to the app. + try { + Map event = new HashMap<>(); + event.put("origin", origin); + sendEventWithStringData("geolocationRequest", mapper.writeValueAsString(event)); + } catch (IOException e) { + Log.e("ShopifyCheckoutSheetKit", "Error emitting \"geolocationRequest\" event", e); + } + } + + @Override + public void onGeolocationPermissionsHidePrompt() { + super.onGeolocationPermissionsHidePrompt(); + + // Reset the geolocation callback and origin when the prompt is hidden. + this.geolocationCallback = null; + this.geolocationOrigin = null; + } + + @Override + public void onWebPixelEvent(@NonNull PixelEvent event) { + try { + String data = mapper.writeValueAsString(event); + sendEventWithStringData("pixel", data); + } catch (IOException e) { + Log.e("ShopifyCheckoutSheetKit", "Error processing pixel event", e); + } + } + + @Override + public void onCheckoutFailed(CheckoutException checkoutError) { + try { + String data = mapper.writeValueAsString(populateErrorDetails(checkoutError)); + sendEventWithStringData("error", data); + } catch (IOException e) { + Log.e("ShopifyCheckoutSheetKit", "Error processing checkout failed event", e); + } + } + + @Override + public void onCheckoutCanceled() { + sendEvent("close", null); + } + + @Override + public void onCheckoutCompleted(@NonNull CheckoutCompletedEvent event) { + try { + String data = mapper.writeValueAsString(event); + sendEventWithStringData("completed", data); + } catch (IOException e) { + Log.e("ShopifyCheckoutSheetKit", "Error processing completed event", e); + } + } + + // Private + + private Map populateErrorDetails(CheckoutException checkoutError) { + Map errorMap = new HashMap(); + errorMap.put("__typename", getErrorTypeName(checkoutError)); + errorMap.put("message", checkoutError.getErrorDescription()); + errorMap.put("recoverable", checkoutError.isRecoverable()); + errorMap.put("code", checkoutError.getErrorCode()); + + if (checkoutError instanceof HttpException) { + errorMap.put("statusCode", ((HttpException) checkoutError).getStatusCode()); + } + + return errorMap; + } + + private String getErrorTypeName(CheckoutException error) { + if (error instanceof CheckoutExpiredException) { + return "CheckoutExpiredError"; + } else if (error instanceof ClientException) { + return "CheckoutClientError"; + } else if (error instanceof HttpException) { + return "CheckoutHTTPError"; + } else if (error instanceof ConfigurationException) { + return "ConfigurationError"; + } else if (error instanceof CheckoutSheetKitException) { + return "InternalError"; + } else { + return "UnknownError"; + } + } + + private void sendEvent(String eventName, @Nullable WritableNativeMap params) { + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit(eventName, params); + } + + private void sendEventWithStringData(String name, String data) { + reactContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + .emit(name, data); + } +} diff --git a/react-native/modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java b/react-native/modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java new file mode 100644 index 00000000..53dd39e2 --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java @@ -0,0 +1,349 @@ +/* +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +package com.shopify.reactnative.checkoutsheetkit; + +import android.app.Activity; +import android.content.Context; +import androidx.activity.ComponentActivity; +import androidx.annotation.NonNull; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.ReadableMap; +import com.facebook.react.bridge.WritableMap; +import com.shopify.checkoutsheetkit.*; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +public class ShopifyCheckoutSheetKitModule extends ReactContextBaseJavaModule { + private static final String MODULE_NAME = "ShopifyCheckoutSheetKit"; + + public static Configuration checkoutConfig = new Configuration(); + + private final ReactApplicationContext reactContext; + + private CheckoutSheetKitDialog checkoutSheet; + + private CustomCheckoutEventProcessor checkoutEventProcessor; + + public ShopifyCheckoutSheetKitModule(ReactApplicationContext reactContext) { + super(reactContext); + + this.reactContext = reactContext; + + ShopifyCheckoutSheetKit.configure(configuration -> { + configuration.setPlatform(Platform.REACT_NATIVE); + checkoutConfig = configuration; + }); + } + + @NonNull + @Override + public String getName() { + return MODULE_NAME; + } + + @Override + public Map getConstants() { + final Map constants = new HashMap<>(); + constants.put("version", ShopifyCheckoutSheetKit.version); + return constants; + } + + @ReactMethod + public void addListener(String eventName) { + // No-op but required for RN to register module + } + + @ReactMethod + public void removeListeners(Integer count) { + // No-op but required for RN to register module + } + + @ReactMethod + public void present(String checkoutURL) { + Activity currentActivity = getCurrentActivity(); + if (currentActivity instanceof ComponentActivity) { + checkoutEventProcessor = new CustomCheckoutEventProcessor(currentActivity, this.reactContext); + currentActivity.runOnUiThread(() -> { + checkoutSheet = ShopifyCheckoutSheetKit.present(checkoutURL, (ComponentActivity) currentActivity, + checkoutEventProcessor); + }); + } + } + + @ReactMethod + public void dismiss() { + if (checkoutSheet != null) { + checkoutSheet.dismiss(); + checkoutSheet = null; + } + } + + @ReactMethod + public void preload(String checkoutURL) { + Activity currentActivity = getCurrentActivity(); + + if (currentActivity instanceof ComponentActivity) { + ShopifyCheckoutSheetKit.preload(checkoutURL, (ComponentActivity) currentActivity); + } + } + + @ReactMethod + public void invalidateCache() { + ShopifyCheckoutSheetKit.invalidate(); + } + + @ReactMethod + public void getConfig(Promise promise) { + WritableMap resultConfig = Arguments.createMap(); + + resultConfig.putBoolean("preloading", checkoutConfig.getPreloading().getEnabled()); + resultConfig.putString("colorScheme", colorSchemeToString(checkoutConfig.getColorScheme())); + resultConfig.putString("logLevel", logLevelToString(checkoutConfig.getLogLevel())); + + promise.resolve(resultConfig); + } + + @ReactMethod + public void setConfig(ReadableMap config) { + Context context = getReactApplicationContext(); + + ShopifyCheckoutSheetKit.configure(configuration -> { + if (config.hasKey("preloading")) { + configuration.setPreloading(new Preloading(config.getBoolean("preloading"))); + } + + if (config.hasKey("logLevel")) { + LogLevel logLevel = getLogLevel(config.getString("logLevel")); + configuration.setLogLevel(logLevel); + } else { + configuration.setLogLevel(LogLevel.ERROR); + } + + if (config.hasKey("colorScheme")) { + ColorScheme colorScheme = getColorScheme(Objects.requireNonNull(config.getString("colorScheme"))); + ReadableMap colorsConfig = config.hasKey("colors") ? config.getMap("colors") : null; + ReadableMap androidConfig = null; + + if (colorsConfig != null && colorsConfig.hasKey("android")) { + androidConfig = colorsConfig.getMap("android"); + } + + if (this.isValidColorConfig(androidConfig)) { + ColorScheme colorSchemeWithOverrides = getColors(colorScheme, androidConfig); + if (colorSchemeWithOverrides != null) { + configuration.setColorScheme(colorSchemeWithOverrides); + checkoutConfig = configuration; + return; + } + } + + configuration.setColorScheme(colorScheme); + } + + checkoutConfig = configuration; + }); + } + + @ReactMethod + public void initiateGeolocationRequest(Boolean allow) { + if (checkoutEventProcessor != null) { + checkoutEventProcessor.invokeGeolocationCallback(allow); + } + } + + // Private + + private ColorScheme getColorScheme(String colorScheme) { + switch (colorScheme) { + case "web_default": + return new ColorScheme.Web(); + case "light": + return new ColorScheme.Light(); + case "dark": + return new ColorScheme.Dark(); + case "automatic": + default: + return new ColorScheme.Automatic(); + } + } + + private String colorSchemeToString(ColorScheme colorScheme) { + return colorScheme.getId(); + } + + private LogLevel getLogLevel(String logLevel) { + if (logLevel == null) { + return LogLevel.ERROR; + } + + switch (logLevel.toLowerCase()) { + case "debug": + return LogLevel.DEBUG; + default: + return LogLevel.ERROR; + } + } + + private String logLevelToString(LogLevel logLevel) { + if (logLevel == LogLevel.DEBUG) { + return "debug"; + } + return "error"; + } + + private boolean isValidColorConfig(ReadableMap config) { + if (config == null) { + return false; + } + + String[] requiredColorKeys = { "backgroundColor", "progressIndicator", "headerTextColor", "headerBackgroundColor" }; + + for (String key : requiredColorKeys) { + if (!config.hasKey(key) || config.getString(key) == null || parseColor(config.getString(key)) == null) { + return false; + } + } + + // closeButtonColor is optional, so we only validate it if it's present + if (config.hasKey("closeButtonColor") && config.getString("closeButtonColor") != null) { + if (parseColor(config.getString("closeButtonColor")) == null) { + return false; + } + } + + return true; + } + + private boolean isValidColorScheme(ColorScheme colorScheme, ReadableMap colorConfig) { + if (colorConfig == null) { + return false; + } + + if (colorScheme instanceof ColorScheme.Automatic) { + if (!colorConfig.hasKey("light") || !colorConfig.hasKey("dark")) { + return false; + } + + boolean validLight = this.isValidColorConfig(colorConfig.getMap("light")); + boolean validDark = this.isValidColorConfig(colorConfig.getMap("dark")); + + return validLight && validDark; + } + + return this.isValidColorConfig(colorConfig); + } + + private Color parseColorFromConfig(ReadableMap config, String colorKey) { + if (config.hasKey(colorKey)) { + String colorStr = config.getString(colorKey); + return parseColor(colorStr); + } + + return null; + } + + private Colors createColorsFromConfig(ReadableMap config) { + if (config == null) { + return null; + } + + Color webViewBackground = parseColorFromConfig(config, "backgroundColor"); + Color headerBackground = parseColorFromConfig(config, "headerBackgroundColor"); + Color headerFont = parseColorFromConfig(config, "headerTextColor"); + Color progressIndicator = parseColorFromConfig(config, "progressIndicator"); + Color closeButtonColor = parseColorFromConfig(config, "closeButtonColor"); + + if (webViewBackground != null && progressIndicator != null && headerFont != null && headerBackground != null) { + return new Colors( + webViewBackground, + headerBackground, + headerFont, + progressIndicator, + // Parameter allows passing a custom drawable, we'll just support custom color + // for now + null, + closeButtonColor); + } + + return null; + } + + private ColorScheme getColors(ColorScheme colorScheme, ReadableMap config) { + if (!this.isValidColorScheme(colorScheme, config)) { + return null; + } + + if (colorScheme instanceof ColorScheme.Automatic && this.isValidColorScheme(colorScheme, config)) { + Colors lightColors = createColorsFromConfig(config.getMap("light")); + Colors darkColors = createColorsFromConfig(config.getMap("dark")); + + if (lightColors != null && darkColors != null) { + ColorScheme.Automatic automaticColorScheme = (ColorScheme.Automatic) colorScheme; + automaticColorScheme.setLightColors(lightColors); + automaticColorScheme.setDarkColors(darkColors); + return automaticColorScheme; + } + } + + Colors colors = createColorsFromConfig(config); + + if (colors != null) { + if (colorScheme instanceof ColorScheme.Light) { + ((ColorScheme.Light) colorScheme).setColors(colors); + } else if (colorScheme instanceof ColorScheme.Dark) { + ((ColorScheme.Dark) colorScheme).setColors(colors); + } else if (colorScheme instanceof ColorScheme.Web) { + ((ColorScheme.Web) colorScheme).setColors(colors); + } + return colorScheme; + } + + return null; + } + + private Color parseColor(String colorStr) { + try { + colorStr = colorStr.replace("#", ""); + + long color = Long.parseLong(colorStr, 16); + + if (colorStr.length() == 6) { + // If alpha is not included, assume full opacity + // "L" is not needed here on the end of the hex value + color = color | 0xFF000000; + } + + return new Color.SRGB((int) color); + } catch (NumberFormatException e) { + System.out.println("Warning: Invalid color string. Default color will be used."); + return null; + } + } +} diff --git a/react-native/modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitPackage.java b/react-native/modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitPackage.java new file mode 100644 index 00000000..5300fb63 --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitPackage.java @@ -0,0 +1,56 @@ +/* +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +package com.shopify.reactnative.checkoutsheetkit; + +import androidx.annotation.NonNull; + +import com.facebook.react.ReactPackage; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.uimanager.ViewManager; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ShopifyCheckoutSheetKitPackage implements ReactPackage { + + @NonNull + @Override + public List createViewManagers(@NonNull ReactApplicationContext reactContext) { + return Collections.emptyList(); + } + + @NonNull + @Override + public List createNativeModules( + @NonNull ReactApplicationContext reactContext) { + List modules = new ArrayList<>(); + + modules.add(new ShopifyCheckoutSheetKitModule(reactContext)); + + return modules; + } + +} diff --git a/react-native/modules/@shopify/checkout-sheet-kit/ios/.clang-format b/react-native/modules/@shopify/checkout-sheet-kit/ios/.clang-format new file mode 100644 index 00000000..f2646986 --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/ios/.clang-format @@ -0,0 +1,29 @@ +BasedOnStyle: LLVM +Language: ObjC +UseTab: Never +IndentWidth: 2 +ContinuationIndentWidth: 2 +ColumnLimit: 120 + +# Keep long macro calls (e.g., RCT_EXTERN_METHOD) readable +AlignAfterOpenBracket: DontAlign +BinPackArguments: false +BinPackParameters: false +PenaltyBreakBeforeFirstCallParameter: 200 +ReflowComments: false + +# Objective-C specifics +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true +ObjCBreakBeforeNestedBlockParam: true +PointerAlignment: Left +BreakBeforeBraces: Attach +SpaceBeforeParens: ControlStatements +SortIncludes: false + +# Treat common React Native macros as statements for nicer wrapping +StatementMacros: + - RCT_EXTERN_METHOD + - RCT_EXTERN_MODULE + - RCT_EXPORT_VIEW_PROPERTY + diff --git a/react-native/modules/@shopify/checkout-sheet-kit/ios/AcceleratedCheckoutButtons+Extensions.swift b/react-native/modules/@shopify/checkout-sheet-kit/ios/AcceleratedCheckoutButtons+Extensions.swift new file mode 100644 index 00000000..cddc7f6c --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/ios/AcceleratedCheckoutButtons+Extensions.swift @@ -0,0 +1,80 @@ +/* + MIT License + + Copyright 2023 - Present, Shopify Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import _PassKit_SwiftUI +import Foundation +import PassKit +import SwiftUI + +// MARK: - Apple Pay Button + +@available(iOS 16.0, *) +extension PayWithApplePayButtonLabel { + static func from(_ string: String?, fallback: PayWithApplePayButtonLabel = .plain) -> PayWithApplePayButtonLabel { + guard let string, let value = map[string] else { + return fallback + } + + return value + } + + private static let map: [String: PayWithApplePayButtonLabel] = [ + "addMoney": .addMoney, + "book": .book, + "buy": .buy, + "checkout": .checkout, + "continue": .continue, + "contribute": .contribute, + "donate": .donate, + "inStore": .inStore, + "order": .order, + "plain": .plain, + "reload": .reload, + "rent": .rent, + "setUp": .setUp, + "subscribe": .subscribe, + "support": .support, + "tip": .tip, + "topUp": .topUp + ] +} + +// MARK: - Apple Pay Button Style + +@available(iOS 16.0, *) +extension PayWithApplePayButtonStyle { + static func from(_ string: String?, fallback: PayWithApplePayButtonStyle = .automatic) -> PayWithApplePayButtonStyle { + guard let string, let value = map[string] else { + return fallback + } + + return value + } + + private static let map: [String: PayWithApplePayButtonStyle] = [ + "automatic": .automatic, + "black": .black, + "white": .white, + "whiteOutline": .whiteOutline + ] +} diff --git a/react-native/modules/@shopify/checkout-sheet-kit/ios/AcceleratedCheckoutButtons.swift b/react-native/modules/@shopify/checkout-sheet-kit/ios/AcceleratedCheckoutButtons.swift new file mode 100644 index 00000000..fcefb67c --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/ios/AcceleratedCheckoutButtons.swift @@ -0,0 +1,449 @@ +/* + MIT License + + Copyright 2023 - Present, Shopify Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import Foundation +import PassKit +import React +import ShopifyCheckoutSheetKit +import SwiftUI +import UIKit + +// MARK: - AcceleratedCheckout Components + +@available(iOS 16.0, *) +class AcceleratedCheckoutConfiguration { + static let shared = AcceleratedCheckoutConfiguration() + var configuration: ShopifyAcceleratedCheckouts.Configuration? + var applePayConfiguration: ShopifyAcceleratedCheckouts.ApplePayConfiguration? + + var available: Bool { + if #available(iOS 16.0, *) { + return configuration != nil + } else { + return false + } + } + + var applePayAvailable: Bool { + if #available(iOS 16.0, *) { + return applePayConfiguration != nil + } else { + return false + } + } +} + +@objc(RCTAcceleratedCheckoutButtonsManager) +class RCTAcceleratedCheckoutButtonsManager: RCTViewManager { + /// Internal property used in tests to simulate legacy devices + internal var supported: Bool = true + + override func view() -> UIView! { + if supported { + if #available(iOS 16.0, *) { + return RCTAcceleratedCheckoutButtonsView() + } + } + + // Return an empty view for iOS < 16.0 (silent fallback) + return UIView() + } + + override static func requiresMainQueueSetup() -> Bool { + return true + } + + override func constantsToExport() -> [AnyHashable: Any]! { + return [:] + } +} + +@available(iOS 16.0, *) +class RCTAcceleratedCheckoutButtonsView: UIView { + private var hostingController: UIHostingController? + private var configuration: ShopifyAcceleratedCheckouts.Configuration? + private weak var parentViewController: UIViewController? + internal var instance: AcceleratedCheckoutButtons? + + @objc var onSizeChange: RCTDirectEventBlock? + + // MARK: - Props + + // Note that prop values are intentionally nil so that the kit defaults are used + + /** + * Accepts either { cartId } or { variantId, quantity }. + */ + @objc var checkoutIdentifier: NSDictionary? { + didSet { + updateView() + } + } + + @objc var cornerRadius: NSNumber? { + didSet { + updateView() + } + } + + @objc var wallets: [String]? { + didSet { + invalidateIntrinsicContentSize() + setNeedsLayout() + updateView() + } + } + + @objc var applePayLabel: String? { + didSet { + updateView() + } + } + + @objc var applePayStyle: String? { + didSet { + updateView() + } + } + + @objc var onFail: RCTBubblingEventBlock? + @objc var onComplete: RCTBubblingEventBlock? + @objc var onCancel: RCTBubblingEventBlock? + @objc var onRenderStateChange: RCTBubblingEventBlock? + @objc var onShouldRecoverFromError: RCTDirectEventBlock? + @objc var onWebPixelEvent: RCTBubblingEventBlock? + @objc var onClickLink: RCTBubblingEventBlock? + + // MARK: - Private + + /// Compute the wallets to render based on the `wallets` prop. + /// If `wallets` is provided and empty, render nothing. No fallback here; SDK provides defaults. + private var shopifyWallets: [Wallet] { + guard let providedWallets = wallets else { return [] } + do { + return try convertToShopifyWallets(providedWallets) + } catch { + return [] + } + } + + override init(frame: CGRect) { + super.init(frame: frame) + setupView() + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + setupView() + } + + override func layoutSubviews() { + super.layoutSubviews() + hostingController?.view.frame = bounds + } + + // Deprecated in iOS 17 — superseded by registerForTraitChanges in setupView(). + // Remove this override when dropping iOS 16 support. + override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { + super.traitCollectionDidChange(previousTraitCollection) + if #unavailable(iOS 17.0) { + if traitCollection.hasDifferentColorAppearance(comparedTo: previousTraitCollection) { + updateView() + } + } + } + + override var intrinsicContentSize: CGSize { + let height = calculateRequiredHeight() + return CGSize(width: UIView.noIntrinsicMetric, height: height) + } + + private func setupView() { + // Configuration will be set via a static method from the main module + configuration = AcceleratedCheckoutConfiguration.shared.configuration + + // Find the parent view controller + DispatchQueue.main.async { [weak self] in + self?.parentViewController = self?.findViewController() + } + + updateView() + + // Replaces traitCollectionDidChange for iOS 17+. + // When dropping iOS 16 support, remove the traitCollectionDidChange override above. + if #available(iOS 17.0, *) { + registerForTraitChanges([UITraitUserInterfaceStyle.self]) { [weak self] (_: RCTAcceleratedCheckoutButtonsView, _: UITraitCollection) in + self?.updateView() + } + } + + // Listen for configuration updates + NotificationCenter.default.addObserver( + self, + selector: #selector(configurationUpdated), + name: Notification.Name("AcceleratedCheckoutConfigurationUpdated"), + object: nil + ) + + NotificationCenter.default.addObserver( + self, + selector: #selector(configurationUpdated), + name: Notification.Name("CheckoutKitConfigurationUpdated"), + object: nil + ) + + // Fire initial size change event + resizeWallets() + } + + private func findViewController() -> UIViewController? { + var responder: UIResponder? = self + while let nextResponder = responder?.next { + if let viewController = nextResponder as? UIViewController { + return viewController + } + responder = nextResponder + } + return nil + } + + @objc private func configurationUpdated() { + configuration = AcceleratedCheckoutConfiguration.shared.configuration + updateView() + } + + private func attachModifiers(to buttons: AcceleratedCheckoutButtons, wallets: [Wallet]?, applePayLabel: PayWithApplePayButtonLabel?, applePayStyle: PayWithApplePayButtonStyle) -> AcceleratedCheckoutButtons { + var modifiedButtons = buttons + + if let wallets { + modifiedButtons = modifiedButtons.wallets(wallets) + } + + if let applePayLabel { + modifiedButtons = modifiedButtons.applePayLabel(applePayLabel) + } + + modifiedButtons = modifiedButtons.applePayStyle(applePayStyle) + + if let cornerRadius { + modifiedButtons = modifiedButtons.cornerRadius(CGFloat(cornerRadius.doubleValue)) + } + + return modifiedButtons + } + + private func attachEventListeners(to buttons: AcceleratedCheckoutButtons) -> AcceleratedCheckoutButtons { + return buttons + .onComplete { [weak self] event in + self?.handleCheckoutCompleted(event) + } + .onFail { [weak self] error in + self?.handleCheckoutFailed(error) + } + .onCancel { [weak self] in + self?.handleCheckoutCancelled() + } + .onRenderStateChange { [weak self] state in + self?.handleRenderStateChange(state) + } + .onClickLink { [weak self] url in + self?.handleClickLink(url) + } + .onWebPixelEvent { [weak self] event in + self?.handleWebPixelEvent(event) + } + } + + private func updateView() { + let walletsEmpty = wallets != nil && shopifyWallets.isEmpty + + guard + let config = configuration, + let checkoutIdentifierDictionary = checkoutIdentifier as? [String: Any], + !walletsEmpty + else { + renderEmptyView() + return + } + + // Map wallets if provided; otherwise let the kit decide the defaults + let shopifyWallets: [Wallet]? = wallets != nil ? shopifyWallets : nil + + var buttons: AcceleratedCheckoutButtons + + if let cartIdentifier = extractCartIdentifier(from: checkoutIdentifierDictionary) { + buttons = AcceleratedCheckoutButtons(cartID: cartIdentifier) + } else if let productIdentifier = extractProductIdentifier(from: checkoutIdentifierDictionary) { + buttons = AcceleratedCheckoutButtons( + variantID: productIdentifier.variantId, + quantity: productIdentifier.quantity + ) + } else { + renderEmptyView() + return + } + + // Attach modifiers (wallets, applePayLabel, applePayStyle, cornerRadius) + buttons = attachModifiers(to: buttons, wallets: shopifyWallets, applePayLabel: PayWithApplePayButtonLabel.from(applePayLabel), applePayStyle: PayWithApplePayButtonStyle.from(applePayStyle)) + // Attach event handlers + buttons = attachEventListeners(to: buttons) + + var view: AnyView + + let colorScheme: SwiftUI.ColorScheme = traitCollection.userInterfaceStyle == .dark ? .dark : .light + + // Attach config (and Apple Pay config if available) + if let applePayConfig = AcceleratedCheckoutConfiguration.shared.applePayConfiguration { + view = AnyView(buttons.environmentObject(config).environmentObject(applePayConfig).environment(\.colorScheme, colorScheme)) + } else { + view = AnyView(buttons.environmentObject(config).environment(\.colorScheme, colorScheme)) + } + + if let hostingController { + hostingController.rootView = view + } else { + hostingController = UIHostingController(rootView: view) + hostingController?.view.backgroundColor = UIColor.clear + + // Ensure the hosting view can receive touch events + hostingController?.view.isUserInteractionEnabled = true + + if let hostingView = hostingController?.view { + addSubview(hostingView) + hostingView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + hostingView.topAnchor.constraint(equalTo: topAnchor), + hostingView.leadingAnchor.constraint(equalTo: leadingAnchor), + hostingView.trailingAnchor.constraint(equalTo: trailingAnchor), + hostingView.bottomAnchor.constraint(equalTo: bottomAnchor) + ]) + } + } + + // Ensure this view can also receive touch events + isUserInteractionEnabled = true + + instance = buttons + + // Fire size change event + resizeWallets() + } + + // MARK: - Event Handlers + + private func handleCheckoutCompleted(_ event: CheckoutCompletedEvent) { + onComplete?(ShopifyEventSerialization.serialize(checkoutCompletedEvent: event)) + } + + private func handleCheckoutFailed(_ error: CheckoutError) { + onFail?(ShopifyEventSerialization.serialize(checkoutError: error)) + } + + private func handleCheckoutCancelled() { + onCancel?([:]) + } + + private func handleRenderStateChange(_ state: RenderState) { + onRenderStateChange?(ShopifyEventSerialization.serialize(renderState: state)) + } + + private func handleWebPixelEvent(_ event: PixelEvent) { + onWebPixelEvent?(ShopifyEventSerialization.serialize(pixelEvent: event)) + } + + private func handleClickLink(_ url: URL) { + onClickLink?(ShopifyEventSerialization.serialize(clickEvent: url)) + } + + // MARK: - Helper Methods + + /// Parses `cartId` from `checkoutIdentifier` NSDictionary + private func extractCartIdentifier(from dictionary: [String: Any]) -> String? { + guard let rawCartId = dictionary["cartId"] as? String else { return nil } + let trimmedCartId = rawCartId.trimmingCharacters(in: .whitespacesAndNewlines) + return trimmedCartId.isEmpty ? nil : trimmedCartId + } + + /// Parses `variantId` and `quantity` from `checkoutIdentifier` NSDictionary + private func extractProductIdentifier(from dictionary: [String: Any]) -> (variantId: String, quantity: Int)? { + guard let rawVariantId = dictionary["variantId"] as? String else { return nil } + guard let rawQuantity = dictionary["quantity"] as? NSNumber else { return nil } + + let trimmedVariantId = rawVariantId.trimmingCharacters(in: .whitespacesAndNewlines) + let quantityValue = rawQuantity.intValue + + guard !trimmedVariantId.isEmpty, quantityValue > 0 else { return nil } + return (variantId: trimmedVariantId, quantity: quantityValue) + } + + private func renderEmptyView() { + instance = nil + hostingController?.rootView = AnyView(EmptyView()) + onSizeChange?(["height": 0]) + } + + /// Cases for returning 0 height + /// - No buttons instance available + /// - Wallets is explicitly an empty array + /// - OR wallets is provided and maps to empty + private func calculateRequiredHeight() -> CGFloat { + guard + let instance, + wallets?.isEmpty != true, + !(wallets != nil && shopifyWallets.isEmpty) + else { + return 0 + } + + let numberOfWallets = shopifyWallets.isEmpty + ? instance.wallets.count + : max(shopifyWallets.count, 1) + + let buttonHeight: CGFloat = 48 + let gapHeight: CGFloat = 8 + return (CGFloat(numberOfWallets) * buttonHeight) + (CGFloat(numberOfWallets - 1) * gapHeight) + } + + private func resizeWallets() { + DispatchQueue.main.async { [weak self] in + guard let self else { + return + } + + let height = self.calculateRequiredHeight() + self.onSizeChange?(["height": height]) + } + } + + private func convertToShopifyWallets(_ walletStrings: [String]) throws -> [Wallet] { + return try walletStrings.compactMap { walletString in + guard let wallet = Wallet(rawValue: walletString), wallet != nil else { + let message = "Unknown wallet option: \(String(describing: walletString))" + print("[ShopifyAcceleratedCheckouts] \(message)") + throw NSError(domain: "ShopifyAcceleratedCheckouts", code: 1, userInfo: ["message": message]) + } + + return wallet + } + } +} diff --git a/react-native/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit+EventSerialization.swift b/react-native/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit+EventSerialization.swift new file mode 100644 index 00000000..801937fb --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit+EventSerialization.swift @@ -0,0 +1,175 @@ +/* + MIT License + + Copyright 2023 - Present, Shopify Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import Foundation +import ShopifyCheckoutSheetKit + +/** + * Shared event serialization utilities for converting ShopifyCheckoutSheetKit events + * to React Native compatible dictionaries. + */ +internal enum ShopifyEventSerialization { + /** + * Encodes a Codable object to a JSON dictionary for React Native bridge. + */ + static func encodeToJSON(from value: Codable) -> [String: Any] { + let encoder = JSONEncoder() + + do { + let jsonData = try encoder.encode(value) + if let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] { + return jsonObject + } + } catch { + print("Error encoding to JSON object: \(error)") + } + return [:] + } + + /** + * Converts a JSON string to a dictionary. + */ + static func stringToJSON(from value: String?) -> [String: Any]? { + guard let data = value?.data(using: .utf8, allowLossyConversion: false) else { return [:] } + do { + return try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] + } catch { + print("Failed to convert string to JSON: \(error)", value ?? "nil") + return [:] + } + } + + /** + * Converts a CheckoutCompletedEvent to a React Native compatible dictionary. + */ + static func serialize(checkoutCompletedEvent event: CheckoutCompletedEvent) -> [String: Any] { + return encodeToJSON(from: event) + } + + /** + * Converts a PixelEvent to a React Native compatible dictionary. + */ + static func serialize(pixelEvent event: PixelEvent) -> [String: Any] { + switch event { + case let .standardEvent(standardEvent): + let encoded = encodeToJSON(from: standardEvent) + return [ + "context": encoded["context"] ?? NSNull(), + "data": encoded["data"] ?? NSNull(), + "id": encoded["id"] ?? NSNull(), + "name": encoded["name"] ?? NSNull(), + "timestamp": encoded["timestamp"] ?? NSNull(), + "type": "STANDARD" + ] + + case let .customEvent(customEvent): + return [ + "context": encodeToJSON(from: customEvent.context), + "customData": stringToJSON(from: customEvent.customData) ?? NSNull(), + "id": customEvent.id, + "name": customEvent.name, + "timestamp": customEvent.timestamp, + "type": "CUSTOM" + ] + } + } + + static func serialize(clickEvent url: URL) -> [String: URL] { + return ["url": url] + } + + /** + * Converts a CheckoutError to a React Native compatible dictionary. + * Handles all specific error types with proper type information. + */ + static func serialize(checkoutError error: CheckoutError) -> [String: Any] { + switch error { + case let .checkoutExpired(message, code, recoverable): + return [ + "__typename": "CheckoutExpiredError", + "message": message, + "code": code.rawValue, + "recoverable": recoverable + ] + + case let .checkoutUnavailable(message, code, recoverable): + switch code { + case let .clientError(clientErrorCode): + return [ + "__typename": "CheckoutClientError", + "message": message, + "code": clientErrorCode.rawValue, + "recoverable": recoverable + ] + case let .httpError(statusCode): + return [ + "__typename": "CheckoutHTTPError", + "message": message, + "code": "http_error", + "statusCode": statusCode, + "recoverable": recoverable + ] + } + + case let .configurationError(message, code, recoverable): + return [ + "__typename": "ConfigurationError", + "message": message, + "code": code.rawValue, + "recoverable": recoverable + ] + + case let .sdkError(underlying, recoverable): + return [ + "__typename": "InternalError", + "code": "unknown", + "message": underlying.localizedDescription, + "recoverable": recoverable + ] + + @unknown default: + return [ + "__typename": "UnknownError", + "code": "unknown", + "message": error.localizedDescription, + "recoverable": error.isRecoverable + ] + } + } + + /** + * Converts a RenderState enum to a string for React Native. + */ + static func serialize(renderState state: RenderState) -> [String: String] { + switch state { + case .loading: + return ["state": "loading"] + case .rendered: + return ["state": "rendered"] + case let .error(reason): + return ["state": "error", "reason": reason] + @unknown default: + return ["state": "error", "reason": "unknown"] + } + } +} diff --git a/react-native/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit+Extensions.swift b/react-native/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit+Extensions.swift new file mode 100644 index 00000000..5b8a0033 --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit+Extensions.swift @@ -0,0 +1,52 @@ +/* + MIT License + + Copyright 2023 - Present, Shopify Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import UIKit + +// MARK: - UIColor Extensions + +extension UIColor { + convenience init(hex: String) { + let hexString: String = hex.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) + let start = hexString.index(hexString.startIndex, offsetBy: hexString.hasPrefix("#") ? 1 : 0) + let hexColor = String(hexString[start...]) + + let scanner = Scanner(string: hexColor) + var hexNumber: UInt64 = 0 + + if scanner.scanHexInt64(&hexNumber) { + let red = (hexNumber & 0xFF0000) >> 16 + let green = (hexNumber & 0x00FF00) >> 8 + let blue = hexNumber & 0x0000FF + + self.init( + red: CGFloat(red) / 0xFF, + green: CGFloat(green) / 0xFF, + blue: CGFloat(blue) / 0xFF, + alpha: 1 + ) + } else { + self.init(red: 0, green: 0, blue: 0, alpha: 1) + } + } +} diff --git a/react-native/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit-Bridging-Header.h b/react-native/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit-Bridging-Header.h new file mode 100644 index 00000000..3e860d9e --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit-Bridging-Header.h @@ -0,0 +1,28 @@ +/* +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#import +#import +#import +#import +#import diff --git a/react-native/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm b/react-native/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm new file mode 100644 index 00000000..5a3d7017 --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.mm @@ -0,0 +1,154 @@ +/* +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#import +#import + +@interface RCT_EXTERN_MODULE (RCTShopifyCheckoutSheetKit, NSObject) + +/** + * Present checkout + */ +RCT_EXTERN_METHOD(present : (NSString*)checkoutURLString); + +/** + * Preload checkout + */ +RCT_EXTERN_METHOD(preload : (NSString*)checkoutURLString); + +/** + * Dismiss checkout + */ +RCT_EXTERN_METHOD(dismiss); + +/** + * Invalidate preload cache + */ +RCT_EXTERN_METHOD(invalidateCache); + +/** + * Set configuration for checkout + */ +RCT_EXTERN_METHOD(setConfig : (NSDictionary*)configuration); + +/** + * Return configuration for checkout + */ +RCT_EXTERN_METHOD(getConfig : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject) + +/** + * Configure AcceleratedCheckouts + */ +RCT_EXTERN_METHOD(configureAcceleratedCheckouts : (NSString*)storefrontDomain storefrontAccessToken : ( + NSString*)storefrontAccessToken customerEmail : (NSString*)customerEmail customerPhoneNumber : (NSString*) + customerPhoneNumber customerAccessToken : (NSString*)customerAccessToken applePayMerchantIdentifier : (NSString*) + applePayMerchantIdentifier applyPayContactFields : (NSArray*)applyPayContactFields supportedShippingCountries : (NSArray*)supportedShippingCountries resolve : ( + RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject); + +/** + * Check if accelerated checkout is available + */ +RCT_EXTERN_METHOD( + isAcceleratedCheckoutAvailable : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject); + +/** + * Check if Apple Pay is available + */ +RCT_EXTERN_METHOD(isApplePayAvailable : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject); + +@end + +/** + * AcceleratedCheckoutButtons View Manager + */ +@interface RCT_EXTERN_MODULE (RCTAcceleratedCheckoutButtonsManager, RCTViewManager) + +/** + * Unified checkout identifier payload. + * Accepts either { cartId } or { variantId, quantity }. + */ +RCT_EXPORT_VIEW_PROPERTY(checkoutIdentifier, NSDictionary*) + +/** + * Corner radius for rendered buttons, in points. Defaults to 8. + */ +RCT_EXPORT_VIEW_PROPERTY(cornerRadius, NSNumber*) + +/** + * Wallets to render. Accepts an array of identifiers such as "shopPay" and "applePay". + * If omitted, native defaults are used. + */ +RCT_EXPORT_VIEW_PROPERTY(wallets, NSArray*) + +/** + * Label variant for the Apple Pay button (e.g., "plain", "buy", "checkout"). + */ +RCT_EXPORT_VIEW_PROPERTY(applePayLabel, NSString*) + +/** + * Style variant for the Apple Pay button (e.g., "automatic", "black", "white", "whiteOutline"). + */ +RCT_EXPORT_VIEW_PROPERTY(applePayStyle, NSString*) + +/** + * Emitted when checkout fails. Payload contains a CheckoutException-like shape. + */ +RCT_EXPORT_VIEW_PROPERTY(onFail, RCTBubblingEventBlock) + +/** + * Emitted when checkout completes successfully. Payload contains order details. + */ +RCT_EXPORT_VIEW_PROPERTY(onComplete, RCTBubblingEventBlock) + +/** + * Emitted when checkout is cancelled by the buyer. + */ +RCT_EXPORT_VIEW_PROPERTY(onCancel, RCTBubblingEventBlock) + +/** + * Emitted when the native render state changes. Values: "loading", "rendered", "error". + */ +RCT_EXPORT_VIEW_PROPERTY(onRenderStateChange, RCTBubblingEventBlock) + +/** + * Direct event used to determine whether native should attempt recovery from an error. + */ +RCT_EXPORT_VIEW_PROPERTY(onShouldRecoverFromError, RCTDirectEventBlock) + +/** + * Emitted when a web pixel event occurs during checkout. + */ +RCT_EXPORT_VIEW_PROPERTY(onWebPixelEvent, RCTBubblingEventBlock) + +/** + * Emitted when a link is clicked within the checkout experience. Payload contains the URL. + */ +RCT_EXPORT_VIEW_PROPERTY(onClickLink, RCTBubblingEventBlock) + +/** + * Emitted when the intrinsic height of the native view changes. Payload contains { height }. + */ +RCT_EXPORT_VIEW_PROPERTY(onSizeChange, RCTDirectEventBlock) + +@end diff --git a/react-native/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.swift b/react-native/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.swift new file mode 100644 index 00000000..25425e7c --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/ios/ShopifyCheckoutSheetKit.swift @@ -0,0 +1,329 @@ +/* + MIT License + + Copyright 2023 - Present, Shopify Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import Foundation +import PassKit +import React +import ShopifyCheckoutSheetKit +import SwiftUI +import UIKit + +@objc(RCTShopifyCheckoutSheetKit) +class RCTShopifyCheckoutSheetKit: RCTEventEmitter, CheckoutDelegate { + private var hasListeners = false + + internal var checkoutSheet: UIViewController? + private var acceleratedCheckoutsConfiguration: Any? + private var acceleratedCheckoutsApplePayConfiguration: Any? + private var defaultLogLevel: LogLevel = .error + + override var methodQueue: DispatchQueue! { + return DispatchQueue.main + } + + @objc override static func requiresMainQueueSetup() -> Bool { + return true + } + + override init() { + ShopifyCheckoutSheetKit.configure { + $0.platform = ShopifyCheckoutSheetKit.Platform.reactNative + } + + super.init() + } + + override func supportedEvents() -> [String]! { + return ["close", "completed", "error", "pixel"] + } + + override func startObserving() { + hasListeners = true + } + + override func stopObserving() { + hasListeners = false + } + + func checkoutDidComplete(event: CheckoutCompletedEvent) { + if hasListeners { + sendEvent(withName: "completed", body: ShopifyEventSerialization.serialize(checkoutCompletedEvent: event)) + } + } + + func shouldRecoverFromError(error: CheckoutError) -> Bool { + return error.isRecoverable + } + + func checkoutDidFail(error: ShopifyCheckoutSheetKit.CheckoutError) { + guard hasListeners else { return } + + sendEvent(withName: "error", body: ShopifyEventSerialization.serialize(checkoutError: error)) + } + + func checkoutDidEmitWebPixelEvent(event: ShopifyCheckoutSheetKit.PixelEvent) { + if hasListeners { + sendEvent(withName: "pixel", body: ShopifyEventSerialization.serialize(pixelEvent: event)) + } + } + + func checkoutDidCancel() { + DispatchQueue.main.async { + if self.hasListeners { + self.sendEvent(withName: "close", body: nil) + } + + self.checkoutSheet?.dismiss(animated: true) + } + } + + @objc override func constantsToExport() -> [AnyHashable: Any]! { + return [ + "version": ShopifyCheckoutSheetKit.version + ] + } + + static func getRootViewController() -> UIViewController? { + return ( + UIApplication.shared.connectedScenes + .first(where: { $0.activationState == .foregroundActive }) as? UIWindowScene + )?.windows + .first(where: { $0.isKeyWindow })?.rootViewController + } + + func getCurrentViewController(_ controller: UIViewController? = getRootViewController()) -> UIViewController? { + if let presentedViewController = controller?.presentedViewController { + return getCurrentViewController(presentedViewController) + } + + if let navigationController = controller as? UINavigationController { + return getCurrentViewController(navigationController.visibleViewController) + } + + if let tabBarController = controller as? UITabBarController { + if let selectedViewController = tabBarController.selectedViewController { + return getCurrentViewController(selectedViewController) + } + } + + return controller + } + + @objc func dismiss() { + DispatchQueue.main.async { + self.checkoutSheet?.dismiss(animated: true) + } + } + + @objc func invalidateCache() { + ShopifyCheckoutSheetKit.invalidate() + } + + @objc func present(_ checkoutURL: String) { + DispatchQueue.main.async { + if let url = URL(string: checkoutURL), let viewController = self.getCurrentViewController() { + let view = CheckoutViewController(checkout: url, delegate: self) + viewController.present(view, animated: true) + self.checkoutSheet = view + } + } + } + + @objc func preload(_ checkoutURL: String) { + DispatchQueue.main.async { + if let url = URL(string: checkoutURL) { + ShopifyCheckoutSheetKit.preload(checkout: url) + } + } + } + + private func getColorScheme(_ colorScheme: String) -> ShopifyCheckoutSheetKit.Configuration.ColorScheme { + switch colorScheme { + case "web_default": + return ShopifyCheckoutSheetKit.Configuration.ColorScheme.web + case "automatic": + return ShopifyCheckoutSheetKit.Configuration.ColorScheme.automatic + case "light": + return ShopifyCheckoutSheetKit.Configuration.ColorScheme.light + case "dark": + return ShopifyCheckoutSheetKit.Configuration.ColorScheme.dark + default: + return ShopifyCheckoutSheetKit.Configuration.ColorScheme.automatic + } + } + + @objc func setConfig(_ configuration: [AnyHashable: Any]) { + let colorConfig = configuration["colors"] as? [AnyHashable: Any] + let iosConfig = colorConfig?["ios"] as? [String: String] + + if let title = configuration["title"] as? String { + ShopifyCheckoutSheetKit.configuration.title = title + } + + if let preloading = configuration["preloading"] as? Bool { + ShopifyCheckoutSheetKit.configuration.preloading.enabled = preloading + } + + if let colorScheme = configuration["colorScheme"] as? String { + ShopifyCheckoutSheetKit.configuration.colorScheme = getColorScheme(colorScheme) + } + + if let tintColorHex = iosConfig?["tintColor"] as? String { + ShopifyCheckoutSheetKit.configuration.tintColor = UIColor(hex: tintColorHex) + } + + if let backgroundColorHex = iosConfig?["backgroundColor"] as? String { + ShopifyCheckoutSheetKit.configuration.backgroundColor = UIColor(hex: backgroundColorHex) + } + + if let closeButtonColorHex = iosConfig?["closeButtonColor"] as? String { + ShopifyCheckoutSheetKit.configuration.closeButtonTintColor = UIColor(hex: closeButtonColorHex) + } + + if let logLevel = configuration["logLevel"] as? String { + ShopifyCheckoutSheetKit.configuration.logLevel = ShopifyCheckoutSheetKit.LogLevel(rawValue: logLevel.lowercased()) ?? defaultLogLevel + } else { + ShopifyCheckoutSheetKit.configuration.logLevel = defaultLogLevel + } + + NotificationCenter.default.post(name: Notification.Name("CheckoutKitConfigurationUpdated"), object: nil) + } + + @objc func getConfig(_ resolve: @escaping RCTPromiseResolveBlock, reject _: @escaping RCTPromiseRejectBlock) { + let config: [String: Any] = [ + "title": ShopifyCheckoutSheetKit.configuration.title, + "preloading": ShopifyCheckoutSheetKit.configuration.preloading.enabled, + "colorScheme": ShopifyCheckoutSheetKit.configuration.colorScheme.rawValue, + "tintColor": ShopifyCheckoutSheetKit.configuration.tintColor, + "backgroundColor": ShopifyCheckoutSheetKit.configuration.backgroundColor, + "closeButtonColor": ShopifyCheckoutSheetKit.configuration.closeButtonTintColor, + "logLevel": logLevelToString(ShopifyCheckoutSheetKit.configuration.logLevel) + ] + + resolve(config) + } + + @objc func configureAcceleratedCheckouts( + _ storefrontDomain: String, + storefrontAccessToken: String, + customerEmail: String?, + customerPhoneNumber: String?, + customerAccessToken: String?, + applePayMerchantIdentifier: String?, + applyPayContactFields: [String]?, + supportedShippingCountries: [String]?, + resolve: @escaping RCTPromiseResolveBlock, + reject _: @escaping RCTPromiseRejectBlock + ) { + guard #available(iOS 16.0, *) else { + resolve(false) + return + } + + let customer = ShopifyAcceleratedCheckouts.Customer( + email: customerEmail, + phoneNumber: customerPhoneNumber, + customerAccessToken: customerAccessToken + ) + + acceleratedCheckoutsConfiguration = ShopifyAcceleratedCheckouts.Configuration( + storefrontDomain: storefrontDomain, + storefrontAccessToken: storefrontAccessToken, + customer: customer + ) + + if let merchantIdentifier = applePayMerchantIdentifier, let contactFields = applyPayContactFields { + do { + let fields = try contactFieldsToRequiredContactFields(contactFields) + + acceleratedCheckoutsApplePayConfiguration = ShopifyAcceleratedCheckouts.ApplePayConfiguration( + merchantIdentifier: merchantIdentifier, + contactFields: fields, + supportedShippingCountries: Set(supportedShippingCountries ?? []) + ) + + AcceleratedCheckoutConfiguration.shared.applePayConfiguration = acceleratedCheckoutsApplePayConfiguration as? ShopifyAcceleratedCheckouts.ApplePayConfiguration + } catch { + resolve(false) + return + } + } + + AcceleratedCheckoutConfiguration.shared.configuration = acceleratedCheckoutsConfiguration as? ShopifyAcceleratedCheckouts.Configuration + + NotificationCenter.default.post(name: Notification.Name("AcceleratedCheckoutConfigurationUpdated"), object: nil) + + resolve(true) + } + + @objc func isAcceleratedCheckoutAvailable( + _ resolve: @escaping RCTPromiseResolveBlock, + reject _: @escaping RCTPromiseRejectBlock + ) { + guard #available(iOS 16.0, *) else { + resolve(false) + return + } + + resolve(AcceleratedCheckoutConfiguration.shared.available) + } + + @objc func isApplePayAvailable( + _ resolve: @escaping RCTPromiseResolveBlock, + reject _: @escaping RCTPromiseRejectBlock + ) { + guard #available(iOS 16.0, *) else { + resolve(false) + return + } + + let available = AcceleratedCheckoutConfiguration.shared.available && AcceleratedCheckoutConfiguration.shared.applePayAvailable + + resolve(available) + } + + // MARK: - Private + + @available(iOS 16.0, *) + private func contactFieldsToRequiredContactFields(_ contactFields: [String]) throws -> [ShopifyAcceleratedCheckouts.RequiredContactFields] { + return try contactFields.compactMap { + guard let field = ShopifyAcceleratedCheckouts.RequiredContactFields(rawValue: $0), field != nil else { + let message = "Unknown contactField option: \(String(describing: $0))" + print("[ShopifyCheckoutSheetKit] \(message)") + throw NSError(domain: "ShopifyCheckoutSheetKit", code: 1, userInfo: ["message": message]) + } + return field + } + } + + private func logLevelToString(_ logLevel: LogLevel) -> String { + switch logLevel { + case .all, .debug: + return "debug" + case .error: + return "error" + default: + return "error" + } + } +} diff --git a/react-native/modules/@shopify/checkout-sheet-kit/package.json b/react-native/modules/@shopify/checkout-sheet-kit/package.json new file mode 100644 index 00000000..76ebaa87 --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/package.json @@ -0,0 +1,79 @@ +{ + "name": "@shopify/checkout-sheet-kit", + "license": "MIT", + "version": "3.8.0", + "main": "lib/commonjs/index.js", + "types": "src/index.ts", + "source": "src/index.ts", + "module": "lib/module/index.js", + "description": "A React Native library for Shopify's Checkout Kit.", + "author": "Shopify", + "homepage": "https://github.com/shopify/checkout-sheet-kit-react-native", + "repository": { + "type": "git", + "url": "https://github.com/Shopify/checkout-sheet-kit-react-native" + }, + "publishConfig": { + "registry": "https://registry.npmjs.org/" + }, + "scripts": { + "clean": "rm -rf lib", + "build": "bob build", + "lint": "pnpm run typecheck && eslint src", + "typecheck": "tsc --noEmit" + }, + "files": [ + "LICENSE", + "README.md", + "package.json", + "src", + "ios", + "android", + "lib", + "*.podspec", + "!ios/build", + "!android/build", + "!android/gradle", + "!android/gradlew", + "!android/gradlew.bat", + "!android/local.properties", + "!**/*.spec.*", + "!**/*.test.*", + "!**/.*" + ], + "keywords": [ + "react-native", + "shopify", + "checkout" + ], + "peerDependencies": { + "react": "*", + "react-native": "*" + }, + "devDependencies": { + "react-native-builder-bob": "^0.23.2", + "typescript": "^5.9.2" + }, + "codegenConfig": { + "name": "RNShopifyCheckoutSheetKitSpec", + "type": "all", + "jsSrcsDir": "src/specs", + "android": { + "javaPackageName": "com.shopify.checkoutsheetkit" + } + }, + "react-native-builder-bob": { + "source": "src", + "output": "lib", + "targets": [ + "commonjs", + "module", + [ + "typescript", + { + "project": "tsconfig.build.json" + } + ] + ] + } +} diff --git a/react-native/modules/@shopify/checkout-sheet-kit/package.snapshot.json b/react-native/modules/@shopify/checkout-sheet-kit/package.snapshot.json new file mode 100644 index 00000000..8cddcc9f --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/package.snapshot.json @@ -0,0 +1,75 @@ +[ + "LICENSE", + "RNShopifyCheckoutSheetKit.podspec", + "android/build.gradle", + "android/gradle.properties", + "android/proguard-rules.pro", + "android/src/main/AndroidManifest.xml", + "android/src/main/AndroidManifestNew.xml", + "android/src/main/java/com/shopify/reactnative/checkoutsheetkit/CustomCheckoutEventProcessor.java", + "android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitModule.java", + "android/src/main/java/com/shopify/reactnative/checkoutsheetkit/ShopifyCheckoutSheetKitPackage.java", + "ios/AcceleratedCheckoutButtons.swift", + "ios/AcceleratedCheckoutButtons+Extensions.swift", + "ios/ShopifyCheckoutSheetKit-Bridging-Header.h", + "ios/ShopifyCheckoutSheetKit.mm", + "ios/ShopifyCheckoutSheetKit.swift", + "ios/ShopifyCheckoutSheetKit+EventSerialization.swift", + "ios/ShopifyCheckoutSheetKit+Extensions.swift", + "lib/commonjs/components/AcceleratedCheckoutButtons.js", + "lib/commonjs/components/AcceleratedCheckoutButtons.js.map", + "lib/commonjs/context.js", + "lib/commonjs/context.js.map", + "lib/commonjs/errors.d.js", + "lib/commonjs/errors.d.js.map", + "lib/commonjs/events.d.js", + "lib/commonjs/events.d.js.map", + "lib/commonjs/index.d.js", + "lib/commonjs/index.d.js.map", + "lib/commonjs/index.js", + "lib/commonjs/index.js.map", + "lib/commonjs/pixels.d.js", + "lib/commonjs/pixels.d.js.map", + "lib/commonjs/specs/NativeShopifyCheckoutSheetKit.js", + "lib/commonjs/specs/NativeShopifyCheckoutSheetKit.js.map", + "lib/commonjs/specs/RCTAcceleratedCheckoutButtonsNativeComponent.js", + "lib/commonjs/specs/RCTAcceleratedCheckoutButtonsNativeComponent.js.map", + "lib/module/components/AcceleratedCheckoutButtons.js", + "lib/module/components/AcceleratedCheckoutButtons.js.map", + "lib/module/context.js", + "lib/module/context.js.map", + "lib/module/errors.d.js", + "lib/module/errors.d.js.map", + "lib/module/events.d.js", + "lib/module/events.d.js.map", + "lib/module/index.d.js", + "lib/module/index.d.js.map", + "lib/module/index.js", + "lib/module/index.js.map", + "lib/module/pixels.d.js", + "lib/module/pixels.d.js.map", + "lib/module/specs/NativeShopifyCheckoutSheetKit.js", + "lib/module/specs/NativeShopifyCheckoutSheetKit.js.map", + "lib/module/specs/RCTAcceleratedCheckoutButtonsNativeComponent.js", + "lib/module/specs/RCTAcceleratedCheckoutButtonsNativeComponent.js.map", + "lib/typescript/src/components/AcceleratedCheckoutButtons.d.ts", + "lib/typescript/src/components/AcceleratedCheckoutButtons.d.ts.map", + "lib/typescript/src/context.d.ts", + "lib/typescript/src/context.d.ts.map", + "lib/typescript/src/index.d.ts", + "lib/typescript/src/index.d.ts.map", + "lib/typescript/src/specs/NativeShopifyCheckoutSheetKit.d.ts", + "lib/typescript/src/specs/NativeShopifyCheckoutSheetKit.d.ts.map", + "lib/typescript/src/specs/RCTAcceleratedCheckoutButtonsNativeComponent.d.ts", + "lib/typescript/src/specs/RCTAcceleratedCheckoutButtonsNativeComponent.d.ts.map", + "package.json", + "src/components/AcceleratedCheckoutButtons.tsx", + "src/context.tsx", + "src/errors.d.ts", + "src/events.d.ts", + "src/index.d.ts", + "src/index.ts", + "src/pixels.d.ts", + "src/specs/NativeShopifyCheckoutSheetKit.ts", + "src/specs/RCTAcceleratedCheckoutButtonsNativeComponent.ts" +] diff --git a/react-native/modules/@shopify/checkout-sheet-kit/src/components/AcceleratedCheckoutButtons.tsx b/react-native/modules/@shopify/checkout-sheet-kit/src/components/AcceleratedCheckoutButtons.tsx new file mode 100644 index 00000000..c52ad100 --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/src/components/AcceleratedCheckoutButtons.tsx @@ -0,0 +1,370 @@ +/* +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import React, {useCallback, useMemo, useState} from 'react'; +import {codegenNativeComponent, Platform} from 'react-native'; +import type {ViewStyle} from 'react-native'; +import type { + AcceleratedCheckoutWallet, + CheckoutCompletedEvent, + CheckoutException, + PixelEvent, +} from '..'; + +export enum RenderState { + Loading = 'loading', + Rendered = 'rendered', + Error = 'error', +} + +export type RenderStateChangeEvent = + | {state: RenderState.Error; reason?: string} + | {state: Omit}; + +export enum ApplePayLabel { + addMoney = 'addMoney', + book = 'book', + buy = 'buy', + checkout = 'checkout', + continue = 'continue', + contribute = 'contribute', + donate = 'donate', + inStore = 'inStore', + order = 'order', + plain = 'plain', + reload = 'reload', + rent = 'rent', + setUp = 'setUp', + subscribe = 'subscribe', + support = 'support', + tip = 'tip', + topUp = 'topUp', +} + +export enum ApplePayStyle { + automatic = 'automatic', + black = 'black', + white = 'white', + whiteOutline = 'whiteOutline', +} + +type CheckoutIdentifier = + | { + cartId: string; + } + | { + variantId: string; + quantity: number; + }; + +interface CommonAcceleratedCheckoutButtonsProps { + /** + * Corner radius for the button (defaults to 8) + */ + cornerRadius?: number; + + /** + * Wallets to display in the button + * Defaults to both shopPay and applePay if not specified + */ + wallets?: AcceleratedCheckoutWallet[]; + + /** + * Label for the Apple Pay button + */ + applePayLabel?: ApplePayLabel; + + /** + * Style for the Apple Pay button color + * Defaults to 'automatic' which adapts to the current appearance (light/dark mode) + */ + applePayStyle?: ApplePayStyle; + + /** + * Called when checkout fails + */ + onFail?: (error: CheckoutException) => void; + + /** + * Called when checkout is completed successfully + */ + onComplete?: (event: CheckoutCompletedEvent) => void; + + /** + * Called when checkout is cancelled + */ + onCancel?: () => void; + + /** + * Called when the render state changes + * States from SDK: loading, rendered, error + */ + onRenderStateChange?: (event: RenderStateChangeEvent) => void; + + /** + * Called when a web pixel event is triggered + */ + onWebPixelEvent?: (event: PixelEvent) => void; + + /** + * Called when a link is clicked within the checkout + */ + onClickLink?: (url: string) => void; + + /** + * Called when the size of the button changes + */ + onSizeChange?: (event: {nativeEvent: {height: number}}) => void; +} + +interface CartProps { + /** + * The cart ID for cart-based checkout + */ + cartId: string; +} + +interface VariantProps { + /** + * The variant ID for product-based checkout + */ + variantId: string; + + /** + * The quantity for product-based checkout + */ + quantity: number; +} + +export type AcceleratedCheckoutButtonsProps = (CartProps | VariantProps) & + CommonAcceleratedCheckoutButtonsProps; + +interface NativeAcceleratedCheckoutButtonsProps { + applePayLabel?: string; + applePayStyle?: string; + style?: ViewStyle; + checkoutIdentifier: CheckoutIdentifier; + cornerRadius?: number; + wallets?: AcceleratedCheckoutWallet[]; + onFail?: (event: {nativeEvent: CheckoutException}) => void; + onComplete?: (event: {nativeEvent: CheckoutCompletedEvent}) => void; + onCancel?: () => void; + onRenderStateChange?: (event: { + nativeEvent: {state: string; reason?: string | undefined}; + }) => void; + onWebPixelEvent?: (event: {nativeEvent: PixelEvent}) => void; + onClickLink?: (event: {nativeEvent: {url: string}}) => void; + onSizeChange?: (event: {nativeEvent: {height: number}}) => void; +} + +const RCTAcceleratedCheckoutButtons = + codegenNativeComponent( + 'RCTAcceleratedCheckoutButtons', + ); + +/** + * AcceleratedCheckoutButton provides pre-built payment UI components for Shop Pay and Apple Pay. + * It enables faster checkout with fewer steps and supports both cart and product page checkout. + * + * @example Cart-based checkout + * console.log('Checkout completed!', event.orderDetails)} + * onFail={(error) => console.error('Checkout failed:', error.message)} + * /> + * + * @example Product-based checkout + * console.log('Checkout completed!', event.orderDetails)} + * /> + */ + +const defaultStyles = {flex: 1}; + +export const AcceleratedCheckoutButtons: React.FC< + AcceleratedCheckoutButtonsProps +> = ({ + applePayLabel, + applePayStyle, + cornerRadius, + wallets, + onFail, + onComplete, + onCancel, + onRenderStateChange, + onWebPixelEvent, + onClickLink, + ...props +}) => { + const isCart = isCartProps(props); + const isVariant = isVariantProps(props); + const [dynamicHeight, setDynamicHeight] = useState( + undefined, + ); + + const handleFail = useCallback( + (event: {nativeEvent: CheckoutException}) => { + onFail?.(event.nativeEvent); + }, + [onFail], + ); + + const handleComplete = useCallback( + (event: {nativeEvent: CheckoutCompletedEvent}) => { + onComplete?.(event.nativeEvent); + }, + [onComplete], + ); + + const handleCancel = useCallback(() => { + onCancel?.(); + }, [onCancel]); + + const handleRenderStateChange = useCallback( + (event: {nativeEvent: {state: string; reason?: string | undefined}}) => { + const state = validRenderState(event.nativeEvent.state); + const reason = event.nativeEvent.reason; + + if (state === RenderState.Error) { + onRenderStateChange?.({state, reason}); + } else { + onRenderStateChange?.({state}); + } + }, + [onRenderStateChange], + ); + + const handleWebPixelEvent = useCallback( + (event: {nativeEvent: PixelEvent}) => { + onWebPixelEvent?.(event.nativeEvent); + }, + [onWebPixelEvent], + ); + + const handleClickLink = useCallback( + (event: {nativeEvent: {url: string}}) => { + if (event.nativeEvent?.url) { + onClickLink?.(event.nativeEvent.url); + } + }, + [onClickLink], + ); + + const handleSizeChange = useCallback( + (event: {nativeEvent: {height: number}}) => { + setDynamicHeight(event.nativeEvent.height); + }, + [], + ); + + const checkoutIdentifier: CheckoutIdentifier | undefined = useMemo(() => { + switch (true) { + case isCart: + return {cartId: props.cartId}; + case isVariant: + return {variantId: props.variantId, quantity: props.quantity}; + default: + return undefined; + } + }, [isCart, isVariant, props]); + + // Only render on iOS for now since ShopifyAcceleratedCheckouts is iOS-only + if (Platform.OS !== 'ios' || parseInt(Platform.Version, 10) < 16) { + return null; + } + + if (!checkoutIdentifier) { + /** + * @todo + * + * The ShopifyAcceleratedCheckouts module will handle this error by returning an empty view over the bridge + * to the javascript client. + * + * The onRenderStateChange event will be invoked with both an error state and a reason to indicate the error, at + * which point this error handling can be removed. + * + */ + + const error = new Error( + 'AcceleratedCheckoutButton: Either `cartId` or `variantId` and `quantity` must be provided', + ); + if (__DEV__) { + throw error; + } else { + // eslint-disable-next-line no-console + console.warn(error.message); + return null; + } + } + + return ( + + ); +}; + +export default AcceleratedCheckoutButtons; + +function validRenderState(state: string): RenderState { + switch (state) { + case RenderState.Loading: + return RenderState.Loading; + case RenderState.Rendered: + return RenderState.Rendered; + case RenderState.Error: + return RenderState.Error; + default: + // eslint-disable-next-line no-console + console.error( + `[ShopifyAcceleratedCheckouts] Invalid render state: ${state}`, + ); + return RenderState.Error; + } +} + +function isCartProps( + props: AcceleratedCheckoutButtonsProps, +): props is CartProps { + return 'cartId' in props; +} + +function isVariantProps( + props: AcceleratedCheckoutButtonsProps, +): props is VariantProps { + return 'variantId' in props && 'quantity' in props && props.quantity > 0; +} diff --git a/react-native/modules/@shopify/checkout-sheet-kit/src/context.tsx b/react-native/modules/@shopify/checkout-sheet-kit/src/context.tsx new file mode 100644 index 00000000..daf0c627 --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/src/context.tsx @@ -0,0 +1,179 @@ +/* +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import React, {useCallback, useMemo, useRef, useEffect, useState} from 'react'; +import type {PropsWithChildren} from 'react'; +import {type EmitterSubscription} from 'react-native'; +import {ShopifyCheckoutSheet} from './index'; +import type {Features} from './index.d'; +import type { + AddEventListener, + RemoveEventListeners, + CheckoutEvent, + Configuration, +} from './index.d'; + +type Maybe = T | undefined; + +interface Context { + acceleratedCheckoutsAvailable: boolean; + addEventListener: AddEventListener; + getConfig: () => Promise; + setConfig: (config: Configuration) => Promise; + removeEventListeners: RemoveEventListeners; + preload: (checkoutUrl: string) => void; + present: (checkoutUrl: string) => void; + dismiss: () => void; + invalidate: () => void; + version: Maybe; +} + +const ShopifyCheckoutSheetContext = React.createContext( + null as unknown as Context, +); + +interface Props { + features?: Partial; + configuration?: Configuration; +} + +export function ShopifyCheckoutSheetProvider({ + features, + configuration, + children, +}: PropsWithChildren) { + const [acceleratedCheckoutsAvailable, setAcceleratedCheckoutsAvailable] = + useState(false); + const instance = useRef(null); + + if (!instance.current) { + instance.current = new ShopifyCheckoutSheet(configuration, features); + } + + useEffect(() => { + async function configureCheckoutKit() { + if (!instance.current || !configuration) { + return; + } + + const customer = configuration.acceleratedCheckouts?.customer; + if (customer?.accessToken && (customer?.email || customer?.phoneNumber)) { + // eslint-disable-next-line no-console + console.warn( + '[ShopifyCheckoutSheetKit] Providing accessToken with contactFields (email / phoneNumber) is deprecated and will become an error in v4.' + + 'When the user is authenticated with Customer Accounts, provide accessToken' + + 'When the user is otherwise authenticated, provide email/phoneNumber.', + ); + } + + await instance.current?.setConfig(configuration); + setAcceleratedCheckoutsAvailable( + instance.current.acceleratedCheckoutsReady, + ); + } + + configureCheckoutKit(); + }, [configuration]); + + const addEventListener: AddEventListener = useCallback( + (eventName, callback): EmitterSubscription | undefined => { + return instance.current?.addEventListener(eventName, callback); + }, + [], + ); + + const removeEventListeners = useCallback((eventName: CheckoutEvent) => { + instance.current?.removeEventListeners(eventName); + }, []); + + const present = useCallback((checkoutUrl: string) => { + if (checkoutUrl) { + instance.current?.present(checkoutUrl); + } + }, []); + + const preload = useCallback((checkoutUrl: string) => { + if (checkoutUrl) { + instance.current?.preload(checkoutUrl); + } + }, []); + + const invalidate = useCallback(() => { + instance.current?.invalidate(); + }, []); + + const dismiss = useCallback(() => { + instance.current?.dismiss(); + }, []); + + const setConfig = useCallback(async (config: Configuration) => { + await instance.current?.setConfig(config); + }, []); + + const getConfig = useCallback(async () => { + return instance.current?.getConfig(); + }, []); + + const context = useMemo((): Context => { + return { + acceleratedCheckoutsAvailable, + addEventListener, + dismiss, + setConfig, + getConfig, + preload, + present, + invalidate, + removeEventListeners, + version: instance.current?.version, + }; + }, [ + acceleratedCheckoutsAvailable, + addEventListener, + dismiss, + removeEventListeners, + getConfig, + setConfig, + preload, + present, + invalidate, + ]); + + return ( + + {children} + + ); +} + +export function useShopifyCheckoutSheet() { + const context = React.useContext(ShopifyCheckoutSheetContext); + if (!context) { + throw new Error( + 'useShopifyCheckoutSheet must be used from within a ShopifyCheckoutSheetContext', + ); + } + return context; +} + +export default ShopifyCheckoutSheetContext; diff --git a/react-native/modules/@shopify/checkout-sheet-kit/src/errors.d.ts b/react-native/modules/@shopify/checkout-sheet-kit/src/errors.d.ts new file mode 100644 index 00000000..faff876b --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/src/errors.d.ts @@ -0,0 +1,132 @@ +/* +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +export enum CheckoutErrorCode { + storefrontPasswordRequired = 'storefront_password_required', + cartExpired = 'cart_expired', + cartCompleted = 'cart_completed', + invalidCart = 'invalid_cart', + clientError = 'client_error', + httpError = 'http_error', + sendingBridgeEventError = 'error_sending_message', + receivingBridgeEventError = 'error_receiving_message', + renderProcessGone = 'render_process_gone', + unknown = 'unknown', +} + +export enum CheckoutNativeErrorType { + InternalError = 'InternalError', + ConfigurationError = 'ConfigurationError', + CheckoutClientError = 'CheckoutClientError', + CheckoutHTTPError = 'CheckoutHTTPError', + CheckoutExpiredError = 'CheckoutExpiredError', + UnknownError = 'UnknownError', +} + +function getCheckoutErrorCode(code: string | undefined): CheckoutErrorCode { + const codeKey = Object.keys(CheckoutErrorCode).find( + key => CheckoutErrorCode[key as keyof typeof CheckoutErrorCode] === code, + ); + + return codeKey ? CheckoutErrorCode[codeKey] : CheckoutErrorCode.unknown; +} + +type BridgeError = { + __typename: CheckoutNativeErrorType; + code: CheckoutErrorCode; + message: string; + recoverable: boolean; +}; + +export type CheckoutNativeError = + | BridgeError + | (BridgeError & {statusCode: number}); + +class GenericErrorWithCode { + message: string; + recoverable: boolean; + code: CheckoutErrorCode; + + constructor(exception: CheckoutNativeError) { + this.code = getCheckoutErrorCode(exception.code); + this.message = exception.message; + this.recoverable = exception.recoverable; + this.name = this.constructor.name; + } +} + +class GenericNetworkError { + code: CheckoutErrorCode; + message: string; + recoverable: boolean; + statusCode: number; + + constructor(exception: CheckoutNativeError) { + this.code = getCheckoutErrorCode(exception.code); + this.statusCode = exception.statusCode; + this.message = exception.message; + this.recoverable = exception.recoverable; + this.name = this.constructor.name; + } +} + +export class ConfigurationError extends GenericErrorWithCode {} +export class CheckoutClientError extends GenericErrorWithCode {} +export class CheckoutExpiredError extends GenericErrorWithCode {} +export class CheckoutHTTPError extends GenericNetworkError {} + +export class GenericError { + code: CheckoutErrorCode; + message?: string; + recoverable: boolean; + statusCode?: number; + name: string; + + constructor(exception?: CheckoutNativeError) { + this.code = getCheckoutErrorCode(exception?.code); + this.message = exception?.message; + this.name = this.constructor.name; + this.recoverable = exception?.recoverable ?? false; + this.statusCode = exception?.statusCode; + } +} + +export class InternalError { + code: CheckoutErrorCode; + message: string; + recoverable: boolean; + + constructor(exception: CheckoutNativeError) { + this.code = getCheckoutErrorCode(exception.code); + this.message = exception.message; + this.recoverable = exception.recoverable; + } +} + +export type CheckoutException = + | CheckoutClientError + | CheckoutExpiredError + | CheckoutHTTPError + | ConfigurationError + | GenericError + | InternalError; diff --git a/react-native/modules/@shopify/checkout-sheet-kit/src/events.d.ts b/react-native/modules/@shopify/checkout-sheet-kit/src/events.d.ts new file mode 100644 index 00000000..325596d4 --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/src/events.d.ts @@ -0,0 +1,112 @@ +/* +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +namespace CheckoutCompletedEvent { + interface Address { + address1?: string; + address2?: string; + city?: string; + countryCode?: string; + firstName?: string; + lastName?: string; + name?: string; + phone?: string; + postalCode?: string; + referenceId?: string; + zoneCode?: string; + } + + interface CartInfo { + lines: CartLine[]; + price: Price; + token: string; + } + + interface CartLineImage { + altText?: string; + lg: string; + md: string; + sm: string; + } + + interface CartLine { + discounts?: Discount[]; + image?: CartLineImage; + merchandiseId?: string; + price: Money; + productId?: string; + quantity: number; + title: string; + } + + interface DeliveryDetails { + additionalInfo?: string; + location?: Address; + name?: string; + } + + interface DeliveryInfo { + details: DeliveryDetails; + method: string; + } + + interface Discount { + amount?: Money; + applicationType?: string; + title?: string; + value?: number; + valueType?: string; + } + + export interface OrderDetails { + billingAddress?: Address; + cart: CartInfo; + deliveries?: DeliveryInfo[]; + email?: string; + id: string; + paymentMethods?: PaymentMethod[]; + phone?: string; + } + + interface PaymentMethod { + details: {[key: string]: string | null}; + type: string; + } + + interface Price { + discounts?: Discount[]; + shipping?: Money; + subtotal?: Money; + taxes?: Money; + total?: Money; + } + + interface Money { + amount?: number; + currencyCode?: string; + } +} + +export interface CheckoutCompletedEvent { + orderDetails: CheckoutCompletedEvent.OrderDetails; +} diff --git a/react-native/modules/@shopify/checkout-sheet-kit/src/index.d.ts b/react-native/modules/@shopify/checkout-sheet-kit/src/index.d.ts new file mode 100644 index 00000000..34147dad --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/src/index.d.ts @@ -0,0 +1,353 @@ +/* +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import type {EmitterSubscription} from 'react-native'; +import type {PixelEvent} from './pixels'; +import type {CheckoutCompletedEvent} from './events'; +import type {CheckoutException} from './errors'; + +export type Maybe = T | undefined; + +/** + * Configuration options for checkout sheet kit features + */ +export interface Features { + /** + * When enabled, the checkout will handle geolocation permission requests internally. + * If disabled, geolocation requests will emit a 'geolocationRequest' event that + * must be handled by the application. + */ + handleGeolocationRequests: boolean; +} + +export enum ColorScheme { + automatic = 'automatic', + light = 'light', + dark = 'dark', + web = 'web_default', +} + +/** + * Log level for the checkout sheet kit. + * Controls the verbosity of logs emitted by the native SDK. + * @defaults to error + */ +export enum LogLevel { + /** + * Show debug logs. + */ + debug = 'debug', + /** + * Show only error logs. + */ + error = 'error', +} + +export interface IosColors { + /** + * A HEX color value for customizing the color of the progress bar. + */ + tintColor?: string; + /** + * A HEX color value for customizing the background color of the webview. + */ + backgroundColor?: string; + /** + * A HEX color value for customizing the color of the close button. + */ + closeButtonColor?: string; +} + +export interface AndroidColors { + /** + * A HEX color value for customizing the color of the progress bar. + */ + progressIndicator: string; + /** + * A HEX color value for customizing the background color of the webview. + */ + backgroundColor: string; + /** + * A HEX color value for customizing the background color of the webview header. + */ + headerBackgroundColor: string; + /** + * A HEX color value for customizing the text color of the webview header. + */ + headerTextColor: string; + /** + * A HEX color value for customizing the color of the close button. + */ + closeButtonColor?: string; +} + +export interface AndroidAutomaticColors { + /** + * Color overrides when the theme preference is 'light'. + */ + light: AndroidColors; + /** + * Color overrides when the theme preference is 'dark'. + */ + dark: AndroidColors; +} + +interface CommonConfiguration { + /** + * Enable/disable preloading for checkout. This option must be enabled for `.preload()` to work as expected. + */ + preloading?: boolean; + /** + * Sets the title of the Checkout sheet. + * + * * Important: This will only modify the Checkout Sheet on iOS, not Android. + * + * To implement localization support for iOS: + * 1. Create a "Localizable.xcstrings" file under "ios/{YourApplication}" + * 2. Set a translated value for a "shopify_checkout_sheet_title" key + * + * To implement localization support for Android: + * 1. Open the "android/app/src/main/res/values/strings.xml" file + * 2. Add "Checkout" + */ + title?: string; + /** + * Sets the log level for the checkout sheet kit. + * Controls the verbosity of logs emitted by the native SDK. + * + * @default LogLevel.error + */ + logLevel?: LogLevel; +} + +export type Configuration = CommonConfiguration & { + acceleratedCheckouts?: AcceleratedCheckoutConfiguration; +} & ( + | { + /** + * The selected color scheme for the checkout. See README.md for more details. + */ + colorScheme?: ColorScheme.web | ColorScheme.light | ColorScheme.dark; + /** + * Platform-specific color overrides + */ + colors?: { + ios?: IosColors; + android?: AndroidColors; + }; + } + | { + /** + * The selected color scheme for the checkout. See README.md for more details. + */ + colorScheme?: ColorScheme.automatic; + /** + * Platform-specific color overrides + */ + colors?: { + ios?: IosColors; + android?: AndroidAutomaticColors; + }; + } + ); + +export type CheckoutEvent = + | 'close' + | 'completed' + | 'error' + | 'geolocationRequest' + | 'pixel'; + +export interface GeolocationRequestEvent { + origin: string; +} + +export type CloseEventCallback = () => void; +export type GeolocationRequestEventCallback = ( + event: GeolocationRequestEvent, +) => void; +export type PixelEventCallback = (event: PixelEvent) => void; +export type CheckoutExceptionCallback = (error: CheckoutException) => void; +export type CheckoutCompletedEventCallback = ( + event: CheckoutCompletedEvent, +) => void; + +export type CheckoutEventCallback = + | CloseEventCallback + | CheckoutExceptionCallback + | CheckoutCompletedEventCallback + | GeolocationRequestEventCallback + | PixelEventCallback; + +/** + * Available wallet types for accelerated checkout + */ +export enum AcceleratedCheckoutWallet { + shopPay = 'shopPay', + applePay = 'applePay', +} + +export enum ApplePayContactField { + email = 'email', + phone = 'phone', +} + +/** + * Configuration for AcceleratedCheckouts + */ +export interface AcceleratedCheckoutConfiguration { + /** + * The storefront domain (e.g., "your-shop.myshopify.com") + */ + storefrontDomain: string; + + /** + * The storefront access token with `write_cart_wallet_payments` scope + */ + storefrontAccessToken: string; + + /** + * Customer information for personalized checkout + * + * @todo v4: Change to union type to match Swift SDK: + * `{ accessToken: string } | { email: string; phoneNumber: string }` + * This will enforce mutually exclusive customer identification patterns. + */ + customer?: { + email?: string; + phoneNumber?: string; + accessToken?: string; + }; + /** + * Enable and configure accelerated checkout wallets. + */ + wallets?: { + /** + * Apple Pay specific configuration. + * When provided, Apple Pay buttons can render and the Apple Pay sheet will + * request the specified buyer contact fields. + */ + applePay?: { + /** + * Buyer contact fields to request in the Apple Pay sheet. + * Supported values: + * - 'email': request the buyer's email address + * - 'phone': request the buyer's phone number + */ + contactFields: ApplePayContactField[]; + /** + * The Apple Merchant Identifier used to sign Apple Pay payment requests on iOS. + * Example: 'merchant.com.yourcompany' + */ + merchantIdentifier: string; + /** + * Restrict the countries available for shipping during the Apple Pay flow. + * Expects ISO 3166-1 alpha-2 country codes (e.g., "US", "CA", "GB"). + * @default null (all countries supported, as per Shop configuration) + */ + supportedShippingCountries?: string[]; + }; + }; +} + +function addEventListener( + event: 'close', + callback: () => void, +): Maybe; + +function addEventListener( + event: 'completed', + callback: CheckoutCompletedEventCallback, +): Maybe; + +function addEventListener( + event: 'error', + callback: CheckoutExceptionCallback, +): Maybe; + +function addEventListener( + event: 'pixel', + callback: PixelEventCallback, +): Maybe; + +function addEventListener( + event: 'geolocationRequest', + callback: GeolocationRequestEventCallback, +): Maybe; + +function removeEventListeners(event: CheckoutEvent): void; + +export type AddEventListener = typeof addEventListener; +export type RemoveEventListeners = typeof removeEventListeners; + +export interface ShopifyCheckoutSheetKit { + /** + * The version number of the Shopify Checkout SDK. + */ + readonly version: string; + /** + * Preload the checkout for faster presentation. + */ + preload(checkoutURL: string): void; + + /** + * Invalidate preload cache. + */ + invalidate(): void; + /** + * Present the checkout. + */ + present(checkoutURL: string): void; + /** + * Configure the checkout. See README.md for more details. + */ + setConfig(config: Configuration): void; + /** + * Return the current config for the checkout. See README.md for more details. + */ + getConfig(): Promise; + /** + * Listen for checkout events + */ + addEventListener: AddEventListener; + /** + * Remove subscriptions to checkout events. + */ + removeEventListeners: RemoveEventListeners; + /** + * Cleans up any event callbacks to prevent memory leaks. + */ + teardown(): void; + + /** + * Configure AcceleratedCheckouts for Shop Pay and Apple Pay buttons + */ + configureAcceleratedCheckouts( + config: AcceleratedCheckoutConfiguration, + ): Promise; + + /** + * Check if accelerated checkout is available for the given cart or product + */ + isAcceleratedCheckoutAvailable(): Promise; +} diff --git a/react-native/modules/@shopify/checkout-sheet-kit/src/index.ts b/react-native/modules/@shopify/checkout-sheet-kit/src/index.ts new file mode 100644 index 00000000..5e5bc8f1 --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/src/index.ts @@ -0,0 +1,547 @@ +/* +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import {NativeEventEmitter, PermissionsAndroid, Platform} from 'react-native'; +import type { + EmitterSubscription, + EventSubscription, + PermissionStatus, +} from 'react-native'; +import RNShopifyCheckoutSheetKit from './specs/NativeShopifyCheckoutSheetKit'; +import {ShopifyCheckoutSheetProvider, useShopifyCheckoutSheet} from './context'; +import {ApplePayContactField, ColorScheme, LogLevel} from './index.d'; +import type { + AcceleratedCheckoutConfiguration, + CheckoutEvent, + CheckoutEventCallback, + Configuration, + Features, + GeolocationRequestEvent, + Maybe, + ShopifyCheckoutSheetKit, +} from './index.d'; +import {AcceleratedCheckoutWallet} from './index.d'; +import type {CheckoutException, CheckoutNativeError} from './errors.d'; +import { + CheckoutExpiredError, + CheckoutClientError, + CheckoutHTTPError, + ConfigurationError, + InternalError, + CheckoutNativeErrorType, + GenericError, +} from './errors.d'; +import {CheckoutErrorCode} from './errors.d'; +import type {CheckoutCompletedEvent} from './events.d'; +import type {CustomEvent, PixelEvent, StandardEvent} from './pixels.d'; +import {ApplePayLabel, ApplePayStyle} from './components/AcceleratedCheckoutButtons'; +import type { + AcceleratedCheckoutButtonsProps, + RenderStateChangeEvent, +} from './components/AcceleratedCheckoutButtons'; + +const defaultFeatures: Features = { + handleGeolocationRequests: true, +}; + +class ShopifyCheckoutSheet implements ShopifyCheckoutSheetKit { + private static eventEmitter: NativeEventEmitter = new NativeEventEmitter( + RNShopifyCheckoutSheetKit, + ); + + private features: Features; + private geolocationCallback: Maybe; + + private _acceleratedCheckoutsReady = false; + + get acceleratedCheckoutsReady(): boolean { + return this._acceleratedCheckoutsReady; + } + + /** + * Initializes a new ShopifyCheckoutSheet instance + * @param configuration Optional configuration settings for the checkout + * @param features Optional feature flags to customize behavior, defaults to defaultFeatures + */ + constructor( + configuration?: Configuration, + features: Partial = defaultFeatures, + ) { + this.features = {...defaultFeatures, ...features}; + + if (configuration != null) { + this.setConfig(configuration); + } + + if ( + Platform.OS === 'android' && + this.featureEnabled('handleGeolocationRequests') + ) { + this.subscribeToGeolocationRequestPrompts(); + } + } + + public readonly version: string = + RNShopifyCheckoutSheetKit.getConstants().version; + + /** + * Dismisses the currently displayed checkout sheet + */ + public dismiss(): void { + RNShopifyCheckoutSheetKit.dismiss(); + } + + /** + * Invalidates the checkout that was cached using preload + */ + public invalidate(): void { + RNShopifyCheckoutSheetKit.invalidateCache(); + } + + /** + * Preloads checkout for a given URL to improve performance + * @param checkoutUrl The URL of the checkout to preload + */ + public preload(checkoutUrl: string): void { + RNShopifyCheckoutSheetKit.preload(checkoutUrl); + } + + /** + * Presents the checkout sheet for a given checkout URL + * @param checkoutUrl The URL of the checkout to display + */ + public present(checkoutUrl: string): void { + RNShopifyCheckoutSheetKit.present(checkoutUrl); + } + + /** + * Retrieves the current checkout configuration + * @returns Promise containing the current Configuration + */ + public async getConfig(): Promise { + return RNShopifyCheckoutSheetKit.getConfig() as Promise; + } + + /** + * Updates the checkout configuration + * @param configuration New configuration settings to apply + */ + public async setConfig(configuration: Configuration): Promise { + if (configuration.acceleratedCheckouts) { + this._acceleratedCheckoutsReady = + await this.configureAcceleratedCheckouts( + configuration.acceleratedCheckouts, + ); + } + RNShopifyCheckoutSheetKit.setConfig(configuration); + } + + /** + * Adds an event listener for checkout events + * @param event The type of event to listen for + * @param callback Function to be called when the event occurs + * @returns An EmitterSubscription that can be used to remove the listener + */ + public addEventListener( + event: CheckoutEvent, + callback: CheckoutEventCallback, + ): EmitterSubscription | undefined { + let eventCallback; + + switch (event) { + case 'pixel': + eventCallback = this.interceptEventEmission( + 'pixel', + callback, + this.parseCustomPixelData, + ); + break; + case 'completed': + eventCallback = this.interceptEventEmission('completed', callback); + break; + case 'error': + eventCallback = this.interceptEventEmission( + 'error', + callback, + this.parseCheckoutError, + ); + break; + case 'geolocationRequest': + eventCallback = this.interceptEventEmission( + 'geolocationRequest', + callback, + ); + break; + default: + eventCallback = callback; + } + + // Default handler for all non-pixel events + return ShopifyCheckoutSheet.eventEmitter.addListener(event, eventCallback); + } + + /** + * Removes all event listeners for a specific event type + * @param event The type of event to remove listeners for + */ + public removeEventListeners(event: CheckoutEvent) { + ShopifyCheckoutSheet.eventEmitter.removeAllListeners(event); + } + + /** + * Cleans up resources and event listeners used by the checkout sheet + */ + public teardown() { + this.geolocationCallback?.remove(); + } + + /** + * Configure AcceleratedCheckouts for Shop Pay and Apple Pay buttons + * @param config Configuration for AcceleratedCheckouts + */ + public async configureAcceleratedCheckouts( + config: AcceleratedCheckoutConfiguration, + ): Promise { + if (!this.acceleratedCheckoutsSupported) { + return false; + } + + try { + this.validateAcceleratedCheckoutsConfiguration(config); + + const configured = + await RNShopifyCheckoutSheetKit.configureAcceleratedCheckouts( + config.storefrontDomain, + config.storefrontAccessToken, + config.customer?.email || null, + config.customer?.phoneNumber || null, + config.customer?.accessToken || null, + config.wallets?.applePay?.merchantIdentifier || null, + config.wallets?.applePay?.contactFields || [], + config.wallets?.applePay?.supportedShippingCountries || [], + ); + return configured; + } catch (error) { + // eslint-disable-next-line no-console + console.error( + '[ShopifyCheckoutSheetKit] Failed to configure accelerated checkouts with', + error, + ); + return false; + } + } + + /** + * Check if accelerated checkout is available for the given cart or product + * @param options Options containing either cartId or variantId/quantity + * @returns Promise indicating availability + */ + public async isAcceleratedCheckoutAvailable(): Promise { + if (!this.acceleratedCheckoutsSupported) { + return false; + } + + return RNShopifyCheckoutSheetKit.isAcceleratedCheckoutAvailable(); + } + + /** + * Initiates a geolocation request for Android devices + * Only needed if features.handleGeolocationRequests is false + */ + public async initiateGeolocationRequest(allow: boolean) { + if (Platform.OS === 'android') { + RNShopifyCheckoutSheetKit.initiateGeolocationRequest?.(allow); + } + } + + // --- private + + /** + * Accelerated Checkouts is only supported from iOS 16.0 onwards + */ + private get acceleratedCheckoutsSupported(): boolean { + return Platform.OS === 'ios' && this.majorVersion >= 16; + } + + private get majorVersion(): number { + return parseInt(String(Platform.Version), 10); + } + + private validateAcceleratedCheckoutsConfiguration( + acceleratedCheckouts: Configuration['acceleratedCheckouts'], + ) { + if (!acceleratedCheckouts) { + return; + } + + const {storefrontDomain, storefrontAccessToken, wallets} = + acceleratedCheckouts; + + /** + * Required Accelerated Checkouts configuration properties + */ + if (!storefrontDomain) { + throw new Error('`storefrontDomain` is required'); + } + if (!storefrontAccessToken) { + throw new Error('`storefrontAccessToken` is required'); + } + + /** + * Validate Apple Pay config if available + */ + if (wallets?.applePay) { + const {merchantIdentifier, contactFields, supportedShippingCountries} = + wallets.applePay; + + if (!merchantIdentifier) { + throw new Error('`wallets.applePay.merchantIdentifier` is required'); + } + + const expectedContactFields = Object.values(ApplePayContactField); + const hasInvalidContactFields = + Array.isArray(contactFields) && + contactFields.some( + value => + !expectedContactFields.includes( + value.toLowerCase() as ApplePayContactField, + ), + ); + + if (hasInvalidContactFields) { + throw new Error( + `'wallets.applePay.contactFields' contains unexpected values. Expected "${expectedContactFields.join(', ')}", received "${contactFields}"`, + ); + } + + if ( + Array.isArray(supportedShippingCountries) && + supportedShippingCountries?.some(country => typeof country !== 'string') + ) { + throw new Error( + `'wallets.applePay.supportedShippingCountries' contains unexpected values. Expects ISO 3166-1 alpha-2 country codes (e.g., "US", "CA", "GB").`, + ); + } + } + } + + /** + * Checks if a specific feature is enabled in the configuration + * @param feature The feature to check + * @returns boolean indicating if the feature is enabled + */ + private featureEnabled(feature: keyof Features) { + return this.features[feature] ?? true; + } + + /** + * Sets up geolocation request handling for Android devices + */ + private subscribeToGeolocationRequestPrompts() { + this.geolocationCallback = this.addEventListener( + 'geolocationRequest', + async () => { + const coarseOrFineGrainAccessGranted = await this.requestGeolocation(); + + this.initiateGeolocationRequest(coarseOrFineGrainAccessGranted); + }, + ); + } + + /** + * Requests geolocation permissions on Android + * @returns Promise indicating if permission was granted + */ + private async requestGeolocation(): Promise { + const coarse = 'android.permission.ACCESS_COARSE_LOCATION'; + const fine = 'android.permission.ACCESS_FINE_LOCATION'; + const results = await PermissionsAndroid.requestMultiple([coarse, fine]); + + return [results[coarse], results[fine]].some(this.permissionGranted); + } + + /** + * Checks if the given permission status indicates that permission was granted + * @param status The permission status to check + * @returns boolean indicating if the permission was granted + */ + private permissionGranted(status: PermissionStatus): boolean { + return status === 'granted'; + } + + /** + * Parses custom pixel event data from string to object if needed + * @param eventData The pixel event data to parse + * @returns Parsed PixelEvent object + */ + private parseCustomPixelData(eventData: PixelEvent): PixelEvent { + if ( + isCustomPixelEvent(eventData) && + eventData.hasOwnProperty('customData') && + typeof eventData.customData === 'string' + ) { + try { + return { + ...eventData, + customData: JSON.parse(eventData.customData), + }; + } catch { + return eventData; + } + } + + return eventData; + } + + /** + * Converts native checkout errors into appropriate error class instances + * @param exception The native error to parse + * @returns Appropriate CheckoutException instance + */ + private parseCheckoutError( + exception: CheckoutNativeError, + ): CheckoutException { + switch (exception?.__typename) { + case CheckoutNativeErrorType.InternalError: + return new InternalError(exception); + case CheckoutNativeErrorType.ConfigurationError: + return new ConfigurationError(exception); + case CheckoutNativeErrorType.CheckoutClientError: + return new CheckoutClientError(exception); + case CheckoutNativeErrorType.CheckoutHTTPError: + return new CheckoutHTTPError(exception); + case CheckoutNativeErrorType.CheckoutExpiredError: + return new CheckoutExpiredError(exception); + default: + return new GenericError(exception); + } + } + + /** + * Handles event emission parsing and transformation + * @param event The type of event being intercepted + * @param callback The callback to execute with the parsed data + * @param transformData Optional function to transform the event data + * @returns Function that handles the event emission + */ + private interceptEventEmission( + event: CheckoutEvent, + callback: CheckoutEventCallback, + transformData?: (data: any) => any, + ): (eventData: string | typeof callback) => void { + return (eventData: string | typeof callback): void => { + try { + if (typeof eventData === 'string') { + try { + let parsed = JSON.parse(eventData); + parsed = transformData?.(parsed) ?? parsed; + callback(parsed); + } catch (error) { + const parseError = new LifecycleEventParseError( + `Failed to parse "${event}" event data: Invalid JSON`, + { + cause: 'Invalid JSON', + }, + ); + // eslint-disable-next-line no-console + console.error(parseError, eventData); + } + } else if (eventData && typeof eventData === 'object') { + callback(transformData?.(eventData) ?? eventData); + } + } catch (error) { + const parseError = new LifecycleEventParseError( + `Failed to parse "${event}" event data`, + { + cause: 'Unknown', + }, + ); + // eslint-disable-next-line no-console + console.error(parseError); + } + }; + } +} + +function isCustomPixelEvent(event: PixelEvent): event is CustomEvent { + return event.type === 'CUSTOM'; +} + +export class LifecycleEventParseError extends Error { + constructor(message?: string, options?: ErrorOptions) { + super(message, options); + this.name = 'LifecycleEventParseError'; + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, LifecycleEventParseError); + } + } +} + +// API +export { + AcceleratedCheckoutWallet, + ApplePayContactField, + ApplePayLabel, + ApplePayStyle, + ColorScheme, + LogLevel, + ShopifyCheckoutSheet, + ShopifyCheckoutSheetProvider, + useShopifyCheckoutSheet, +}; + +// Error classes +export { + CheckoutClientError, + CheckoutErrorCode, + CheckoutExpiredError, + CheckoutHTTPError, + CheckoutNativeErrorType, + ConfigurationError, + GenericError, + InternalError, +}; + +// Types +export type { + AcceleratedCheckoutButtonsProps, + AcceleratedCheckoutConfiguration, + CheckoutCompletedEvent, + CheckoutEvent, + CheckoutEventCallback, + CheckoutException, + Configuration, + CustomEvent, + Features, + GeolocationRequestEvent, + PixelEvent, + RenderStateChangeEvent, + StandardEvent, +}; + +// Components +export { + AcceleratedCheckoutButtons, + RenderState, +} from './components/AcceleratedCheckoutButtons'; diff --git a/react-native/modules/@shopify/checkout-sheet-kit/src/pixels.d.ts b/react-native/modules/@shopify/checkout-sheet-kit/src/pixels.d.ts new file mode 100644 index 00000000..f60c24ca --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/src/pixels.d.ts @@ -0,0 +1,578 @@ +/* +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +export type PixelEvent = CustomEvent | StandardEvent; + +/** + * A Standard Web Pixels event. + * + * See https://shopify.dev/docs/api/web-pixels-api/standard-events + */ +export interface StandardEvent { + /* A snapshot of various read-only properties of the browser at the time of the event */ + context?: Context; + /* Event data */ + data?: StandardEventData; + /* The ID of the event */ + id?: string; + /* The name of the event */ + name?: string; + /* A timestamp of when the event occurred (ISO_8601) */ + timestamp?: string; + /* Event type */ + type?: 'STANDARD'; +} + +export interface StandardEventData { + checkout?: Checkout; +} + +/** + * A Custom Web Pixels event. + * + * See https://shopify.dev/docs/api/web-pixels-api/emitting-data#publishing-custom-events + */ +export interface CustomEvent { + /* A snapshot of various read-only properties of the browser at the time of the event */ + context?: Context; + /** + * A free-form object representing data specific to a custom event provided by + * the custom event publisher. + */ + customData?: any; + /* The ID of the event */ + id?: string; + /* The name of the event */ + name?: string; + /* The timestamp of when the event occurred (ISO_8601) */ + timestamp?: string; + /* Event type */ + type?: 'CUSTOM'; +} + +interface Context { + /* Snapshot of a subset of properties of the `document` object in the top */ + /* frame of the browser */ + document?: WebPixelsDocument; + /* Snapshot of a subset of properties of the `navigator` object in the top */ + /* frame of the browser */ + navigator?: WebPixelsNavigator; + /* Snapshot of a subset of properties of the `window` object in the top frame */ + /* of the browser */ + window?: WebPixelsWindow; +} + +/** + * A snapshot of a subset of properties of the `document` object in the top frame of the browser. + * + * Per [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Document) + * */ +interface WebPixelsDocument { + /* The character set being used by the document */ + characterSet?: string; + /* Returns the URI of the current document */ + location?: Location; + /* Returns the URI of the page that linked to this page */ + referrer?: string; + /* Returns the title of the current document */ + title?: string; +} + +/** + * The location, or current URL, of the window object. + * + * Per [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Window) + */ +interface Location { + /* String containing a `'#'` followed by the fragment identifier of the URL */ + hash?: string; + /* String containing the host, that is the hostname, a `':'`, and the port of the URL */ + host?: string; + /* String containing the domain of the URL */ + hostname?: string; + /* String containing the entire URL */ + href?: string; + /* String containing the canonical form of the origin of the specific location */ + origin?: string; + /* String containing an initial `'/'` followed by the path of the URL, not including the query string or fragment */ + pathname?: string; + /* String containing the port number of the URL */ + port?: string; + /* String containing the protocol scheme of the URL, including the final `':'` */ + locationProtocol?: string; + /* String containing a `'?'` followed by the parameters or "querystring" of */ + /* the URL */ + search?: string; +} + +/** + * A snapshot of a subset of properties based on the `navigator` object + * in the top frame of the browser. + * + * Per [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Navigator) + */ +interface WebPixelsNavigator { + /* Returns `false` if setting a cookie will be ignored and true otherwise */ + cookieEnabled?: boolean; + /* Returns a string representing the preferred language of the user, usually */ + /* the language of the browser UI. The `null` value is returned when this */ + /* is unknown */ + language?: string; + /* Returns an array of strings representing the languages known to the user, */ + /* by order of preference */ + languages?: string[]; + /* Returns the user agent string for the current browser */ + userAgent?: string; +} + +/** + * A snapshot of a subset of properties of the `window` object in the top frame of the browser. + * + * Per [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Window) + */ +interface WebPixelsWindow { + /* The height of the content area of the browser window including, if */ + /* rendered, the horizontal scrollbar */ + innerHeight?: number; + /* The width of the content area of the browser window including, if rendered, */ + /* the vertical scrollbar */ + innerWidth?: number; + /* Location, or current URL, of the window object */ + location?: Location; + /* The global object's origin, serialized as a string */ + origin?: string; + /* The height of the outside of the browser window */ + outerHeight?: number; + /* The width of the outside of the browser window */ + outerWidth?: number; + /* Alias for window.scrollX */ + pageXOffset?: number; + /* Alias for window.scrollY */ + pageYOffset?: number; + /* Interface representing a screen, usually the one on which the current window is being rendered */ + screen?: Screen; + /* Horizontal distance from the left border of the user's browser viewport to */ + /* the left side of the screen */ + screenX?: number; + /* Vertical distance from the top border of the user's browser viewport to the */ + /* top side of the screen */ + screenY?: number; + /* Number of pixels that the document has already been scrolled horizontally */ + scrollX?: number; + /* Number of pixels that the document has already been scrolled vertically */ + scrollY?: number; +} + +/* Per [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Screen), the */ +/* interface representing a screen, usually the one on which the current */ +/* window is being rendered */ +// +/* The interface representing a screen, usually the one on which the current */ +/* window is being rendered */ +interface Screen { + /* The height of the screen */ + height?: number; + /* The width of the screen */ + width?: number; +} + +/* A monetary value with currency allocated to the transaction method. */ +interface MoneyV2 { + /* A decimal money amount. */ + amount?: number; + /* Three-letter code that represents the currency, for example, USD. */ + /* Supported codes include standard ISO 4217 codes, legacy codes, and non- */ + /* standard codes. */ + currencyCode?: string; +} + +/* Information about the merchandise in the cart. */ +interface CartLine { + /* The cost of the merchandise that the customer will pay for at checkout. The */ + /* costs are subject to change and changes will be reflected at checkout. */ + cost?: CartLineCost; + /* The merchandise that the buyer intends to purchase. */ + merchandise?: ProductVariant; + /* The quantity of the merchandise that the customer intends to purchase. */ + quantity?: number; +} + +/* The cost of the merchandise line that the customer will pay at checkout. */ +interface CartLineCost { + /* The total cost of the merchandise line. */ + totalAmount?: MoneyV2; +} + +/** + * A product variant represents a different version of a product, such as + * differing sizes or differing colors. + * */ +interface ProductVariant { + /* A globally unique identifier. */ + id?: string; + /* Image associated with the product variant. This field falls back to the */ + /* product image if no image is available. */ + image?: Image; + /* The product variant’s price. */ + price?: MoneyV2; + /* The product object that the product variant belongs to. */ + product?: Product; + /* The SKU (stock keeping unit) associated with the variant. */ + sku?: string; + /* The product variant’s title. */ + title?: string; + /* The product variant’s untranslated title. */ + untranslatedTitle?: string; +} + +/* An image resource. */ +interface Image { + /* The location of the image as a URL. */ + src?: string; +} + +/* The product object that the product variant belongs to. */ +interface Product { + /* The ID of the product. */ + id?: string; + /* The product’s title. */ + title?: string; + /* The [product type](https://help.shopify.com/en/manual/products/details/product-type) */ + type?: string; + /* The product’s untranslated title. */ + untranslatedTitle?: string; + /* The relative URL of the product. */ + url?: string; + /* The product’s vendor name. */ + vendor?: string; +} + +/* A container for all the information required to add items to checkout and pay. */ +interface Checkout { + /* A list of attributes accumulated throughout the checkout process. */ + attributes?: Attribute[]; + /* The billing address to where the order will be charged. */ + billingAddress?: MailingAddress; + /* Indicates whether the customer has consented to be sent marketing material via email. */ + buyerAcceptsEmailMarketing?: boolean; + /*Indicates whether the customer has consented to be sent marketing material via SMS. */ + buyerAcceptsSmsMarketing?: boolean; + /** + * The three-letter code that represents the currency, for example, USD. + * Supported codes include standard ISO 4217 codes, legacy codes, and non- + * standard codes. + */ + currencyCode?: string; + /* Represents the selected delivery options for a checkout. */ + delivery?: Delivery; + /* A list of discount applications. */ + discountApplications?: DiscountApplication[]; + /* The total amount of the discounts applied to the price of the checkout. */ + discountsAmount?: MoneyV2; + /* The email attached to this checkout. */ + email?: string; + /* A list of line item objects, each one containing information about an item */ + /* in the checkout. */ + lineItems?: CheckoutLineItem[]; + /* Information about the active localized experience. */ + localization?: Localization; + /* The resulting order from a paid checkout. */ + order?: Order; + /* A unique phone number for the customer. Formatted using E.164 standard. For */ + /* example, *+16135551111*. */ + phone?: string; + /* The shipping address to where the line items will be shipped. */ + shippingAddress?: MailingAddress; + /* Once a shipping rate is selected by the customer it is transitioned to a */ + /* `shipping_line` object. */ + shippingLine?: ShippingRate; + /* The phone number provided by the buyer after opting in to SMS marketing. */ + smsMarketingPhone?: string; + /* The price at checkout before duties, shipping, and taxes. */ + subtotalPrice?: MoneyV2; + /* A unique identifier for a particular checkout. */ + token?: string; + /* The sum of all the prices of all the items in the checkout, including */ + /* duties, taxes, and discounts. */ + totalPrice?: MoneyV2; + /* The sum of all the taxes applied to the line items and shipping lines in */ + /* the checkout. */ + totalTax?: MoneyV2; + /* A list of transactions associated with a checkout or order. */ + transactions?: Transaction[]; +} + +/* Custom attributes left by the customer to the merchant, either in their cart */ +/* or during checkout. */ +interface Attribute { + /* The key for the attribute. */ + key?: string; + /* The value for the attribute. */ + value?: string; +} + +/* A single line item in the checkout, grouped by variant and attributes. */ +interface CheckoutLineItem { + /* The discounts that have been applied to the checkout line item by a */ + /* discount application. */ + discountAllocations?: DiscountAllocation[]; + /* The combined price of all of the items in the line item after line-level discounts have been applied. */ + finalLinePrice?: MoneyV2; + /* A globally unique identifier. */ + id?: string; + /** The properties of the line item. A shop may add, or enable customers to add custom information to a line item. + * Line item properties consist of a key and value pair. + */ + properties?: Property[]; + /* The quantity of the line item. */ + quantity?: number; + /* The title of the line item. Defaults to the product's title. */ + sellingPlanAllocation?: SellingPlanAllocation; + title?: string; + /* Product variant of the line item. */ + variant?: ProductVariant; +} + +interface Country { + /* The ISO-3166-1 code for this country, for example, "US". */ + isoCode?: string; +} + +interface Delivery { + selectedDeliveryOptions?: DeliveryOption[]; +} + +interface DeliveryOption { + /* The delivery option that the customer has selected. */ + cost?: MoneyV2; + /* The cost of the delivery option after discounts have been applied. */ + costAfterDiscounts?: MoneyV2; + /* The description of the delivery option. */ + description?: string; + /* The unique identifier of the delivery option. */ + handle?: string; + /* The title of the delivery option. */ + title?: string; + /* he type of delivery option, e.g. pickup, pickupPoint, shipping, local. */ + type?: string; +} + +/* The discount that has been applied to the checkout line item. */ +interface DiscountAllocation { + /* The monetary value with currency allocated to the discount. */ + amount?: MoneyV2; + /* The information about the intent of the discount. */ + discountApplication?: DiscountApplication; +} + +/* The information about the intent of the discount. */ +interface DiscountApplication { + /* The method by which the discount's value is applied to its entitled items. */ + // + /* - `ACROSS`: The value is spread across all entitled lines. */ + /* - `EACH`: The value is applied onto every entitled line. */ + allocationMethod?: string; + /* How the discount amount is distributed on the discounted lines. */ + // + /* - `ALL`: The discount is allocated onto all the lines. */ + /* - `ENTITLED`: The discount is allocated onto only the lines that it's */ + /* entitled for. */ + /* - `EXPLICIT`: The discount is allocated onto explicitly chosen lines. */ + targetSelection?: string; + /* The type of line (i.e. line item or shipping line) on an order that the */ + /* discount is applicable towards. */ + // + /* - `LINE_ITEM`: The discount applies onto line items. */ + /* - `SHIPPING_LINE`: The discount applies onto shipping lines. */ + targetType?: string; + /* The customer-facing name of the discount. If the type of discount is */ + /* a `DISCOUNT_CODE`, this `title` attribute represents the code of the */ + /* discount. */ + title?: string; + /* The type of the discount. */ + // + /* - `AUTOMATIC`: A discount automatically at checkout or in the cart without */ + /* the need for a code. */ + /* - `DISCOUNT_CODE`: A discount applied onto checkouts through the use of */ + /* a code. */ + /* - `MANUAL`: A discount that is applied to an order by a merchant or store */ + /* owner manually, rather than being automatically applied by the system or */ + /* through a script. */ + /* - `SCRIPT`: A discount applied to a customer's order using a script */ + type?: string; + /* The value of the discount. Fixed discounts return a `Money` Object, while */ + /* Percentage discounts return a `PricingPercentageValue` object. */ + value?: Value; +} + +interface Language { + /* The BCP-47 language tag. It may contain a dash followed by an ISO 3166-1 alpha-2 region code, for example, "en-US". */ + isoCode?: string; +} + +interface Localization { + /* The country of the active localized experience. */ + country?: Country; + /* The language of the active localized experience. */ + language?: Language; + /* The market including the country of the active localized experience. */ + market?: Market; +} + +/* A mailing address for customers and shipping. */ +interface MailingAddress { + /* The first line of the address. This is usually the street address or a P.O. */ + /* Box number. */ + address1?: string; + /* The second line of the address. This is usually an apartment, suite, or */ + /* unit number. */ + address2?: string; + /* The name of the city, district, village, or town. */ + city?: string; + /* The name of the country. */ + country?: string; + /* The two-letter code that represents the country, for example, US. */ + /* The country codes generally follows ISO 3166-1 alpha-2 guidelines. */ + countryCode?: string; + /* The customer’s first name. */ + firstName?: string; + /* The customer’s last name. */ + lastName?: string; + /* The phone number for this mailing address as entered by the customer. */ + phone?: string; + /* The region of the address, such as the province, state, or district. */ + province?: string; + /* The two-letter code for the region. */ + /* For example, ON. */ + provinceCode?: string; + /* The ZIP or postal code of the address. */ + zip?: string; +} + +interface Market { + /* A human-readable, shop-scoped identifier. */ + handle?: string; + /* A globally unique identifier. */ + id?: string; +} + +/** + * An order is a customer’s completed request to purchase one or more products + * from a shop. An order is created when a customer completes the checkout + * process. + */ +interface Order { + /* The ID of the order. */ + id?: string; + /* The customer that placed the order. */ + customer?: OrderCustomer; +} + +interface OrderCustomer { + /* The ID of the customer. */ + id?: string; + /* Indicates whether the customer is a first-time buyer. */ + isFirstOrder?: boolean; +} + +/** + * A value given to a customer when a discount is applied to an order. The + * application of a discount with this value gives the customer the specified + * percentage off a specified item. + */ +interface PricingPercentageValue { + /* The percentage value of the object. */ + percentage?: number; +} + +interface Property { + /* The key for the property. */ + key?: string; + /* The value for the property. */ + value?: string; +} + +interface SellingPlan { + /* A globally unique identifier. */ + id?: string; + /* The name of the selling plan. For example, '6 weeks of prepaid granola, delivered weekly'. */ + name?: string; +} + +interface SellingPlanAllocation { + /** + * A representation of how products and variants can be sold and purchased. For example, an individual selling plan could be + * '6 weeks of prepaid granola, delivered weekly'. + */ + sellingPlan?: SellingPlan; +} + +/* A shipping rate to be applied to a checkout. */ +interface ShippingRate { + /* Price of this shipping rate. */ + price?: MoneyV2; +} + +/* A transaction associated with a checkout or order. */ +interface Transaction { + /* The monetary value with currency allocated to the transaction method. */ + amount?: MoneyV2; + /* The name of the payment provider used for the transaction. */ + gateway?: string; + /* The payment method used for the transaction. */ + paymentMethod?: TransactionPaymentMethod; +} + +interface TransactionPaymentMethod { + /* The name of the payment method used for the transaction. This may further specify the payment method used. */ + name?: string; + /** + * The type of payment method used for the transaction. + * + * - creditCard: A vaulted or manually entered credit card. + * - redeemable: A redeemable payment method, such as a gift card or store credit. + * - deferred: A deferred payment, such as invoicing the buyer and collecting payment later. + * - local: A local payment method specific to the current region or market. + * - manualPayment: A manual payment method, such as an in-person retail transaction. + * - paymentOnDelivery: A payment that will be collected on delivery. + * - wallet: An integrated wallet, such as PayPal, Google Pay, Apple Pay, etc. + * - offsite: A payment processed outside of Shopify's checkout, excluding integrated wallets. + * - customOnSite: A custom payment method that is processed through a checkout extension with a payments app. + * - other: Another type of payment not defined here. + */ + type?: string; +} + +/* A value given to a customer when a discount is applied to an order. The */ +/* application of a discount with this value gives the customer the specified */ +/* percentage off a specified item. */ +interface Value { + /* The decimal money amount. */ + amount?: number; + /* The three-letter code that represents the currency, for example, USD. */ + /* Supported codes include standard ISO 4217 codes, legacy codes, and non- */ + /* standard codes. */ + currencyCode?: string; + /* The percentage value of the object. */ + percentage?: number; +} diff --git a/react-native/modules/@shopify/checkout-sheet-kit/src/specs/NativeShopifyCheckoutSheetKit.ts b/react-native/modules/@shopify/checkout-sheet-kit/src/specs/NativeShopifyCheckoutSheetKit.ts new file mode 100644 index 00000000..89855ea5 --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/src/specs/NativeShopifyCheckoutSheetKit.ts @@ -0,0 +1,101 @@ +/* +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import type {TurboModule} from 'react-native'; +import {TurboModuleRegistry} from 'react-native'; + +type IosColorsSpec = { + tintColor?: string; + backgroundColor?: string; + closeButtonColor?: string; +}; + +type AndroidColorsBaseSpec = { + progressIndicator?: string; + backgroundColor?: string; + headerBackgroundColor?: string; + headerTextColor?: string; + closeButtonColor?: string; +}; + +type AndroidColorsSpec = { + progressIndicator?: string; + backgroundColor?: string; + headerBackgroundColor?: string; + headerTextColor?: string; + closeButtonColor?: string; + light?: AndroidColorsBaseSpec; + dark?: AndroidColorsBaseSpec; +}; + +type ColorsSpec = { + ios?: IosColorsSpec; + android?: AndroidColorsSpec; +}; + +type ConfigurationSpec = { + preloading?: boolean; + title?: string; + colorScheme?: string; + logLevel?: string; + colors?: ColorsSpec; +}; + +type ConfigurationResultSpec = { + preloading: boolean; + colorScheme: string; + logLevel: string; + title?: string; + tintColor?: string; + backgroundColor?: string; + closeButtonColor?: string; +}; + +export interface Spec extends TurboModule { + present(checkoutUrl: string): void; + preload(checkoutUrl: string): void; + dismiss(): void; + invalidateCache(): void; + setConfig(configuration: ConfigurationSpec): void; + getConfig(): Promise; + configureAcceleratedCheckouts( + storefrontDomain: string, + storefrontAccessToken: string, + customerEmail: string | null, + customerPhoneNumber: string | null, + customerAccessToken: string | null, + applePayMerchantIdentifier: string | null, + applyPayContactFields: string[], + supportedShippingCountries: string[], + ): Promise; + isAcceleratedCheckoutAvailable(): Promise; + isApplePayAvailable(): Promise; + initiateGeolocationRequest(allow: boolean): void; + addListener(eventName: string): void; + removeListeners(count: number): void; + getConstants(): {version: string}; +} + +export default TurboModuleRegistry.getEnforcing( + 'ShopifyCheckoutSheetKit', +); diff --git a/react-native/modules/@shopify/checkout-sheet-kit/src/specs/RCTAcceleratedCheckoutButtonsNativeComponent.ts b/react-native/modules/@shopify/checkout-sheet-kit/src/specs/RCTAcceleratedCheckoutButtonsNativeComponent.ts new file mode 100644 index 00000000..9af49d0d --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/src/specs/RCTAcceleratedCheckoutButtonsNativeComponent.ts @@ -0,0 +1,81 @@ +/* +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import {codegenNativeComponent, type ViewProps} from 'react-native'; +import type { + BubblingEventHandler, + DirectEventHandler, + Double, + Float, + // eslint-disable-next-line @react-native/no-deep-imports -- codegen parser requires these type names to be imported directly (not via aliases) so it can match them statically during AST traversal +} from 'react-native/Libraries/Types/CodegenTypes'; + +type FailEvent = Readonly<{ + __typename: string; + message: string; + code?: string; + recoverable?: boolean; +}>; + +type CompleteEvent = Readonly<{ + orderDetails: Readonly<{ + id: string; + email?: string; + phone?: string; + }>; +}>; + +type RenderStateChangeEvent = Readonly<{ + state: string; + reason?: string; +}>; + +type ClickLinkEvent = Readonly<{url: string}>; +type SizeChangeEvent = Readonly<{height: Double}>; + +type CheckoutIdentifierSpec = Readonly<{ + cartId?: string; + variantId?: string; + quantity?: Double; +}>; + +interface NativeProps extends ViewProps { + checkoutIdentifier: CheckoutIdentifierSpec; + cornerRadius?: Float; + wallets?: ReadonlyArray; + applePayLabel?: string; + onFail?: BubblingEventHandler; + onComplete?: BubblingEventHandler; + onCancel?: BubblingEventHandler; + onRenderStateChange?: BubblingEventHandler; + onWebPixelEvent?: BubblingEventHandler>; + onClickLink?: BubblingEventHandler; + onSizeChange?: DirectEventHandler; + onShouldRecoverFromError?: DirectEventHandler< + Readonly<{recoverable: boolean}> + >; +} + +export default codegenNativeComponent( + 'RCTAcceleratedCheckoutButtons', +); diff --git a/react-native/modules/@shopify/checkout-sheet-kit/tests/AcceleratedCheckoutButtons.test.tsx b/react-native/modules/@shopify/checkout-sheet-kit/tests/AcceleratedCheckoutButtons.test.tsx new file mode 100644 index 00000000..862d7105 --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/tests/AcceleratedCheckoutButtons.test.tsx @@ -0,0 +1,386 @@ +import React from 'react'; +import {render, act} from '@testing-library/react-native'; +import {Platform} from 'react-native'; +import { + AcceleratedCheckoutButtons, + AcceleratedCheckoutWallet, + RenderState, +} from '../src'; + +jest.mock('react-native'); + +const mockLog = jest.fn(); +// Silence console.error +const mockError = jest.fn(); + +beforeAll(() => { + global.console = { + ...global.console, + log: mockLog, + error: mockError, + }; +}); + +beforeEach(() => { + jest.clearAllMocks(); + Platform.OS = 'ios'; +}); + +describe('AcceleratedCheckoutButtons', () => { + describe('iOS Platform', () => { + beforeEach(() => { + Platform.OS = 'ios'; + // Default to iOS 16 for most tests + (Platform as any).Version = '16.0'; + }); + + describe('iOS Version Compatibility', () => { + it.each(['16.0', '17.0', '16.4.1'])('renders on iOS %s', version => { + (Platform as any).Version = version; + + const {getByTestId} = render( + , + ); + + expect(getByTestId('accelerated-checkout-buttons')).toBeTruthy(); + }); + + it.each(['15.5', '14.0', '15.0'])( + 'does not render on iOS %s', + version => { + (Platform as any).Version = version; + + const {queryByTestId} = render( + , + ); + + expect(queryByTestId('accelerated-checkout-buttons')).toBeNull(); + expect(mockLog).not.toHaveBeenCalled(); + expect(mockError).not.toHaveBeenCalled(); + }, + ); + }); + + it('renders without crashing with cartId', () => { + const {getByTestId} = render( + , + ); + + expect(getByTestId('accelerated-checkout-buttons')).toBeTruthy(); + }); + + it('renders without crashing with variant', () => { + const {getByTestId} = render( + , + ); + + expect(getByTestId('accelerated-checkout-buttons')).toBeTruthy(); + }); + + it('renders without crashing with variant and quantity', () => { + const {getByTestId} = render( + , + ); + + expect(getByTestId('accelerated-checkout-buttons')).toBeTruthy(); + }); + + it('passes through props to native component', () => { + const {getByTestId} = render( + , + ); + + const nativeComponent = getByTestId('accelerated-checkout-buttons'); + expect(nativeComponent).toBeTruthy(); + expect(nativeComponent.props.checkoutIdentifier).toEqual({ + cartId: 'gid://shopify/Cart/123', + }); + expect(nativeComponent.props.cornerRadius).toBe(12); + expect(nativeComponent.props.wallets).toEqual([ + AcceleratedCheckoutWallet.shopPay, + ]); + }); + + it.each([0, -1, -2, Number.NaN])( + 'throws when invalid variant quantity %p', + quantity => { + expect(() => { + render( + , + ); + }).toThrow( + 'AcceleratedCheckoutButton: Either `cartId` or `variantId` and `quantity` must be provided', + ); + }, + ); + + it('uses default values for cornerRadius', () => { + const {getByTestId} = render( + , + ); + + const nativeComponent = getByTestId('accelerated-checkout-buttons'); + expect(nativeComponent).toBeTruthy(); + expect(nativeComponent.props.cornerRadius).toBeUndefined(); + }); + + it('passes through custom quantity and cornerRadius', () => { + const {getByTestId} = render( + , + ); + + const nativeComponent = getByTestId('accelerated-checkout-buttons'); + expect(nativeComponent).toBeTruthy(); + expect(nativeComponent.props.cornerRadius).toBe(16); + }); + + it('supports custom wallet configuration', () => { + const customWallets = [ + AcceleratedCheckoutWallet.applePay, + AcceleratedCheckoutWallet.shopPay, + ]; + + const {getByTestId} = render( + , + ); + + const nativeComponent = getByTestId('accelerated-checkout-buttons'); + expect(nativeComponent).toBeTruthy(); + expect(nativeComponent.props.wallets).toEqual(customWallets); + }); + + it('forwards native fail event to onFail prop', () => { + const onFail = jest.fn(); + const {getByTestId} = render( + , + ); + + const nativeComponent = getByTestId('accelerated-checkout-buttons'); + const error = {message: 'boom'} as any; + nativeComponent.props.onFail({nativeEvent: error}); + expect(onFail).toHaveBeenCalledWith(error); + }); + + it('forwards native complete event to onComplete prop', () => { + const onComplete = jest.fn(); + const {getByTestId} = render( + , + ); + + const nativeComponent = getByTestId('accelerated-checkout-buttons'); + const details = {orderDetails: {id: '1'}} as any; + nativeComponent.props.onComplete({nativeEvent: details}); + expect(onComplete).toHaveBeenCalledWith(details); + }); + + it('calls onCancel when native cancel is invoked', () => { + const onCancel = jest.fn(); + const {getByTestId} = render( + , + ); + const nativeComponent = getByTestId('accelerated-checkout-buttons'); + nativeComponent.props.onCancel(); + expect(onCancel).toHaveBeenCalled(); + }); + + it('maps render state change to typed states including error reason', () => { + const onRenderStateChange = jest.fn(); + const {getByTestId} = render( + , + ); + const nativeComponent = getByTestId('accelerated-checkout-buttons'); + nativeComponent.props.onRenderStateChange({ + nativeEvent: {state: 'error', reason: 'bad'}, + }); + expect(onRenderStateChange).toHaveBeenCalledWith({ + state: RenderState.Error, + reason: 'bad', + }); + + nativeComponent.props.onRenderStateChange({ + nativeEvent: {state: 'rendered'}, + }); + expect(onRenderStateChange).toHaveBeenCalledWith({ + state: RenderState.Rendered, + }); + + nativeComponent.props.onRenderStateChange({ + nativeEvent: {state: 'loading'}, + }); + expect(onRenderStateChange).toHaveBeenCalledWith({ + state: RenderState.Loading, + }); + + nativeComponent.props.onRenderStateChange({ + nativeEvent: {state: 'unexpected'}, + }); + expect(onRenderStateChange).toHaveBeenCalledWith({ + state: RenderState.Error, + }); + }); + + it('forwards web pixel native events', () => { + const onWebPixelEvent = jest.fn(); + const {getByTestId} = render( + , + ); + const nativeComponent = getByTestId('accelerated-checkout-buttons'); + const pixel = {type: 'STANDARD'} as any; + nativeComponent.props.onWebPixelEvent({nativeEvent: pixel}); + expect(onWebPixelEvent).toHaveBeenCalledWith(pixel); + }); + + it('handles onClickLink when URL is present and ignores when absent', () => { + const onClickLink = jest.fn(); + const {getByTestId} = render( + , + ); + const nativeComponent = getByTestId('accelerated-checkout-buttons'); + nativeComponent.props.onClickLink({ + nativeEvent: {url: 'https://checkout.shopify.com'}, + }); + expect(onClickLink).toHaveBeenCalledWith('https://checkout.shopify.com'); + + onClickLink.mockClear(); + nativeComponent.props.onClickLink({nativeEvent: {}}); + expect(onClickLink).not.toHaveBeenCalled(); + }); + + it('applies dynamic height when onSizeChange is emitted', async () => { + const {getByTestId} = render( + , + ); + let nativeComponent = getByTestId('accelerated-checkout-buttons'); + await act(async () => { + nativeComponent.props.onSizeChange({nativeEvent: {height: 42}}); + }); + nativeComponent = getByTestId('accelerated-checkout-buttons'); + expect(nativeComponent.props.style).toEqual({ + flex: 1, + height: 42, + }); + }); + + it('warns and returns null when missing identifiers in production', () => { + const originalDev = (global as any).__DEV__; + (global as any).__DEV__ = false; + (Platform as any).Version = '16.0'; + const warn = jest.spyOn(global.console, 'warn').mockImplementation(); + + const {queryByTestId} = render( + // invalid: no cartId and invalid variant path + , + ); + expect(queryByTestId('accelerated-checkout-buttons')).toBeNull(); + expect(warn).toHaveBeenCalledWith( + 'AcceleratedCheckoutButton: Either `cartId` or `variantId` and `quantity` must be provided', + ); + + warn.mockRestore(); + (global as any).__DEV__ = originalDev; + }); + + it('handles callbacks without throwing', () => { + const mockCallbacks = { + onFail: jest.fn(), + onComplete: jest.fn(), + onCancel: jest.fn(), + onRenderStateChange: jest.fn(), + onWebPixelEvent: jest.fn(), + onClickLink: jest.fn(), + }; + + expect(() => { + render( + , + ); + }).not.toThrow(); + }); + }); + + describe('Android Platform', () => { + beforeEach(() => { + Platform.OS = 'android'; + }); + + it('returns null on Android', () => { + const {queryByTestId} = render( + , + ); + + expect(queryByTestId('accelerated-checkout-buttons')).toBeNull(); + }); + + it('does not warn on Android even without required props', () => { + render(); + + expect(mockLog).not.toHaveBeenCalled(); + }); + }); + + describe('RenderState enum', () => { + it('exports correct render states', () => { + expect(RenderState.Loading).toBe('loading'); + expect(RenderState.Rendered).toBe('rendered'); + expect(RenderState.Error).toBe('error'); + }); + + it('logs unexpected render states', async () => { + const {getByTestId} = render( + , + ); + const nativeComponent = getByTestId('accelerated-checkout-buttons'); + nativeComponent.props.onRenderStateChange({ + nativeEvent: {state: 'unexpected'}, + }); + + expect(mockError).toHaveBeenCalledWith( + '[ShopifyAcceleratedCheckouts] Invalid render state: unexpected', + ); + }); + }); +}); diff --git a/react-native/modules/@shopify/checkout-sheet-kit/tests/context.test.tsx b/react-native/modules/@shopify/checkout-sheet-kit/tests/context.test.tsx new file mode 100644 index 00000000..6af74682 --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/tests/context.test.tsx @@ -0,0 +1,404 @@ +import React from 'react'; +import {render, act} from '@testing-library/react-native'; +import {NativeModules, Platform} from 'react-native'; +import { + ShopifyCheckoutSheetProvider, + useShopifyCheckoutSheet, +} from '../src/context'; +import {ApplePayContactField, ColorScheme, type Configuration} from '../src'; + +const checkoutUrl = 'https://shopify.com/checkout'; +const config: Configuration = { + colorScheme: ColorScheme.automatic, +}; + +jest.mock('react-native'); + +const HookTestComponent = ({ + onHookValue, +}: { + onHookValue: (value: any) => void; +}) => { + const hookValue = useShopifyCheckoutSheet(); + onHookValue(hookValue); + return null; +}; + +const MockChild = () => null; + +describe('ShopifyCheckoutSheetProvider', () => { + const TestComponent = ({children}: {children: React.ReactNode}) => ( + + {children} + + ); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('renders without crashing', () => { + const component = render( + + + , + ); + + expect(component).toBeTruthy(); + }); + + it('creates ShopifyCheckoutSheet instance with configuration', () => { + render( + + + , + ); + + expect( + NativeModules.ShopifyCheckoutSheetKit.setConfig, + ).toHaveBeenCalledWith(config); + }); + + it('skips configuration when no configuration is provided', () => { + render( + + + , + ); + + expect( + NativeModules.ShopifyCheckoutSheetKit.setConfig, + ).not.toHaveBeenCalled(); + expect( + NativeModules.ShopifyCheckoutSheetKit.configureAcceleratedCheckouts, + ).not.toHaveBeenCalled(); + }); + + it('configures accelerated checkouts when provided', async () => { + (Platform as any).Version = '17.0'; + ( + NativeModules.ShopifyCheckoutSheetKit + .configureAcceleratedCheckouts as unknown as {mockResolvedValue: any} + ).mockResolvedValue(true); + + const configWithAccelerated: Configuration = { + ...config, + acceleratedCheckouts: { + storefrontDomain: 'test-shop.myshopify.com', + storefrontAccessToken: 'shpat_test_token', + customer: { + email: 'test@example.com', + phoneNumber: '+123', + accessToken: 'customer-access-token', + }, + wallets: { + applePay: { + merchantIdentifier: 'merchant.test', + contactFields: [ApplePayContactField.email], + }, + }, + }, + }; + + render( + + + , + ); + + await act(async () => { + await Promise.resolve(); + }); + + expect( + NativeModules.ShopifyCheckoutSheetKit.configureAcceleratedCheckouts, + ).toHaveBeenCalledWith( + 'test-shop.myshopify.com', + 'shpat_test_token', + 'test@example.com', + '+123', + 'customer-access-token', + 'merchant.test', + ['email'], + [], + ); + }); + + it('reuses the same instance across re-renders', () => { + const {rerender} = render( + + + , + ); + + rerender( + + + , + ); + + expect( + NativeModules.ShopifyCheckoutSheetKit.setConfig.mock.calls, + ).toHaveLength(2); + }); +}); + +describe('useShopifyCheckoutSheet', () => { + const Wrapper = ({children}: {children: React.ReactNode}) => ( + + {children} + + ); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('provides addEventListener function', () => { + let hookValue: any; + const onHookValue = (value: any) => { + hookValue = value; + }; + + render( + + + , + ); + + expect(hookValue.addEventListener).toBeDefined(); + expect(typeof hookValue.addEventListener).toBe('function'); + }); + + it('provides removeEventListeners function', () => { + let hookValue: any; + const onHookValue = (value: any) => { + hookValue = value; + }; + + render( + + + , + ); + + act(() => { + hookValue.removeEventListeners('close'); + }); + + expect(hookValue.removeEventListeners).toBeDefined(); + }); + + it('provides present function and calls it with checkoutUrl', () => { + let hookValue: any; + const onHookValue = (value: any) => { + hookValue = value; + }; + + render( + + + , + ); + + act(() => { + hookValue.present(checkoutUrl); + }); + + expect(NativeModules.ShopifyCheckoutSheetKit.present).toHaveBeenCalledWith( + checkoutUrl, + ); + }); + + it('does not call present with empty checkoutUrl', () => { + let hookValue: any; + const onHookValue = (value: any) => { + hookValue = value; + }; + + render( + + + , + ); + + act(() => { + hookValue.present(''); + }); + + expect( + NativeModules.ShopifyCheckoutSheetKit.present, + ).not.toHaveBeenCalled(); + }); + + it('provides preload function and calls it with checkoutUrl', () => { + let hookValue: any; + const onHookValue = (value: any) => { + hookValue = value; + }; + + render( + + + , + ); + + act(() => { + hookValue.preload(checkoutUrl); + }); + + expect(NativeModules.ShopifyCheckoutSheetKit.preload).toHaveBeenCalledWith( + checkoutUrl, + ); + }); + + it('does not call preload with empty checkoutUrl', () => { + let hookValue: any; + const onHookValue = (value: any) => { + hookValue = value; + }; + + render( + + + , + ); + + act(() => { + hookValue.preload(''); + }); + + expect( + NativeModules.ShopifyCheckoutSheetKit.preload, + ).not.toHaveBeenCalled(); + }); + + it('provides invalidate function', () => { + let hookValue: any; + const onHookValue = (value: any) => { + hookValue = value; + }; + + render( + + + , + ); + + act(() => { + hookValue.invalidate(); + }); + + expect( + NativeModules.ShopifyCheckoutSheetKit.invalidateCache, + ).toHaveBeenCalled(); + }); + + it('provides dismiss function', () => { + let hookValue: any; + const onHookValue = (value: any) => { + hookValue = value; + }; + + render( + + + , + ); + + act(() => { + hookValue.dismiss(); + }); + + expect(NativeModules.ShopifyCheckoutSheetKit.dismiss).toHaveBeenCalled(); + }); + + it('provides setConfig function', () => { + let hookValue: any; + const onHookValue = (value: any) => { + hookValue = value; + }; + + const newConfig = {colorScheme: ColorScheme.light}; + + render( + + + , + ); + + act(() => { + hookValue.setConfig(newConfig); + }); + + expect( + NativeModules.ShopifyCheckoutSheetKit.setConfig, + ).toHaveBeenCalledWith(newConfig); + }); + + it('provides getConfig function', async () => { + let hookValue: any; + const onHookValue = (value: any) => { + hookValue = value; + }; + + render( + + + , + ); + + await act(async () => { + const config = await hookValue.getConfig(); + expect(config).toEqual({preloading: true}); + }); + + expect(NativeModules.ShopifyCheckoutSheetKit.getConfig).toHaveBeenCalled(); + }); + + it('provides version from the instance', () => { + let hookValue: any; + const onHookValue = (value: any) => { + hookValue = value; + }; + + render( + + + , + ); + + expect(hookValue.version).toBe('0.7.0'); + }); + + it('addEventListener returns subscription object', () => { + let hookValue: any; + const onHookValue = (value: any) => { + hookValue = value; + }; + + render( + + + , + ); + + const subscription = hookValue.addEventListener('close', jest.fn()); + expect(subscription).toBeDefined(); + expect(subscription.remove).toBeDefined(); + }); +}); + +describe('ShopifyCheckoutSheetContext without provider', () => { + it('throws error when hook is used without provider', () => { + const errorSpy = jest.spyOn(console, 'error').mockImplementation(); + + expect(() => { + render( {}} />); + }).toThrow( + 'useShopifyCheckoutSheet must be used from within a ShopifyCheckoutSheetContext', + ); + + errorSpy.mockRestore(); + }); +}); diff --git a/react-native/modules/@shopify/checkout-sheet-kit/tests/index.test.ts b/react-native/modules/@shopify/checkout-sheet-kit/tests/index.test.ts new file mode 100644 index 00000000..da69755e --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/tests/index.test.ts @@ -0,0 +1,1001 @@ +/* eslint-disable no-new */ +/* eslint-disable no-console */ + +import { + LifecycleEventParseError, + ShopifyCheckoutSheet, + CheckoutErrorCode, + InternalError, + ConfigurationError, + CheckoutHTTPError, + CheckoutClientError, + CheckoutExpiredError, + GenericError, + AcceleratedCheckoutWallet, + RenderState, + LogLevel, + ColorScheme, + CheckoutNativeErrorType, + type Configuration, + type AcceleratedCheckoutConfiguration, +} from '../src'; +import type {ApplePayContactField} from '../src/index.d'; +import {TurboModuleRegistry, PermissionsAndroid, Platform} from 'react-native'; + +const NativeModule = TurboModuleRegistry.getEnforcing( + 'ShopifyCheckoutSheetKit', +) as any; + +const checkoutUrl = 'https://shopify.com/checkout'; +const config: Configuration = { + colorScheme: ColorScheme.automatic, +}; + +jest.mock('react-native'); + +global.console = { + ...global.console, + error: jest.fn(), + warn: jest.fn(), +}; + +describe('Exports', () => { + describe('AcceleratedCheckoutWallet enum', () => { + it('exports correct wallet types', () => { + expect(AcceleratedCheckoutWallet.shopPay).toBe('shopPay'); + expect(AcceleratedCheckoutWallet.applePay).toBe('applePay'); + }); + }); + + describe('RenderState enum', () => { + it('exports correct render states', () => { + expect(RenderState.Loading).toBe('loading'); + expect(RenderState.Rendered).toBe('rendered'); + expect(RenderState.Error).toBe('error'); + }); + }); + + describe('LogLevel enum', () => { + it('exports correct log levels', () => { + expect(LogLevel.debug).toBe('debug'); + expect(LogLevel.error).toBe('error'); + }); + }); +}); + +describe('ShopifyCheckoutSheetKit', () => { + // @ts-expect-error "eventEmitter is private" + const eventEmitter = ShopifyCheckoutSheet.eventEmitter; + + afterEach(() => { + NativeModule.setConfig.mockReset(); + jest.clearAllMocks(); + }); + + describe('instantiation', () => { + it('calls `setConfig` with the specified config on instantiation', () => { + new ShopifyCheckoutSheet(config); + expect( + NativeModule.setConfig, + ).toHaveBeenCalledWith(config); + }); + + it('does not call `setConfig` if no config was specified on instantiation', () => { + new ShopifyCheckoutSheet(); + expect( + NativeModule.setConfig, + ).not.toHaveBeenCalled(); + }); + }); + + describe('setConfig', () => { + it('calls the `setConfig` on the Native Module', () => { + const instance = new ShopifyCheckoutSheet(); + instance.setConfig(config); + expect( + NativeModule.setConfig, + ).toHaveBeenCalledTimes(1); + expect( + NativeModule.setConfig, + ).toHaveBeenCalledWith(config); + }); + + it('calls `setConfig` with logLevel configuration', () => { + const instance = new ShopifyCheckoutSheet(); + const configWithLogLevel: Configuration = { + colorScheme: ColorScheme.automatic, + logLevel: LogLevel.debug, + }; + instance.setConfig(configWithLogLevel); + expect( + NativeModule.setConfig, + ).toHaveBeenCalledWith(configWithLogLevel); + }); + }); + + describe('preload', () => { + it('calls `preload` with a checkout URL', () => { + const instance = new ShopifyCheckoutSheet(); + instance.preload(checkoutUrl); + expect( + NativeModule.preload, + ).toHaveBeenCalledTimes(1); + expect( + NativeModule.preload, + ).toHaveBeenCalledWith(checkoutUrl); + }); + }); + + describe('invalidate', () => { + it('calls `invalidateCache`', () => { + const instance = new ShopifyCheckoutSheet(); + instance.invalidate(); + expect( + NativeModule.invalidateCache, + ).toHaveBeenCalledTimes(1); + }); + }); + + describe('present', () => { + it('calls `present` with a checkout URL', () => { + const instance = new ShopifyCheckoutSheet(); + instance.present(checkoutUrl); + expect( + NativeModule.present, + ).toHaveBeenCalledTimes(1); + expect( + NativeModule.present, + ).toHaveBeenCalledWith(checkoutUrl); + }); + }); + + describe('dismiss', () => { + it('calls `dismiss`', () => { + const instance = new ShopifyCheckoutSheet(); + instance.dismiss(); + expect( + NativeModule.dismiss, + ).toHaveBeenCalledTimes(1); + }); + }); + + describe('getConfig', () => { + it('returns the config from the Native Module', async () => { + const instance = new ShopifyCheckoutSheet(); + await expect(instance.getConfig()).resolves.toStrictEqual({ + preloading: true, + }); + expect( + NativeModule.getConfig, + ).toHaveBeenCalledTimes(1); + }); + }); + + describe('addEventListener', () => { + it('creates a new event listener for a specific event', () => { + const instance = new ShopifyCheckoutSheet(); + const eventName = 'close'; + const callback = jest.fn(); + instance.addEventListener(eventName, callback); + expect(eventEmitter.addListener).toHaveBeenCalledWith( + eventName, + callback, + ); + }); + + describe('Pixel Events', () => { + it('parses web pixel event JSON string data', () => { + const instance = new ShopifyCheckoutSheet(); + const eventName = 'pixel'; + const callback = jest.fn(); + instance.addEventListener(eventName, callback); + NativeModule.addEventListener( + eventName, + callback, + ); + expect(eventEmitter.addListener).toHaveBeenCalledWith( + 'pixel', + expect.any(Function), + ); + eventEmitter.emit( + 'pixel', + JSON.stringify({type: 'STANDARD', someAttribute: 123}), + ); + expect(callback).toHaveBeenCalledWith({ + type: 'STANDARD', + someAttribute: 123, + }); + }); + + it('parses custom web pixel event data', () => { + const instance = new ShopifyCheckoutSheet(); + const eventName = 'pixel'; + const callback = jest.fn(); + instance.addEventListener(eventName, callback); + NativeModule.addEventListener( + eventName, + callback, + ); + expect(eventEmitter.addListener).toHaveBeenCalledWith( + 'pixel', + expect.any(Function), + ); + eventEmitter.emit( + 'pixel', + JSON.stringify({ + type: 'CUSTOM', + someAttribute: 123, + customData: JSON.stringify({valid: true}), + }), + ); + expect(callback).toHaveBeenCalledWith({ + type: 'CUSTOM', + someAttribute: 123, + customData: {valid: true}, + }); + }); + + it('fails gracefully if custom event data cannot be parsed', () => { + const instance = new ShopifyCheckoutSheet(); + const eventName = 'pixel'; + const callback = jest.fn(); + instance.addEventListener(eventName, callback); + NativeModule.addEventListener( + eventName, + callback, + ); + expect(eventEmitter.addListener).toHaveBeenCalledWith( + 'pixel', + expect.any(Function), + ); + eventEmitter.emit( + 'pixel', + JSON.stringify({ + type: 'CUSTOM', + someAttribute: 123, + customData: 'Invalid JSON', + }), + ); + expect(callback).toHaveBeenCalledWith({ + type: 'CUSTOM', + someAttribute: 123, + customData: 'Invalid JSON', + }); + }); + + it('prints an error if the web pixel event data cannot be parsed', () => { + const mock = jest.spyOn(global.console, 'error'); + const instance = new ShopifyCheckoutSheet(); + const eventName = 'pixel'; + const callback = jest.fn(); + instance.addEventListener(eventName, callback); + NativeModule.addEventListener( + eventName, + callback, + ); + expect(eventEmitter.addListener).toHaveBeenCalledWith( + 'pixel', + expect.any(Function), + ); + const invalidData = '{"someAttribute": 123'; + eventEmitter.emit('pixel', invalidData); + expect(mock).toHaveBeenCalledWith( + expect.any(LifecycleEventParseError), + invalidData, + ); + }); + + it('handles unexpected errors during event processing', () => { + const mock = jest.spyOn(global.console, 'error'); + const instance = new ShopifyCheckoutSheet(); + const eventName = 'pixel'; + const callback = jest.fn(() => { + throw new Error('Callback error'); + }); + instance.addEventListener(eventName, callback); + NativeModule.addEventListener( + eventName, + callback, + ); + expect(eventEmitter.addListener).toHaveBeenCalledWith( + 'pixel', + expect.any(Function), + ); + eventEmitter.emit('pixel', {type: 'STANDARD', someAttribute: 123}); + expect(mock).toHaveBeenCalledWith(expect.any(LifecycleEventParseError)); + }); + + it('handles falsy object event data', () => { + const instance = new ShopifyCheckoutSheet(); + const eventName = 'pixel'; + const callback = jest.fn(); + instance.addEventListener(eventName, callback); + + // Emit falsy object (null is typeof 'object') + eventEmitter.emit('pixel', null); + expect(callback).not.toHaveBeenCalled(); + }); + }); + + describe('Completed Event', () => { + it('parses completed event string data as JSON', () => { + const instance = new ShopifyCheckoutSheet(); + const eventName = 'completed'; + const callback = jest.fn(); + instance.addEventListener(eventName, callback); + NativeModule.addEventListener( + eventName, + callback, + ); + expect(eventEmitter.addListener).toHaveBeenCalledWith( + 'completed', + expect.any(Function), + ); + eventEmitter.emit( + 'completed', + JSON.stringify({orderDetails: {id: 'test-id'}}), + ); + expect(callback).toHaveBeenCalledWith({orderDetails: {id: 'test-id'}}); + }); + + it('parses completed event JSON data', () => { + const instance = new ShopifyCheckoutSheet(); + const eventName = 'completed'; + const callback = jest.fn(); + instance.addEventListener(eventName, callback); + NativeModule.addEventListener( + eventName, + callback, + ); + expect(eventEmitter.addListener).toHaveBeenCalledWith( + 'completed', + expect.any(Function), + ); + eventEmitter.emit('completed', {orderDetails: {id: 'test-id'}}); + expect(callback).toHaveBeenCalledWith({orderDetails: {id: 'test-id'}}); + }); + + it('prints an error if the completed event data cannot be parsed', () => { + const mock = jest.spyOn(global.console, 'error'); + const instance = new ShopifyCheckoutSheet(); + const eventName = 'completed'; + const callback = jest.fn(); + instance.addEventListener(eventName, callback); + NativeModule.addEventListener( + eventName, + callback, + ); + expect(eventEmitter.addListener).toHaveBeenCalledWith( + 'completed', + expect.any(Function), + ); + const invalidData = 'INVALID JSON'; + eventEmitter.emit('completed', invalidData); + expect(mock).toHaveBeenCalledWith( + expect.any(LifecycleEventParseError), + invalidData, + ); + }); + }); + + describe('Error Event', () => { + const internalError = { + __typename: CheckoutNativeErrorType.InternalError, + message: 'Something went wrong', + code: CheckoutErrorCode.unknown, + recoverable: true, + }; + + const configError = { + __typename: CheckoutNativeErrorType.ConfigurationError, + message: 'Storefront Password Required', + code: CheckoutErrorCode.storefrontPasswordRequired, + recoverable: false, + }; + + const clientError = { + __typename: CheckoutNativeErrorType.CheckoutClientError, + message: 'Storefront Password Required', + code: CheckoutErrorCode.storefrontPasswordRequired, + recoverable: false, + }; + + const networkError = { + __typename: CheckoutNativeErrorType.CheckoutHTTPError, + message: 'Checkout not found', + code: CheckoutErrorCode.httpError, + statusCode: 400, + recoverable: false, + }; + + const expiredError = { + __typename: CheckoutNativeErrorType.CheckoutExpiredError, + message: 'Customer Account Required', + code: CheckoutErrorCode.cartExpired, + recoverable: false, + }; + + it.each([ + {error: internalError, constructor: InternalError}, + {error: configError, constructor: ConfigurationError}, + {error: clientError, constructor: CheckoutClientError}, + {error: networkError, constructor: CheckoutHTTPError}, + {error: expiredError, constructor: CheckoutExpiredError}, + ])( + `correctly parses error $error`, + ({ + error, + constructor, + }: { + error: any; + constructor: new (...args: any[]) => any; + }) => { + const instance = new ShopifyCheckoutSheet(); + const eventName = 'error'; + const callback = jest.fn(); + instance.addEventListener(eventName, callback); + NativeModule.addEventListener( + eventName, + callback, + ); + expect(eventEmitter.addListener).toHaveBeenCalledWith( + 'error', + expect.any(Function), + ); + eventEmitter.emit('error', error); + const calledWith = callback.mock.calls[0][0]; + expect(calledWith).toBeInstanceOf(constructor); + expect(calledWith).not.toHaveProperty('__typename'); + expect(calledWith).toHaveProperty('code'); + expect(calledWith).toHaveProperty('message'); + expect(calledWith).toHaveProperty('recoverable'); + }, + ); + + it('returns an unknown generic error if the error cannot be parsed', () => { + const instance = new ShopifyCheckoutSheet(); + const eventName = 'error'; + const callback = jest.fn(); + instance.addEventListener(eventName, callback); + NativeModule.addEventListener( + eventName, + callback, + ); + const error = { + __typename: 'UnknownError', + message: 'Something went wrong', + }; + expect(eventEmitter.addListener).toHaveBeenCalledWith( + 'error', + expect.any(Function), + ); + eventEmitter.emit('error', error); + const calledWith = callback.mock.calls[0][0]; + expect(calledWith).toBeInstanceOf(GenericError); + expect(callback).toHaveBeenCalledWith(new GenericError(error as any)); + }); + }); + }); + + describe('removeEventListeners', () => { + it('Removes all listeners for a specific event', () => { + const instance = new ShopifyCheckoutSheet(); + instance.addEventListener('close', () => {}); + instance.addEventListener('close', () => {}); + instance.removeEventListeners('close'); + expect(eventEmitter.removeAllListeners).toHaveBeenCalledWith('close'); + }); + }); + + describe('Geolocation', () => { + const defaultConfig = {}; + + async function emitGeolocationRequest() { + await new Promise(resolve => { + eventEmitter.emit('geolocationRequest', { + origin: 'https://shopify.com', + }); + setTimeout(resolve); + }); + } + + describe('Android', () => { + const originalPlatform = Platform.OS; + + beforeEach(() => { + Platform.OS = 'android'; + }); + + afterAll(() => { + Platform.OS = originalPlatform; + }); + + it('subscribes to geolocation requests on Android when feature is enabled', () => { + new ShopifyCheckoutSheet(defaultConfig); + + expect(eventEmitter.addListener).toHaveBeenCalledWith( + 'geolocationRequest', + expect.any(Function), + ); + }); + + it('does not subscribe to geolocation requests when feature is disabled', () => { + new ShopifyCheckoutSheet(defaultConfig, { + handleGeolocationRequests: false, + }); + + expect(eventEmitter.addListener).not.toHaveBeenCalledWith( + 'geolocationRequest', + expect.any(Function), + ); + }); + + it('handles geolocation permission grant correctly', async () => { + const mockPermissions = { + 'android.permission.ACCESS_COARSE_LOCATION': 'granted', + 'android.permission.ACCESS_FINE_LOCATION': 'denied', + }; + + ( + PermissionsAndroid.requestMultiple as unknown as { + mockResolvedValue: (v: any) => void; + } + ).mockResolvedValue(mockPermissions); + + new ShopifyCheckoutSheet(); + + await emitGeolocationRequest(); + + expect(PermissionsAndroid.requestMultiple).toHaveBeenCalledWith([ + 'android.permission.ACCESS_COARSE_LOCATION', + 'android.permission.ACCESS_FINE_LOCATION', + ]); + expect( + NativeModule.initiateGeolocationRequest, + ).toHaveBeenCalledWith(true); + }); + + it('handles geolocation permission denial correctly', async () => { + const mockPermissions = { + 'android.permission.ACCESS_COARSE_LOCATION': 'denied', + 'android.permission.ACCESS_FINE_LOCATION': 'denied', + }; + + ( + PermissionsAndroid.requestMultiple as unknown as { + mockResolvedValue: (v: any) => void; + } + ).mockResolvedValue(mockPermissions); + + new ShopifyCheckoutSheet(); + + await emitGeolocationRequest(); + + expect(PermissionsAndroid.requestMultiple).toHaveBeenCalledWith([ + 'android.permission.ACCESS_COARSE_LOCATION', + 'android.permission.ACCESS_FINE_LOCATION', + ]); + expect( + NativeModule.initiateGeolocationRequest, + ).toHaveBeenCalledWith(false); + }); + + it('cleans up geolocation callback on teardown', () => { + const sheet = new ShopifyCheckoutSheet(); + const mockRemove = jest.fn(); + + // @ts-expect-error + sheet.geolocationCallback = { + remove: mockRemove, + }; + + sheet.teardown(); + + expect(mockRemove).toHaveBeenCalled(); + }); + }); + + describe('iOS', () => { + const originalPlatform = Platform.OS; + + beforeEach(() => { + Platform.OS = 'ios'; + }); + + afterAll(() => { + Platform.OS = originalPlatform; + }); + + it('does not subscribe to geolocation requests', () => { + new ShopifyCheckoutSheet(); + + expect(eventEmitter.addListener).not.toHaveBeenCalledWith( + 'geolocationRequest', + expect.any(Function), + ); + }); + + it('does not call the native function, even if an event is emitted', async () => { + new ShopifyCheckoutSheet(); + + await emitGeolocationRequest(); + + expect( + NativeModule.initiateGeolocationRequest, + ).not.toHaveBeenCalled(); + }); + + it('tears down gracefully', () => { + const sheet = new ShopifyCheckoutSheet(); + + expect(() => sheet.teardown()).not.toThrow(); + }); + }); + }); + + describe('Feature Management', () => { + it('returns true for undefined features (feature fallback)', () => { + // Create instance without any features to test fallback + const instance = new ShopifyCheckoutSheet(undefined, {}); + + // Access private method via type assertion to test featureEnabled + const featureEnabled = (instance as any).featureEnabled( + 'handleGeolocationRequests', + ); + expect(featureEnabled).toBe(true); + }); + + it('returns false when feature is explicitly disabled', () => { + // Create instance with feature explicitly disabled + const instance = new ShopifyCheckoutSheet(undefined, { + handleGeolocationRequests: false, + }); + + // Access private method via type assertion to test featureEnabled + const featureEnabled = (instance as any).featureEnabled( + 'handleGeolocationRequests', + ); + expect(featureEnabled).toBe(false); + }); + + it('returns true when feature is explicitly enabled', () => { + // Create instance with feature explicitly enabled + const instance = new ShopifyCheckoutSheet(undefined, { + handleGeolocationRequests: true, + }); + + // Access private method via type assertion to test featureEnabled + const featureEnabled = (instance as any).featureEnabled( + 'handleGeolocationRequests', + ); + expect(featureEnabled).toBe(true); + }); + }); + + describe('LifecycleEventParseError', () => { + it('creates error without Error.captureStackTrace', () => { + const originalCaptureStackTrace = Error.captureStackTrace; + delete (Error as any).captureStackTrace; + + const error = new LifecycleEventParseError('test message'); + expect(error.name).toBe('LifecycleEventParseError'); + expect(error.message).toBe('test message'); + + // Restore + if (originalCaptureStackTrace) { + Error.captureStackTrace = originalCaptureStackTrace; + } + }); + + it('creates error with Error.captureStackTrace', () => { + const mockCaptureStackTrace = jest.fn(); + Error.captureStackTrace = mockCaptureStackTrace; + + const error = new LifecycleEventParseError('test message'); + expect(error.name).toBe('LifecycleEventParseError'); + expect(mockCaptureStackTrace).toHaveBeenCalledWith( + error, + LifecycleEventParseError, + ); + }); + }); + + describe('Accelerated Checkout', () => { + const acceleratedConfig: AcceleratedCheckoutConfiguration = { + storefrontDomain: 'test-shop.myshopify.com', + storefrontAccessToken: 'shpat_test_token', + customer: { + email: 'test@example.com', + phoneNumber: '+1234567890', + accessToken: 'customer-access-token', + }, + wallets: { + applePay: { + contactFields: ['email', 'phone'] as ApplePayContactField[], + merchantIdentifier: 'merchant.com.test', + }, + }, + }; + + beforeEach(() => { + Platform.OS = 'ios'; + Platform.Version = '17.0'; + NativeModule.configureAcceleratedCheckouts.mockReset(); + NativeModule.isAcceleratedCheckoutAvailable.mockReset(); + }); + + describe('configureAcceleratedCheckouts', () => { + it('calls native configureAcceleratedCheckouts with correct parameters on iOS', async () => { + const instance = new ShopifyCheckoutSheet(); + NativeModule.configureAcceleratedCheckouts.mockResolvedValue( + true, + ); + + const result = + await instance.configureAcceleratedCheckouts(acceleratedConfig); + + expect(result).toBe(true); + expect( + NativeModule.configureAcceleratedCheckouts, + ).toHaveBeenCalledWith( + 'test-shop.myshopify.com', + 'shpat_test_token', + 'test@example.com', + '+1234567890', + 'customer-access-token', + 'merchant.com.test', + ['email', 'phone'], + [], + ); + }); + + it('calls native configureAcceleratedCheckouts with null customer data when not provided', async () => { + const instance = new ShopifyCheckoutSheet(); + const minimalConfig = { + storefrontDomain: 'test-shop.myshopify.com', + storefrontAccessToken: 'shpat_test_token', + }; + NativeModule.configureAcceleratedCheckouts.mockResolvedValue( + true, + ); + + await instance.configureAcceleratedCheckouts(minimalConfig); + + expect( + NativeModule.configureAcceleratedCheckouts, + ).toHaveBeenCalledWith( + 'test-shop.myshopify.com', + 'shpat_test_token', + null, + null, + null, + null, + [], + [], + ); + }); + + it('returns false on Android', async () => { + Platform.OS = 'android'; + const instance = new ShopifyCheckoutSheet(); + + const result = + await instance.configureAcceleratedCheckouts(acceleratedConfig); + + expect(result).toBe(false); + expect( + NativeModule.configureAcceleratedCheckouts, + ).not.toHaveBeenCalled(); + }); + + it('validates required storefrontDomain', async () => { + const instance = new ShopifyCheckoutSheet(); + const invalidConfig = { + ...acceleratedConfig, + storefrontDomain: '', + }; + const expectedError = new Error('`storefrontDomain` is required'); + + await expect( + instance.configureAcceleratedCheckouts(invalidConfig), + ).resolves.toBe(false); + expect(console.error).toHaveBeenCalledWith( + '[ShopifyCheckoutSheetKit] Failed to configure accelerated checkouts with', + expectedError, + ); + }); + + it('validates required storefrontAccessToken', async () => { + const instance = new ShopifyCheckoutSheet(); + const invalidConfig = { + ...acceleratedConfig, + storefrontAccessToken: '', + }; + + const expectedError = new Error('`storefrontAccessToken` is required'); + + await expect( + instance.configureAcceleratedCheckouts(invalidConfig), + ).resolves.toBe(false); + expect(console.error).toHaveBeenCalledWith( + '[ShopifyCheckoutSheetKit] Failed to configure accelerated checkouts with', + expectedError, + ); + }); + + it('validates required merchantIdentifier when Apple Pay is configured', async () => { + const instance = new ShopifyCheckoutSheet(); + const invalidConfig = { + ...acceleratedConfig, + wallets: { + applePay: { + contactFields: ['email'] as ApplePayContactField[], + merchantIdentifier: '', + }, + }, + }; + + const expectedError = new Error( + '`wallets.applePay.merchantIdentifier` is required', + ); + + await expect( + instance.configureAcceleratedCheckouts(invalidConfig), + ).resolves.toBe(false); + expect(console.error).toHaveBeenCalledWith( + '[ShopifyCheckoutSheetKit] Failed to configure accelerated checkouts with', + expectedError, + ); + }); + + it('validates required contactFields when Apple Pay is configured', async () => { + const instance = new ShopifyCheckoutSheet(); + const invalidConfig = { + ...acceleratedConfig, + wallets: { + applePay: { + contactFields: ['invalid'], + merchantIdentifier: 'merchant.test.com', + }, + }, + }; + + const expectedError = new Error( + `'wallets.applePay.contactFields' contains unexpected values. Expected "email, phone", received "invalid"`, + ); + + await expect( + instance.configureAcceleratedCheckouts(invalidConfig as any), + ).resolves.toBe(false); + expect(console.error).toHaveBeenCalledWith( + '[ShopifyCheckoutSheetKit] Failed to configure accelerated checkouts with', + expectedError, + ); + }); + + it('does not throw when Apple Pay wallet is not configured', async () => { + const instance = new ShopifyCheckoutSheet(); + const configWithoutApplePay = { + storefrontDomain: 'test-shop.myshopify.com', + storefrontAccessToken: 'shpat_test_token', + }; + NativeModule.configureAcceleratedCheckouts.mockResolvedValue( + true, + ); + + await expect( + instance.configureAcceleratedCheckouts(configWithoutApplePay), + ).resolves.toBe(true); + }); + + it('throws when a non-string value is given for supportedShippingCountries', async () => { + const instance = new ShopifyCheckoutSheet(); + const invalidConfig = { + ...acceleratedConfig, + wallets: { + applePay: { + contactFields: [], + merchantIdentifier: 'merchant.test.com', + supportedShippingCountries: [NaN], + }, + }, + }; + + const expectedError = new Error( + `'wallets.applePay.supportedShippingCountries' contains unexpected values. Expects ISO 3166-1 alpha-2 country codes (e.g., "US", "CA", "GB").`, + ); + + await expect( + instance.configureAcceleratedCheckouts(invalidConfig as any), + ).resolves.toBe(false); + expect(console.error).toHaveBeenCalledWith( + '[ShopifyCheckoutSheetKit] Failed to configure accelerated checkouts with', + expectedError, + ); + }); + + it('calls configureAcceleratedCheckouts with an empty array for supportShippingCountries when omitted', async () => { + const instance = new ShopifyCheckoutSheet(); + + await instance.configureAcceleratedCheckouts({ + ...acceleratedConfig, + wallets: { + applePay: { + contactFields: [], + merchantIdentifier: 'merchant.test.com', + }, + }, + }); + + expect( + NativeModule.configureAcceleratedCheckouts, + ).toHaveBeenCalledWith( + 'test-shop.myshopify.com', + 'shpat_test_token', + 'test@example.com', + '+1234567890', + 'customer-access-token', + 'merchant.test.com', + [], + [], + ); + }); + + it('calls configureAcceleratedCheckouts with supportShippingCountries when given', async () => { + const instance = new ShopifyCheckoutSheet(); + + await instance.configureAcceleratedCheckouts({ + ...acceleratedConfig, + wallets: { + applePay: { + contactFields: [], + merchantIdentifier: 'merchant.test.com', + supportedShippingCountries: ['IE', 'CA'], + }, + }, + }); + + expect( + NativeModule.configureAcceleratedCheckouts, + ).toHaveBeenCalledWith( + 'test-shop.myshopify.com', + 'shpat_test_token', + 'test@example.com', + '+1234567890', + 'customer-access-token', + 'merchant.test.com', + [], + ['IE', 'CA'], + ); + }); + }); + + describe('isAcceleratedCheckoutAvailable', () => { + it('calls native isAcceleratedCheckoutAvailable on iOS', async () => { + const instance = new ShopifyCheckoutSheet(); + NativeModule.isAcceleratedCheckoutAvailable.mockResolvedValue( + true, + ); + + const result = await instance.isAcceleratedCheckoutAvailable(); + + expect(result).toBe(true); + expect( + NativeModule.isAcceleratedCheckoutAvailable, + ).toHaveBeenCalledTimes(1); + }); + + it('returns false on Android', async () => { + Platform.OS = 'android'; + const instance = new ShopifyCheckoutSheet(); + + const result = await instance.isAcceleratedCheckoutAvailable(); + + expect(result).toBe(false); + expect( + NativeModule.isAcceleratedCheckoutAvailable, + ).not.toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/react-native/modules/@shopify/checkout-sheet-kit/tests/linking.test.ts b/react-native/modules/@shopify/checkout-sheet-kit/tests/linking.test.ts new file mode 100644 index 00000000..f83795f8 --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/tests/linking.test.ts @@ -0,0 +1,22 @@ +jest.mock('react-native', () => ({ + NativeModules: {}, + NativeEventEmitter: jest.fn(), + Platform: { + OS: 'ios', + }, + TurboModuleRegistry: { + getEnforcing: jest.fn((name: string) => { + throw new Error( + `TurboModuleRegistry.getEnforcing(...): '${name}' could not be found.`, + ); + }), + }, +})); + +describe('Native Module Linking', () => { + it('throws error when native module is not linked', () => { + expect(() => { + require('../src/index'); + }).toThrow('ShopifyCheckoutSheetKit'); + }); +}); diff --git a/react-native/modules/@shopify/checkout-sheet-kit/tsconfig.build.json b/react-native/modules/@shopify/checkout-sheet-kit/tsconfig.build.json new file mode 100644 index 00000000..0e96e3a0 --- /dev/null +++ b/react-native/modules/@shopify/checkout-sheet-kit/tsconfig.build.json @@ -0,0 +1,27 @@ + +{ + "compilerOptions": { + "rootDir": ".", + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "jsx": "react-native", + "lib": ["esnext"], + "module": "esnext", + "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noImplicitUseStrict": false, + "noStrictGenericChecks": false, + "noImplicitAny": true, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "esnext", + "verbatimModuleSyntax": true, + } +} diff --git a/react-native/package.json b/react-native/package.json new file mode 100644 index 00000000..1993ca2b --- /dev/null +++ b/react-native/package.json @@ -0,0 +1,87 @@ +{ + "name": "checkout-sheet-kit-react-native", + "description": "A React Native library for Shopify's Checkout Kit.", + "private": true, + "version": "0.0.1", + "repository": "https://github.com/Shopify/checkout-sheet-kit-react-native", + "author": "Shopify (https://github.com/Shopify/checkout-sheet-kit-react-native)", + "license": "MIT", + "bugs": { + "url": "https://github.com/Shopify/checkout-sheet-kit-react-native/issues" + }, + "homepage": "https://github.com/Shopify/checkout-sheet-kit-react-native", + "packageManager": "pnpm@10.33.1+sha512.05ba3c1d5d1c18f68df06470d74055e62d41fc110a0c660db1b2dfb2785327f04cf0f68345d4609bc52089e7fa0343c31593b2f9594e2c5d5da426230acc9820", + "pnpm": { + "onlyBuiltDependencies": [ + "unrs-resolver" + ] + }, + "scripts": { + "clean": "rm -rf node_modules; watchman watch-del . || true", + "sample": "pnpm --filter sample", + "module": "pnpm --filter @shopify/checkout-sheet-kit", + "pod-install": "(cd sample/ios && bundle install && bundle exec pod repo update && bundle exec pod cache clean --all && bundle exec pod install --repo-update)", + "snapshot": "./scripts/create_snapshot", + "compare-snapshot": "./scripts/compare_snapshot", + "turbo": "turbo", + "test": "jest" + }, + "devDependencies": { + "@babel/core": "^7.25.2", + "@babel/preset-env": "^7.25.3", + "@babel/runtime": "^7.25.0", + "@react-native-community/cli": "19.1.1", + "@react-native-community/cli-platform-android": "19.1.1", + "@react-native-community/cli-platform-ios": "19.1.1", + "@react-native/babel-preset": "0.80.2", + "@react-native/eslint-config": "0.80.2", + "@react-native/metro-config": "0.80.2", + "@react-native/typescript-config": "0.80.2", + "@testing-library/react-native": "^13.3.1", + "@tsconfig/react-native": "^3.0.6", + "@types/jest": "^29.5.13", + "@types/react": "^19.1.0", + "@types/react-native-dotenv": "^0.2.1", + "@types/react-test-renderer": "19.1.0", + "eslint": "^8.57.1", + "eslint-plugin-prettier": "^5.5.4", + "jest": "^30.0.5", + "prettier": "^3.2.5", + "react": "19.1.0", + "react-native": "0.80.2", + "react-native-dotenv": "^3.4.11", + "react-native-gesture-handler": "2.26.0", + "react-native-gradle-plugin": "^0.71.19", + "react-test-renderer": "19.1.0", + "ts-jest": "^29.4.1", + "turbo": "^1.13.4", + "typescript": "^5.9.2" + }, + "engines": { + "node": "22.14.0" + }, + "prettier": { + "arrowParens": "avoid", + "bracketSameLine": true, + "bracketSpacing": false, + "singleQuote": true, + "trailingComma": "all", + "semi": true, + "useTabs": false, + "proseWrap": "always" + }, + "keywords": [ + "react-native", + "shopify", + "checkout" + ], + "eslintConfig": { + "root": true, + "extends": "@react-native", + "rules": { + "@typescript-eslint/no-shadow": "off", + "@typescript-eslint/consistent-type-imports": "error", + "no-console": "error" + } + } +} diff --git a/react-native/pnpm-lock.yaml b/react-native/pnpm-lock.yaml new file mode 100644 index 00000000..278d55ac --- /dev/null +++ b/react-native/pnpm-lock.yaml @@ -0,0 +1,10295 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@babel/core': + specifier: ^7.25.2 + version: 7.28.3 + '@babel/preset-env': + specifier: ^7.25.3 + version: 7.28.3(@babel/core@7.28.3) + '@babel/runtime': + specifier: ^7.25.0 + version: 7.28.3 + '@react-native-community/cli': + specifier: 19.1.1 + version: 19.1.1(typescript@5.9.2) + '@react-native-community/cli-platform-android': + specifier: 19.1.1 + version: 19.1.1 + '@react-native-community/cli-platform-ios': + specifier: 19.1.1 + version: 19.1.1 + '@react-native/babel-preset': + specifier: 0.80.2 + version: 0.80.2(@babel/core@7.28.3) + '@react-native/eslint-config': + specifier: 0.80.2 + version: 0.80.2(eslint@8.57.1)(jest@30.0.5(@types/node@20.9.3))(prettier@3.2.5)(typescript@5.9.2) + '@react-native/metro-config': + specifier: 0.80.2 + version: 0.80.2(@babel/core@7.28.3) + '@react-native/typescript-config': + specifier: 0.80.2 + version: 0.80.2 + '@testing-library/react-native': + specifier: ^13.3.1 + version: 13.3.1(jest@30.0.5(@types/node@20.9.3))(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0) + '@tsconfig/react-native': + specifier: ^3.0.6 + version: 3.0.6 + '@types/jest': + specifier: ^29.5.13 + version: 29.5.14 + '@types/react': + specifier: ^19.1.0 + version: 19.1.12 + '@types/react-native-dotenv': + specifier: ^0.2.1 + version: 0.2.2 + '@types/react-test-renderer': + specifier: 19.1.0 + version: 19.1.0 + eslint: + specifier: ^8.57.1 + version: 8.57.1 + eslint-plugin-prettier: + specifier: ^5.5.4 + version: 5.5.4(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.2.5) + jest: + specifier: ^30.0.5 + version: 30.0.5(@types/node@20.9.3) + prettier: + specifier: ^3.2.5 + version: 3.2.5 + react: + specifier: 19.1.0 + version: 19.1.0 + react-native: + specifier: 0.80.2 + version: 0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + react-native-dotenv: + specifier: ^3.4.11 + version: 3.4.11(@babel/runtime@7.28.3) + react-native-gesture-handler: + specifier: 2.26.0 + version: 2.26.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + react-native-gradle-plugin: + specifier: ^0.71.19 + version: 0.71.19 + react-test-renderer: + specifier: 19.1.0 + version: 19.1.0(react@19.1.0) + ts-jest: + specifier: ^29.4.1 + version: 29.4.1(@babel/core@7.28.3)(@jest/transform@30.0.5)(@jest/types@30.0.5)(babel-jest@30.0.5(@babel/core@7.28.3))(jest-util@30.0.5)(jest@30.0.5(@types/node@20.9.3))(typescript@5.9.2) + turbo: + specifier: ^1.13.4 + version: 1.13.4 + typescript: + specifier: ^5.9.2 + version: 5.9.2 + + modules/@shopify/checkout-sheet-kit: + dependencies: + react: + specifier: '*' + version: 19.1.0 + react-native: + specifier: '*' + version: 0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + devDependencies: + react-native-builder-bob: + specifier: ^0.23.2 + version: 0.23.2 + typescript: + specifier: ^5.9.2 + version: 5.9.2 + + sample: + dependencies: + '@apollo/client': + specifier: ^3.13.9 + version: 3.14.1(@types/react@19.1.12)(graphql@16.13.2)(react@19.1.0) + '@react-native/babel-preset': + specifier: '*' + version: 0.80.2(@babel/core@7.28.3) + '@react-native/metro-config': + specifier: '*' + version: 0.80.2(@babel/core@7.28.3) + '@react-navigation/bottom-tabs': + specifier: ^7.4.6 + version: 7.15.9(@react-native-masked-view/masked-view@0.3.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(@react-navigation/native@7.2.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.7.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + '@react-navigation/native': + specifier: ^7.1.17 + version: 7.2.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + '@react-navigation/native-stack': + specifier: ^7.3.25 + version: 7.14.11(@react-native-masked-view/masked-view@0.3.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(@react-navigation/native@7.2.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.7.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + '@react-navigation/stack': + specifier: ^7.4.8 + version: 7.8.10(4bd8dd23d724a396af48ae32b42242ae) + '@shopify/checkout-sheet-kit': + specifier: workspace:* + version: link:../modules/@shopify/checkout-sheet-kit + '@types/react-native': + specifier: '*' + version: 0.73.0(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + graphql: + specifier: ^16.8.2 + version: 16.13.2 + jotai: + specifier: ^2.13.1 + version: 2.19.1(@babel/core@7.28.3)(@babel/template@7.28.6)(@types/react@19.1.12)(react@19.1.0) + react: + specifier: '*' + version: 19.1.0 + react-native: + specifier: '*' + version: 0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + react-native-config: + specifier: 1.5.6 + version: 1.5.6 + react-native-dotenv: + specifier: ^3.4.11 + version: 3.4.11(@babel/runtime@7.28.3) + react-native-encrypted-storage: + specifier: ^4.0.3 + version: 4.0.3(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + react-native-nitro-modules: + specifier: ^0.33.7 + version: 0.33.9(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + react-native-quick-base64: + specifier: ^2.2.2 + version: 2.2.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + react-native-quick-crypto: + specifier: 1.0.9 + version: 1.0.9(react-native-nitro-modules@0.33.9(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-quick-base64@2.2.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + react-native-reanimated: + specifier: 3.18.0 + version: 3.18.0(@babel/core@7.28.3)(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + react-native-safe-area-context: + specifier: ^5.6.1 + version: 5.7.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + react-native-screens: + specifier: 4.16.0 + version: 4.16.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + react-native-vector-icons: + specifier: ^10.3.0 + version: 10.3.0 + react-native-webview: + specifier: ^13.16.0 + version: 13.16.1(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + devDependencies: + '@babel/core': + specifier: ^7.20.0 + version: 7.28.3 + '@babel/plugin-proposal-nullish-coalescing-operator': + specifier: ^7.18.6 + version: 7.18.6(@babel/core@7.28.3) + '@babel/plugin-proposal-optional-chaining': + specifier: ^7.21.0 + version: 7.21.0(@babel/core@7.28.3) + '@babel/plugin-transform-arrow-functions': + specifier: ^7.27.1 + version: 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-shorthand-properties': + specifier: ^7.23.3 + version: 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-template-literals': + specifier: ^7.23.3 + version: 7.27.1(@babel/core@7.28.3) + '@babel/preset-env': + specifier: ^7.20.0 + version: 7.28.3(@babel/core@7.28.3) + '@babel/runtime': + specifier: ^7.27.6 + version: 7.28.3 + '@react-native-masked-view/masked-view': + specifier: ^0.3.2 + version: 0.3.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + '@types/react-native-vector-icons': + specifier: ^6.4.18 + version: 6.4.18 + '@types/setimmediate': + specifier: ^1 + version: 1.0.4 + babel-plugin-module-resolver: + specifier: ^5.0.0 + version: 5.0.3 + setimmediate: + specifier: ^1.0.5 + version: 1.0.5 + +packages: + + '@aashutoshrathi/word-wrap@1.2.6': + resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} + engines: {node: '>=0.10.0'} + + '@ampproject/remapping@2.2.1': + resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} + engines: {node: '>=6.0.0'} + + '@apollo/client@3.14.1': + resolution: {integrity: sha512-SgGX6E23JsZhUdG2anxiyHvEvvN6CUaI4ZfMsndZFeuHPXL3H0IsaiNAhLITSISbeyeYd+CBd9oERXQDdjXWZw==} + peerDependencies: + graphql: ^15.0.0 || ^16.0.0 + graphql-ws: ^5.5.5 || ^6.0.3 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || >=19.0.0-rc + subscriptions-transport-ws: ^0.9.0 || ^0.11.0 + peerDependenciesMeta: + graphql-ws: + optional: true + react: + optional: true + react-dom: + optional: true + subscriptions-transport-ws: + optional: true + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.0': + resolution: {integrity: sha512-60X7qkglvrap8mn1lh2ebxXdZYtUcpd7gsmy9kLaBJ4i/WdY8PqTSdxyA8qraikqKQK5C1KRBKXqznrVapyNaw==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.3': + resolution: {integrity: sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==} + engines: {node: '>=6.9.0'} + + '@babel/eslint-parser@7.28.0': + resolution: {integrity: sha512-N4ntErOlKvcbTt01rr5wj3y55xnIdx1ymrfIr8C2WnM1Y9glFgWaGDEULJIazOX3XM9NRzhfJ6zZnQ1sBNWU+w==} + engines: {node: ^10.13.0 || ^12.13.0 || >=14.0.0} + peerDependencies: + '@babel/core': ^7.11.0 + eslint: ^7.5.0 || ^8.0.0 || ^9.0.0 + + '@babel/generator@7.28.3': + resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.28.3': + resolution: {integrity: sha512-V9f6ZFIYSLNEbuGA/92uOvYsGCJNsuA8ESZ4ldc09bWk/j8H8TKiPw8Mk1eG6olpnO0ALHJmYfZvF4MEE4gajg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-create-class-features-plugin@7.28.6': + resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-create-regexp-features-plugin@7.27.1': + resolution: {integrity: sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-define-polyfill-provider@0.6.5': + resolution: {integrity: sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.27.1': + resolution: {integrity: sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-remap-async-to-generator@7.27.1': + resolution: {integrity: sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-replace-supers@7.27.1': + resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-replace-supers@7.28.6': + resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.27.1': + resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-wrap-function@7.28.3': + resolution: {integrity: sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.3': + resolution: {integrity: sha512-PTNtvUQihsAsDHMOP5pfobP8C6CM4JWXmP8DrEIt46c3r2bf87Ua1zoqevsMo9g+tWDwgWrFP5EIxuBx5RudAw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.3': + resolution: {integrity: sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1': + resolution: {integrity: sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1': + resolution: {integrity: sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1': + resolution: {integrity: sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1': + resolution: {integrity: sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.3': + resolution: {integrity: sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-proposal-class-properties@7.18.6': + resolution: {integrity: sha512-cumfXOF0+nzZrrN8Rf0t7M+tF6sZc7vhQwYQck9q1/5w2OExlD+b4v4RpMJFaV1Z7WcDRgO6FqvxqxGlwo+RHQ==} + engines: {node: '>=6.9.0'} + 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. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-export-default-from@7.27.1': + resolution: {integrity: sha512-hjlsMBl1aJc5lp8MoCDEZCiYzlgdRAShOjAfRw6X+GlpLpUPU7c3XNLsKFZbQk/1cRzBlJ7CXg3xJAJMrFa1Uw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6': + resolution: {integrity: sha512-wQxQzxYeJqHcfppzBDnm1yAY0jSRkUXR2z8RePZYrKwMKgMlE8+Z6LUno+bd6LvbGh8Gltvy74+9pIYkr+XkKA==} + engines: {node: '>=6.9.0'} + 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. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-optional-chaining@7.21.0': + resolution: {integrity: sha512-p4zeefM72gpmEe2fkUr/OnOXpWEf8nAgk7ZYVqqfFiyIG7oFfVZcCrU64hWn5xp4tQ9LkV4bTIa5rD0KANpKNA==} + engines: {node: '>=6.9.0'} + 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. + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': + resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-dynamic-import@7.8.3': + resolution: {integrity: sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-export-default-from@7.27.1': + resolution: {integrity: sha512-eBC/3KSekshx19+N40MzjWqJd7KTEdOoLesAfa4IDFI8eRz5a47i5Oszus6zG/cwIXN63YhgLOMSSNJx49sENg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-flow@7.27.1': + resolution: {integrity: sha512-p9OkPbZ5G7UT1MofwYFigGebnrzGJacoBSQM0/6bi/PUMVE+qlWDD/OalvQKbwgQzU6dl0xAv6r4X7Jme0RYxA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-assertions@7.27.1': + resolution: {integrity: sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.27.1': + resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.27.1': + resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.27.1': + resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': + resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-arrow-functions@7.27.1': + resolution: {integrity: sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-generator-functions@7.28.0': + resolution: {integrity: sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-to-generator@7.27.1': + resolution: {integrity: sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoped-functions@7.27.1': + resolution: {integrity: sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoping@7.28.0': + resolution: {integrity: sha512-gKKnwjpdx5sER/wl0WN0efUBFzF/56YZO0RJrSYP4CljXnP31ByY7fol89AzomdlLNzI36AvOTmYHsnZTCkq8Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-properties@7.27.1': + resolution: {integrity: sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-static-block@7.28.3': + resolution: {integrity: sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + + '@babel/plugin-transform-classes@7.28.3': + resolution: {integrity: sha512-DoEWC5SuxuARF2KdKmGUq3ghfPMO6ZzR12Dnp5gubwbeWJo4dbNWXJPVlwvh4Zlq6Z7YVvL8VFxeSOJgjsx4Sg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-computed-properties@7.27.1': + resolution: {integrity: sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-destructuring@7.28.0': + resolution: {integrity: sha512-v1nrSMBiKcodhsyJ4Gf+Z0U/yawmJDBOTpEB3mcQY52r9RIyPneGyAS/yM6seP/8I+mWI3elOMtT5dB8GJVs+A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-dotall-regex@7.27.1': + resolution: {integrity: sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-keys@7.27.1': + resolution: {integrity: sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1': + resolution: {integrity: sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-dynamic-import@7.27.1': + resolution: {integrity: sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-explicit-resource-management@7.28.0': + resolution: {integrity: sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-exponentiation-operator@7.27.1': + resolution: {integrity: sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-export-namespace-from@7.27.1': + resolution: {integrity: sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-flow-strip-types@7.27.1': + resolution: {integrity: sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-for-of@7.27.1': + resolution: {integrity: sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-function-name@7.27.1': + resolution: {integrity: sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-json-strings@7.27.1': + resolution: {integrity: sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-literals@7.27.1': + resolution: {integrity: sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-logical-assignment-operators@7.27.1': + resolution: {integrity: sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-member-expression-literals@7.27.1': + resolution: {integrity: sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-amd@7.27.1': + resolution: {integrity: sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.27.1': + resolution: {integrity: sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-systemjs@7.27.1': + resolution: {integrity: sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-umd@7.27.1': + resolution: {integrity: sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-named-capturing-groups-regex@7.27.1': + resolution: {integrity: sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-new-target@7.27.1': + resolution: {integrity: sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-nullish-coalescing-operator@7.27.1': + resolution: {integrity: sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-numeric-separator@7.27.1': + resolution: {integrity: sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-rest-spread@7.28.0': + resolution: {integrity: sha512-9VNGikXxzu5eCiQjdE4IZn8sb9q7Xsk5EXLDBKUYg1e/Tve8/05+KJEtcxGxAgCY5t/BpKQM+JEL/yT4tvgiUA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-super@7.27.1': + resolution: {integrity: sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-catch-binding@7.27.1': + resolution: {integrity: sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-chaining@7.27.1': + resolution: {integrity: sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-parameters@7.27.7': + resolution: {integrity: sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-methods@7.27.1': + resolution: {integrity: sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-property-in-object@7.27.1': + resolution: {integrity: sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-property-literals@7.27.1': + resolution: {integrity: sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-display-name@7.28.0': + resolution: {integrity: sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-development@7.27.1': + resolution: {integrity: sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx@7.27.1': + resolution: {integrity: sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-pure-annotations@7.27.1': + resolution: {integrity: sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regenerator@7.28.3': + resolution: {integrity: sha512-K3/M/a4+ESb5LEldjQb+XSrpY0nF+ZBFlTCbSnKaYAMfD8v33O6PMs4uYnOk19HlcsI8WMu3McdFPTiQHF/1/A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regexp-modifiers@7.27.1': + resolution: {integrity: sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-reserved-words@7.27.1': + resolution: {integrity: sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-runtime@7.28.3': + resolution: {integrity: sha512-Y6ab1kGqZ0u42Zv/4a7l0l72n9DKP/MKoKWaUSBylrhNZO2prYuqFOLbn5aW5SIFXwSH93yfjbgllL8lxuGKLg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-shorthand-properties@7.27.1': + resolution: {integrity: sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-spread@7.27.1': + resolution: {integrity: sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-sticky-regex@7.27.1': + resolution: {integrity: sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-template-literals@7.27.1': + resolution: {integrity: sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typeof-symbol@7.27.1': + resolution: {integrity: sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.28.0': + resolution: {integrity: sha512-4AEiDEBPIZvLQaWlc9liCavE0xRM0dNca41WtBeM3jgFptfUOSG9z0uteLhq6+3rq+WB6jIvUwKDTpXEHPJ2Vg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.28.6': + resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-escapes@7.27.1': + resolution: {integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-property-regex@7.27.1': + resolution: {integrity: sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-regex@7.27.1': + resolution: {integrity: sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-sets-regex@7.27.1': + resolution: {integrity: sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/preset-env@7.28.3': + resolution: {integrity: sha512-ROiDcM+GbYVPYBOeCR6uBXKkQpBExLl8k9HO1ygXEyds39j+vCCsjmj7S8GOniZQlEs81QlkdJZe76IpLSiqpg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-flow@7.27.1': + resolution: {integrity: sha512-ez3a2it5Fn6P54W8QkbfIyyIbxlXvcxyWHHvno1Wg0Ej5eiJY5hBb8ExttoIOJJk7V2dZE6prP7iby5q2aQ0Lg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-modules@0.1.6-no-external-plugins': + resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + + '@babel/preset-react@7.28.5': + resolution: {integrity: sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-typescript@7.28.5': + resolution: {integrity: sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.28.3': + resolution: {integrity: sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.3': + resolution: {integrity: sha512-7w4kZYHneL3A6NP2nxzHvT3HCZ7puDZZjFMqDpBPECub79sTtSO5CGXDkKrTQq8ksAwfD/XI2MRFX23njdDaIQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.2': + resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@craftzdog/react-native-buffer@6.1.0': + resolution: {integrity: sha512-lJXdjZ7fTllLbzDrwg/FrJLjQ5sBcAgwcqgAB6OPpXTHdCenEhHZblQpfmBLLe7/S7m0yKXL3kN3jpwOEkpjGg==} + + '@egjs/hammerjs@2.0.17': + resolution: {integrity: sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==} + engines: {node: '>=0.8.0'} + + '@emnapi/core@1.4.3': + resolution: {integrity: sha512-4m62DuCE07lw01soJwPiBGC0nAww0Q+RY70VZ+n49yDIO13yyinhbWCeNnaob0lakDtWQzSdtNWzJeOJt2ma+g==} + + '@emnapi/runtime@1.4.3': + resolution: {integrity: sha512-pBPWdu6MLKROBX05wSNKcNb++m5Er+KQ9QkB+WVM+pW2Kx9hoSrVTnu3BdkI5eBLZoKu/J6mW/B6i6bJB2ytXQ==} + + '@emnapi/wasi-threads@1.0.2': + resolution: {integrity: sha512-5n3nTJblwRi8LlXkJ9eBzu+kZR8Yxcc7ubakyQTFzPMtIhFpUBRbsnc2Dv88IZDIbCDlBiWrknhB4Lsz7mg6BA==} + + '@eslint-community/eslint-utils@4.4.0': + resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@graphql-typed-document-node/core@3.2.0': + resolution: {integrity: sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==} + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + + '@hapi/hoek@9.3.0': + resolution: {integrity: sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==} + + '@hapi/topo@5.1.0': + resolution: {integrity: sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==} + + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@isaacs/ttlcache@1.4.1': + resolution: {integrity: sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==} + engines: {node: '>=12'} + + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.3': + resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + engines: {node: '>=8'} + + '@jest/console@30.0.5': + resolution: {integrity: sha512-xY6b0XiL0Nav3ReresUarwl2oIz1gTnxGbGpho9/rbUWsLH0f1OD/VT84xs8c7VmH7MChnLb0pag6PhZhAdDiA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/core@30.0.5': + resolution: {integrity: sha512-fKD0OulvRsXF1hmaFgHhVJzczWzA1RXMMo9LTPuFXo9q/alDbME3JIyWYqovWsUBWSoBcsHaGPSLF9rz4l9Qeg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/create-cache-key-function@29.7.0': + resolution: {integrity: sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/diff-sequences@30.0.1': + resolution: {integrity: sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/environment@29.7.0': + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/environment@30.0.5': + resolution: {integrity: sha512-aRX7WoaWx1oaOkDQvCWImVQ8XNtdv5sEWgk4gxR6NXb7WBUnL5sRak4WRzIQRZ1VTWPvV4VI4mgGjNL9TeKMYA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/expect-utils@29.7.0': + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect-utils@30.0.5': + resolution: {integrity: sha512-F3lmTT7CXWYywoVUGTCmom0vXq3HTTkaZyTAzIy+bXSBizB7o5qzlC9VCtq0arOa8GqmNsbg/cE9C6HLn7Szew==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/expect@30.0.5': + resolution: {integrity: sha512-6udac8KKrtTtC+AXZ2iUN/R7dp7Ydry+Fo6FPFnDG54wjVMnb6vW/XNlf7Xj8UDjAE3aAVAsR4KFyKk3TCXmTA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/fake-timers@29.7.0': + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/fake-timers@30.0.5': + resolution: {integrity: sha512-ZO5DHfNV+kgEAeP3gK3XlpJLL4U3Sz6ebl/n68Uwt64qFFs5bv4bfEEjyRGK5uM0C90ewooNgFuKMdkbEoMEXw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/get-type@30.0.1': + resolution: {integrity: sha512-AyYdemXCptSRFirI5EPazNxyPwAL0jXt3zceFjaj8NFiKP9pOi0bfXonf6qkf82z2t3QWPeLCWWw4stPBzctLw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/globals@30.0.5': + resolution: {integrity: sha512-7oEJT19WW4oe6HR7oLRvHxwlJk2gev0U9px3ufs8sX9PoD1Eza68KF0/tlN7X0dq/WVsBScXQGgCldA1V9Y/jA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/pattern@30.0.1': + resolution: {integrity: sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/reporters@30.0.5': + resolution: {integrity: sha512-mafft7VBX4jzED1FwGC1o/9QUM2xebzavImZMeqnsklgcyxBto8mV4HzNSzUrryJ+8R9MFOM3HgYuDradWR+4g==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/schemas@30.0.5': + resolution: {integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/snapshot-utils@30.0.5': + resolution: {integrity: sha512-XcCQ5qWHLvi29UUrowgDFvV4t7ETxX91CbDczMnoqXPOIcZOxyNdSjm6kV5XMc8+HkxfRegU/MUmnTbJRzGrUQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/source-map@30.0.1': + resolution: {integrity: sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/test-result@30.0.5': + resolution: {integrity: sha512-wPyztnK0gbDMQAJZ43tdMro+qblDHH1Ru/ylzUo21TBKqt88ZqnKKK2m30LKmLLoKtR2lxdpCC/P3g1vfKcawQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/test-sequencer@30.0.5': + resolution: {integrity: sha512-Aea/G1egWoIIozmDD7PBXUOxkekXl7ueGzrsGGi1SbeKgQqCYCIf+wfbflEbf2LiPxL8j2JZGLyrzZagjvW4YQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/transform@29.7.0': + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/transform@30.0.5': + resolution: {integrity: sha512-Vk8amLQCmuZyy6GbBht1Jfo9RSdBtg7Lks+B0PecnjI8J+PCLQPGh7uI8Q/2wwpW2gLdiAfiHNsmekKlywULqg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/types@26.6.2': + resolution: {integrity: sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==} + engines: {node: '>= 10.14.2'} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@30.0.5': + resolution: {integrity: sha512-aREYa3aku9SSnea4aX6bhKn4bgv3AXkgijoQgbYV3yvbiGt6z+MQ85+6mIhx9DsKW2BuB/cLR/A+tcMThx+KLQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/resolve-uri@3.1.1': + resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.5': + resolution: {integrity: sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.30': + resolution: {integrity: sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==} + + '@napi-rs/wasm-runtime@0.2.11': + resolution: {integrity: sha512-9DPkXtvHydrcOsopiYpUgPHpmj0HWZKMUnL2dZqpvC42lsratuBG06V5ipyno0fUek5VlFsNQ+AcFATSrJXgMA==} + + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': + resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@pkgr/core@0.2.7': + resolution: {integrity: sha512-YLT9Zo3oNPJoBjBc4q8G2mjU4tqIbf5CEOORbUUr48dCD9q3umJ3IPlVqOqDakPfd2HuwccBaqlGhN4Gmr5OWg==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + + '@react-native-community/cli-clean@19.1.1': + resolution: {integrity: sha512-pP7SmK+PNw5B1Aa2c6y06FBNc9iGah/leFFM2uewpyZRJQ4zycX6Zz1UANpq9YZfp65n7NZKV9Gct2uaVRuP/Q==} + + '@react-native-community/cli-config-android@19.1.1': + resolution: {integrity: sha512-uAUXU/BPuasBy7For5lvVEpxiwA29X5BWKjM4fgxWmsQhaZHW//6PNRep94w3WVnAp+CUbW6+o3SzFqMX0PdIw==} + + '@react-native-community/cli-config-apple@19.1.1': + resolution: {integrity: sha512-dKS7pg5eAEgRB8sOWYpr6XCR/3xUcttHNsuYYbuMXfY9d0M3d0oGquuMOW/p3Ri9sJI16bRAs/YIXDF2m4gYIA==} + + '@react-native-community/cli-config@19.1.1': + resolution: {integrity: sha512-qGLYCFf3whCa/we3iKd5BY4RlcAUhSykwGpnJpjseXLaI5iJzIn/IMd70EBG8QvhV/KQxM7VFMQj6KgGcoNKYg==} + + '@react-native-community/cli-doctor@19.1.1': + resolution: {integrity: sha512-P6JgTpa8fn6SfGiotyRhiCqBlRlKx8MUUdMESPGyPzvMb8omz+Jv0ibdNg9CVT11/0x5oRsoGv07os/o+Eg0zQ==} + + '@react-native-community/cli-platform-android@19.1.1': + resolution: {integrity: sha512-omEAcIYz22Lxi/WjYHkNaUMEKV+o60PL3DJE6Wz3c4bkuDfxICJ8JcPawT4fDMsBX7DYwnYf6/Lk/leqQmHzOw==} + + '@react-native-community/cli-platform-apple@19.1.1': + resolution: {integrity: sha512-nsJ/TlQ97Lcmz5dVZVSwYYQzJmK6q/9X31VTAFhUf94ShugF3zXjaNnOJieKYDJlXy4G0EnrEulX1gTt29ebyw==} + + '@react-native-community/cli-platform-ios@19.1.1': + resolution: {integrity: sha512-QHw/eBszq+62xUBorVqjgDYsVrZ5JAYJZkc6UKO327LnVn10OUB/bPGA/FzDWZdGB77pt0IalNP8nxyGOytMfg==} + + '@react-native-community/cli-server-api@19.1.1': + resolution: {integrity: sha512-p0FFm82uPrtLZBWTD3bZ43mMBIV5mXwvGFYMcsfGiuMoS9SNbw4ImEFTG2IutVpr7Qb6NMjx6SbgYYMnTdZXmw==} + + '@react-native-community/cli-tools@19.1.1': + resolution: {integrity: sha512-0yWOdrfgO7jVtYzhNcm9hTA1hqrD6haqDaesFq4d3YCmh8lkkTb61Q/kNIKQCUfaCTR/Qcc4mdwy6ObdXRoTIQ==} + + '@react-native-community/cli-types@19.1.1': + resolution: {integrity: sha512-rOGiYjeDM9tkYBEuK6TJrnxpMhmaId1Un8pjQJswz7W9w2Vb6+nnLfWja7X7VmDIvqIK5GhVobRHsmKCKIdDEA==} + + '@react-native-community/cli@19.1.1': + resolution: {integrity: sha512-H17sV83KPg2H2GCNuUSMM1ZM2sy6msVSmxrhJSycH8ua3i9Iixja8DeYtGIcJUzjdU/4U2eSDs6PjOSZUVn8CQ==} + engines: {node: '>=18'} + hasBin: true + + '@react-native-masked-view/masked-view@0.3.2': + resolution: {integrity: sha512-XwuQoW7/GEgWRMovOQtX3A4PrXhyaZm0lVUiY8qJDvdngjLms9Cpdck6SmGAUNqQwcj2EadHC1HwL0bEyoa/SQ==} + peerDependencies: + react: '>=16' + react-native: '>=0.57' + + '@react-native/assets-registry@0.80.2': + resolution: {integrity: sha512-+sI2zIM22amhkZqW+RpD3qDoopeRiezrTtZMP+Y3HI+6/2JbEq7DdyV/2YS1lrSSdyy3STW2V37Lt4dKqP0lEQ==} + engines: {node: '>=18'} + + '@react-native/babel-plugin-codegen@0.80.2': + resolution: {integrity: sha512-q0XzdrdDebPwt5tEi2MSo90kpEcs4e3ZZskrbxda081DEjHhgm3bbIxAiW3BxY6adOf/eXxgOhKEGWTfG2me6g==} + engines: {node: '>=18'} + + '@react-native/babel-preset@0.80.2': + resolution: {integrity: sha512-vLtS8YJV0nAnOZ8kVJBaXzHlwvoMXpYB4/NBR1BuAesE+WTiAkXpDFnKSkXBHoS03d/5HYNVcW8VRaB2f0Jmtw==} + engines: {node: '>=18'} + peerDependencies: + '@babel/core': '*' + + '@react-native/codegen@0.80.2': + resolution: {integrity: sha512-eYad9ex9/RS6oFbbpu6LxsczktbhfJbJlTvtRlcWLJjJbFTeNr5Q7CgBT2/m5VtpxnJ/0YdmZ9vdazsJ2yp9kw==} + engines: {node: '>=18'} + peerDependencies: + '@babel/core': '*' + + '@react-native/community-cli-plugin@0.80.2': + resolution: {integrity: sha512-UBjsE+lv1YtThs56mgFaUdWv0jNE1oO58Lkbf3dn47F0e7YiTubIcvP6AnlaMhZF2Pmt9ky8J1jTpgItO9tGeg==} + engines: {node: '>=18'} + peerDependencies: + '@react-native-community/cli': '*' + peerDependenciesMeta: + '@react-native-community/cli': + optional: true + + '@react-native/debugger-frontend@0.80.2': + resolution: {integrity: sha512-n3D88bqNk0bY+YjNxbM6giqva06xj+rgEfu91Pg+nJ0szSL2eLl7ULERJqI3hxFt0XGuTpTOxZgw/Po5maXa4g==} + engines: {node: '>=18'} + + '@react-native/dev-middleware@0.80.2': + resolution: {integrity: sha512-8OeBEZNiApdbZaqTrrzeyFwXn/JwgJox7jdtjVAH56DggTVJXdbnyUjQ4ts6XAacEQgpFOAskoO730eyafOkAA==} + engines: {node: '>=18'} + + '@react-native/eslint-config@0.80.2': + resolution: {integrity: sha512-gcuMYuuG7XfM0gkDUHoWuOmXFQwFo6U4wkspJCnYnOuTB/uS/qghM2/CnpZVIhS9toFiQ1NO3ib829rdFz5NPg==} + engines: {node: '>=18'} + peerDependencies: + eslint: '>=8' + prettier: '>=2' + + '@react-native/eslint-plugin@0.80.2': + resolution: {integrity: sha512-I0HDmMGtChz6s9EShHasgZfiMUc6XtCAPMjR//jqTTav746eng98Z/zk25/nVeGCCD+X8KtlzHH9AUUqYv5sNQ==} + engines: {node: '>=18'} + + '@react-native/gradle-plugin@0.80.2': + resolution: {integrity: sha512-C5/FYbIfCXPFjF/hIcWFKC9rEadDDhPMbxE7tarGR9tmYKyb9o7fYvfNe8fFgbCRKelMHP0ShATz3T73pHHDfA==} + engines: {node: '>=18'} + + '@react-native/js-polyfills@0.80.2': + resolution: {integrity: sha512-f63M3paxHK92p6L9o+AY7hV/YojCZAhb+fdDpSfOtDtCngWbBhd6foJrO6IybzDFERxlwErupUg3pqr5w3KJWw==} + engines: {node: '>=18'} + + '@react-native/metro-babel-transformer@0.80.2': + resolution: {integrity: sha512-y5KqJpxxbTGIlPgnEeZmNesYyhHfhYKK3mSgeN49VRAj1kinauoXyowfRbpns5EvBzhcoe4uycI+n17U+BmqSA==} + engines: {node: '>=18'} + peerDependencies: + '@babel/core': '*' + + '@react-native/metro-config@0.80.2': + resolution: {integrity: sha512-Xn9q70ySQn4VfsYozRk3brLXyA8joqcFxZdYSNOzbwmXqqc30AC1KqNsjFnK+nE6H/JeqT2hCd+86OzkPzYVzA==} + engines: {node: '>=18'} + + '@react-native/normalize-colors@0.80.2': + resolution: {integrity: sha512-08Ax7554Z31NXi5SQ6h1GsiSrlZEOYHQNSC7u+x91Tdiq87IXldW8Ib1N3ThXoDcD8bjr+I+MdlabEJw36/fFg==} + + '@react-native/typescript-config@0.80.2': + resolution: {integrity: sha512-bq2WgQD0xN+VKBWCDSa7iKbJRUCVNjxjbB7Yzai8EKPSkoYjp08LeoEzPRr1wXJGeFI3OOwsSm/MZbUr72KmNA==} + + '@react-native/virtualized-lists@0.80.2': + resolution: {integrity: sha512-kXsIV2eB73QClbbH/z/lRhZkyj3Dke4tarM5w2yXSNwJthMPMfj4KqLZ6Lnf0nmPPjz7qo/voKtlrGqlM822Rg==} + engines: {node: '>=18'} + peerDependencies: + '@types/react': ^19.0.0 + react: '*' + react-native: '*' + peerDependenciesMeta: + '@types/react': + optional: true + + '@react-navigation/bottom-tabs@7.15.9': + resolution: {integrity: sha512-Ou28A1aZLj5wiFQ3F93aIsrI4NCwn3IJzkkjNo9KLFXsc0Yks+UqrVaFlffHFLsrbajuGRG/OQpnMA1ljayY5Q==} + peerDependencies: + '@react-navigation/native': ^7.2.2 + react: '>= 18.2.0' + react-native: '*' + react-native-safe-area-context: '>= 4.0.0' + react-native-screens: '>= 4.0.0' + + '@react-navigation/core@7.17.2': + resolution: {integrity: sha512-Rt2OZwcgOmjv401uLGAKaRM6xo0fiBce/A7LfRHI1oe5FV+KooWcgAoZ2XOtgKj6UzVMuQWt3b2e6rxo/mDJRA==} + peerDependencies: + react: '>= 18.2.0' + + '@react-navigation/elements@2.9.14': + resolution: {integrity: sha512-lKqzu+su2pI/YIZmR7L7xdOs4UL+rVXKJAMpRMBrwInEy96SjIFst6QDGpE89Dunnu3VjVpjWfByo9f2GWBHDQ==} + peerDependencies: + '@react-native-masked-view/masked-view': '>= 0.2.0' + '@react-navigation/native': ^7.2.2 + react: '>= 18.2.0' + react-native: '*' + react-native-safe-area-context: '>= 4.0.0' + peerDependenciesMeta: + '@react-native-masked-view/masked-view': + optional: true + + '@react-navigation/native-stack@7.14.11': + resolution: {integrity: sha512-1ufBtJ7KbVFlQhXsYSYHqjgkmP30AzJSgW48YjWMQZ3NZGAyYe34w9Wd4KpdebQCfDClPe9maU+8crA/awa6lQ==} + peerDependencies: + '@react-navigation/native': ^7.2.2 + react: '>= 18.2.0' + react-native: '*' + react-native-safe-area-context: '>= 4.0.0' + react-native-screens: '>= 4.0.0' + + '@react-navigation/native@7.2.2': + resolution: {integrity: sha512-kem1Ko2BcbAjmbQIv66dNmr6EtfDut3QU0qjsVhMnLLhktwyXb6FzZYp8gTrUb6AvkAbaJoi+BF5Pl55pAUa5w==} + peerDependencies: + react: '>= 18.2.0' + react-native: '*' + + '@react-navigation/routers@7.5.3': + resolution: {integrity: sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg==} + + '@react-navigation/stack@7.8.10': + resolution: {integrity: sha512-Nwa9VOPAa7hD5Z+5S2Z3jLN8zUZwRJtz2NthFd0PKFzGPUS+6qw69PAO6J99qNBxhc+SEJ6nP+9o0ViLbCLs/A==} + peerDependencies: + '@react-navigation/native': ^7.2.2 + react: '>= 18.2.0' + react-native: '*' + react-native-gesture-handler: '>= 2.0.0' + react-native-safe-area-context: '>= 4.0.0' + react-native-screens: '>= 4.0.0' + + '@sideway/address@4.1.5': + resolution: {integrity: sha512-IqO/DUQHUkPeixNQ8n0JA6102hT9CmaljNTPmQ1u8MEhBo/R4Q8eKLN/vGZxuebwOroDB4cbpjheD4+/sKFK4Q==} + + '@sideway/formula@3.0.1': + resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} + + '@sideway/pinpoint@2.0.0': + resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} + + '@sinclair/typebox@0.27.8': + resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} + + '@sinclair/typebox@0.34.37': + resolution: {integrity: sha512-2TRuQVgQYfy+EzHRTIvkhv2ADEouJ2xNS/Vq+W5EuuewBdOrvATvljZTxHWZSTYr2sTjTHpGvucaGAt67S2akw==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@10.3.0': + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + + '@sinonjs/fake-timers@13.0.5': + resolution: {integrity: sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==} + + '@testing-library/react-native@13.3.1': + resolution: {integrity: sha512-o7aGggPVEl/Samx26ubACT0nuQn8/n+1VfDZ3lJ6SFrK9IYkEvaMY2M5hHg9kYcImNFlgsmqc3FgN1VaLAkjyA==} + engines: {node: '>=18'} + peerDependencies: + jest: '>=29.0.0' + react: '>=18.2.0' + react-native: '>=0.71' + react-test-renderer: '>=18.2.0' + peerDependenciesMeta: + jest: + optional: true + + '@tsconfig/react-native@3.0.6': + resolution: {integrity: sha512-cWG+s5ZJfEBhaJbCs8QqeWhGbYHhUoq93+wOAdGzh1k/m7FkEmJkUTVsCVJ+rhLpwTNIVrLaHL/IUfBne5D6mw==} + + '@tybys/wasm-util@0.9.0': + resolution: {integrity: sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.6.7': + resolution: {integrity: sha512-6Sfsq+EaaLrw4RmdFWE9Onp63TOUue71AWb4Gpa6JxzgTYtimbM086WnYTy2U67AofR++QKCo08ZP6pwx8YFHQ==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.20.4': + resolution: {integrity: sha512-mSM/iKUk5fDDrEV/e83qY+Cr3I1+Q3qqTuEn++HAWYjEa1+NxZr6CNrcJGf2ZTnq4HoFGC3zaTPZTobCzCFukA==} + + '@types/graceful-fs@4.1.9': + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + + '@types/hammerjs@2.0.46': + resolution: {integrity: sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/jest@29.5.14': + resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@20.9.3': + resolution: {integrity: sha512-nk5wXLAXGBKfrhLB0cyHGbSqopS+nz0BUgZkUQqSHSSgdee0kssp1IAqlQOu333bW+gMNs2QREx7iynm19Abxw==} + + '@types/parse-json@4.0.2': + resolution: {integrity: sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==} + + '@types/react-native-dotenv@0.2.2': + resolution: {integrity: sha512-YDgO2hdTK5PaxZrIFtVXrjeFOhJ+7A9a8VDUK4QmHCPGIB5i6DroLG9IpItX5qCshz7aPsQfgy9X3w82Otd4HA==} + + '@types/react-native-vector-icons@6.4.18': + resolution: {integrity: sha512-YGlNWb+k5laTBHd7+uZowB9DpIK3SXUneZqAiKQaj1jnJCZM0x71GDim5JCTMi4IFkhc9m8H/Gm28T5BjyivUw==} + + '@types/react-native@0.70.19': + resolution: {integrity: sha512-c6WbyCgWTBgKKMESj/8b4w+zWcZSsCforson7UdXtXMecG3MxCinYi6ihhrHVPyUrVzORsvEzK8zg32z4pK6Sg==} + + '@types/react-native@0.73.0': + resolution: {integrity: sha512-6ZRPQrYM72qYKGWidEttRe6M5DZBEV5F+MHMHqd4TTYx0tfkcdrUFGdef6CCxY0jXU7wldvd/zA/b0A/kTeJmA==} + deprecated: This is a stub types definition. react-native provides its own type definitions, so you do not need this installed. + + '@types/react-test-renderer@19.1.0': + resolution: {integrity: sha512-XD0WZrHqjNrxA/MaR9O22w/RNidWR9YZmBdRGI7wcnWGrv/3dA8wKCJ8m63Sn+tLJhcjmuhOi629N66W6kgWzQ==} + + '@types/react@19.1.12': + resolution: {integrity: sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==} + + '@types/semver@7.5.6': + resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} + + '@types/setimmediate@1.0.4': + resolution: {integrity: sha512-rWPw1drMVf5zInxNpgH3nn/h6KkWqwgLT2y/ciAYQ16RAsbXOXe0AmtZ/HyzwPNw+r4GMJuI7IV7YNKO7Fs/xA==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@15.0.19': + resolution: {integrity: sha512-2XUaGVmyQjgyAZldf0D0c14vvo/yv0MhQBSTJcejMMaitsn3nxCB6TmH4G0ZQf+uxROOa9mpanoSm8h6SG/1ZA==} + + '@types/yargs@17.0.33': + resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} + + '@typescript-eslint/eslint-plugin@7.18.0': + resolution: {integrity: sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + '@typescript-eslint/parser': ^7.0.0 + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@7.18.0': + resolution: {integrity: sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@5.62.0': + resolution: {integrity: sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/scope-manager@7.18.0': + resolution: {integrity: sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/type-utils@7.18.0': + resolution: {integrity: sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@5.62.0': + resolution: {integrity: sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/types@7.18.0': + resolution: {integrity: sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@typescript-eslint/typescript-estree@5.62.0': + resolution: {integrity: sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/typescript-estree@7.18.0': + resolution: {integrity: sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@5.62.0': + resolution: {integrity: sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + + '@typescript-eslint/utils@7.18.0': + resolution: {integrity: sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==} + engines: {node: ^18.18.0 || >=20.0.0} + peerDependencies: + eslint: ^8.56.0 + + '@typescript-eslint/visitor-keys@5.62.0': + resolution: {integrity: sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@typescript-eslint/visitor-keys@7.18.0': + resolution: {integrity: sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==} + engines: {node: ^18.18.0 || >=20.0.0} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@unrs/resolver-binding-android-arm-eabi@1.9.2': + resolution: {integrity: sha512-tS+lqTU3N0kkthU+rYp0spAYq15DU8ld9kXkaKg9sbQqJNF+WPMuNHZQGCgdxrUOEO0j22RKMwRVhF1HTl+X8A==} + cpu: [arm] + os: [android] + + '@unrs/resolver-binding-android-arm64@1.9.2': + resolution: {integrity: sha512-MffGiZULa/KmkNjHeuuflLVqfhqLv1vZLm8lWIyeADvlElJ/GLSOkoUX+5jf4/EGtfwrNFcEaB8BRas03KT0/Q==} + cpu: [arm64] + os: [android] + + '@unrs/resolver-binding-darwin-arm64@1.9.2': + resolution: {integrity: sha512-dzJYK5rohS1sYl1DHdJ3mwfwClJj5BClQnQSyAgEfggbUwA9RlROQSSbKBLqrGfsiC/VyrDPtbO8hh56fnkbsQ==} + cpu: [arm64] + os: [darwin] + + '@unrs/resolver-binding-darwin-x64@1.9.2': + resolution: {integrity: sha512-gaIMWK+CWtXcg9gUyznkdV54LzQ90S3X3dn8zlh+QR5Xy7Y+Efqw4Rs4im61K1juy4YNb67vmJsCDAGOnIeffQ==} + cpu: [x64] + os: [darwin] + + '@unrs/resolver-binding-freebsd-x64@1.9.2': + resolution: {integrity: sha512-S7QpkMbVoVJb0xwHFwujnwCAEDe/596xqY603rpi/ioTn9VDgBHnCCxh+UFrr5yxuMH+dliHfjwCZJXOPJGPnw==} + cpu: [x64] + os: [freebsd] + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.9.2': + resolution: {integrity: sha512-+XPUMCuCCI80I46nCDFbGum0ZODP5NWGiwS3Pj8fOgsG5/ctz+/zzuBlq/WmGa+EjWZdue6CF0aWWNv84sE1uw==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm-musleabihf@1.9.2': + resolution: {integrity: sha512-sqvUyAd1JUpwbz33Ce2tuTLJKM+ucSsYpPGl2vuFwZnEIg0CmdxiZ01MHQ3j6ExuRqEDUCy8yvkDKvjYFPb8Zg==} + cpu: [arm] + os: [linux] + + '@unrs/resolver-binding-linux-arm64-gnu@1.9.2': + resolution: {integrity: sha512-UYA0MA8ajkEDCFRQdng/FVx3F6szBvk3EPnkTTQuuO9lV1kPGuTB+V9TmbDxy5ikaEgyWKxa4CI3ySjklZ9lFA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-arm64-musl@1.9.2': + resolution: {integrity: sha512-P/CO3ODU9YJIHFqAkHbquKtFst0COxdphc8TKGL5yCX75GOiVpGqd1d15ahpqu8xXVsqP4MGFP2C3LRZnnL5MA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-ppc64-gnu@1.9.2': + resolution: {integrity: sha512-uKStFlOELBxBum2s1hODPtgJhY4NxYJE9pAeyBgNEzHgTqTiVBPjfTlPFJkfxyTjQEuxZbbJlJnMCrRgD7ubzw==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-riscv64-gnu@1.9.2': + resolution: {integrity: sha512-LkbNnZlhINfY9gK30AHs26IIVEZ9PEl9qOScYdmY2o81imJYI4IMnJiW0vJVtXaDHvBvxeAgEy5CflwJFIl3tQ==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-riscv64-musl@1.9.2': + resolution: {integrity: sha512-vI+e6FzLyZHSLFNomPi+nT+qUWN4YSj8pFtQZSFTtmgFoxqB6NyjxSjAxEC1m93qn6hUXhIsh8WMp+fGgxCoRg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-linux-s390x-gnu@1.9.2': + resolution: {integrity: sha512-sSO4AlAYhSM2RAzBsRpahcJB1msc6uYLAtP6pesPbZtptF8OU/CbCPhSRW6cnYOGuVmEmWVW5xVboAqCnWTeHQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-x64-gnu@1.9.2': + resolution: {integrity: sha512-jkSkwch0uPFva20Mdu8orbQjv2A3G88NExTN2oPTI1AJ+7mZfYW3cDCTyoH6OnctBKbBVeJCEqh0U02lTkqD5w==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@unrs/resolver-binding-linux-x64-musl@1.9.2': + resolution: {integrity: sha512-Uk64NoiTpQbkpl+bXsbeyOPRpUoMdcUqa+hDC1KhMW7aN1lfW8PBlBH4mJ3n3Y47dYE8qi0XTxy1mBACruYBaw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@unrs/resolver-binding-wasm32-wasi@1.9.2': + resolution: {integrity: sha512-EpBGwkcjDicjR/ybC0g8wO5adPNdVuMrNalVgYcWi+gYtC1XYNuxe3rufcO7dA76OHGeVabcO6cSkPJKVcbCXQ==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + + '@unrs/resolver-binding-win32-arm64-msvc@1.9.2': + resolution: {integrity: sha512-EdFbGn7o1SxGmN6aZw9wAkehZJetFPao0VGZ9OMBwKx6TkvDuj6cNeLimF/Psi6ts9lMOe+Dt6z19fZQ9Ye2fw==} + cpu: [arm64] + os: [win32] + + '@unrs/resolver-binding-win32-ia32-msvc@1.9.2': + resolution: {integrity: sha512-JY9hi1p7AG+5c/dMU8o2kWemM8I6VZxfGwn1GCtf3c5i+IKcMo2NQ8OjZ4Z3/itvY/Si3K10jOBQn7qsD/whUA==} + cpu: [ia32] + os: [win32] + + '@unrs/resolver-binding-win32-x64-msvc@1.9.2': + resolution: {integrity: sha512-ryoo+EB19lMxAd80ln9BVf8pdOAxLb97amrQ3SFN9OCRn/5M5wvwDgAe4i8ZjhpbiHoDeP8yavcTEnpKBo7lZg==} + cpu: [x64] + os: [win32] + + '@vscode/sudo-prompt@9.3.1': + resolution: {integrity: sha512-9ORTwwS74VaTn38tNbQhsA5U44zkJfcb0BdTSyyG6frP4e8KMtHuTXYmwefe5dpL8XB1aGSIVTaLjD3BbWb5iA==} + + '@wry/caches@1.0.1': + resolution: {integrity: sha512-bXuaUNLVVkD20wcGBWRyo7j9N3TxePEWFZj2Y+r9OoUzfqmavM84+mFykRicNsBqatba5JLay1t48wxaXaWnlA==} + engines: {node: '>=8'} + + '@wry/context@0.7.4': + resolution: {integrity: sha512-jmT7Sb4ZQWI5iyu3lobQxICu2nC/vbUhP0vIdd6tHC9PTfenmRmuIFqktc6GH9cgi+ZHnsLWPvfSvc4DrYmKiQ==} + engines: {node: '>=8'} + + '@wry/equality@0.5.7': + resolution: {integrity: sha512-BRFORjsTuQv5gxcXsuDXx6oGRhuVsEGwZy6LOzRRfgu+eSfxbhUQ9L9YtSEIuIjY/o7g3iWFjrc5eSY1GXP2Dw==} + engines: {node: '>=8'} + + '@wry/trie@0.5.0': + resolution: {integrity: sha512-FNoYzHawTMk/6KMQoEG5O4PuioX19UbwdQKF44yw0nLfOypfQdjtfZzo/UIJWAJ23sNIFbD1Ug9lbaDGMwbqQA==} + engines: {node: '>=8'} + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.11.2: + resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + + ajv@6.12.6: + resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + + anser@1.4.10: + resolution: {integrity: sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-fragments@0.2.1: + resolution: {integrity: sha512-DykbNHxuXQwUDRv5ibc2b0x7uw7wmwOGLBUd5RmaQ5z8Lhx19vwvKV+FAsM5rEA6dEcHxX+/Ad5s9eF2k2bB+w==} + + ansi-regex@4.1.1: + resolution: {integrity: sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==} + engines: {node: '>=6'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + appdirsjs@1.2.7: + resolution: {integrity: sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + array-buffer-byte-length@1.0.0: + resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} + + array-includes@3.1.7: + resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==} + engines: {node: '>= 0.4'} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + array.prototype.flat@1.3.2: + resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.2: + resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.2: + resolution: {integrity: sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==} + + arraybuffer.prototype.slice@1.0.2: + resolution: {integrity: sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==} + engines: {node: '>= 0.4'} + + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + + astral-regex@1.0.0: + resolution: {integrity: sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==} + engines: {node: '>=4'} + + async-limiter@1.0.1: + resolution: {integrity: sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==} + + asynciterator.prototype@1.0.0: + resolution: {integrity: sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==} + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + babel-jest@29.7.0: + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + + babel-jest@30.0.5: + resolution: {integrity: sha512-mRijnKimhGDMsizTvBTWotwNpzrkHr+VvZUQBof2AufXKB8NXrL1W69TG20EvOz7aevx6FTJIaBuBkYxS8zolg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@babel/core': ^7.11.0 + + babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + + babel-plugin-istanbul@7.0.0: + resolution: {integrity: sha512-C5OzENSx/A+gt7t4VH1I2XsflxyPUmXRFPKBxt33xncdOmq7oROVM3bZv9Ysjjkv8OJYDMa+tKuKMvqU/H3xdw==} + engines: {node: '>=12'} + + babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + babel-plugin-jest-hoist@30.0.1: + resolution: {integrity: sha512-zTPME3pI50NsFW8ZBaVIOeAxzEY7XHlmWeXXu9srI+9kNfzCUTy8MFan46xOGZY8NZThMqq+e3qZUKsvXbasnQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + babel-plugin-module-resolver@5.0.3: + resolution: {integrity: sha512-h8h6H71ZvdLJZxZrYkaeR30BojTaV7O9GfqacY14SNj5CNB8ocL9tydNzTC0JrnNN7vY3eJhwCmkDj7tuEUaqQ==} + + babel-plugin-polyfill-corejs2@0.4.14: + resolution: {integrity: sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.13.0: + resolution: {integrity: sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.5: + resolution: {integrity: sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-syntax-hermes-parser@0.28.1: + resolution: {integrity: sha512-meT17DOuUElMNsL5LZN56d+KBp22hb0EfxWfuPUeoSi54e40v1W4C2V36P75FpsH9fVEfDKpw5Nnkahc8haSsQ==} + + babel-plugin-transform-flow-enums@0.0.2: + resolution: {integrity: sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==} + + babel-preset-current-node-syntax@1.1.0: + resolution: {integrity: sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==} + peerDependencies: + '@babel/core': ^7.0.0 + + babel-preset-jest@29.6.3: + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + + babel-preset-jest@30.0.1: + resolution: {integrity: sha512-+YHejD5iTWI46cZmcc/YtX4gaKBtdqCHCVfuVinizVpbmyjO3zYmeuyFdfA8duRqQZfgCAMlsfmkVbJ+e2MAJw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@babel/core': ^7.11.0 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + body-parser@1.20.3: + resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + brace-expansion@1.1.11: + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.25.3: + resolution: {integrity: sha512-cDGv1kkDI4/0e5yON9yM5G/0A5u8sf5TnmdX5C9qHzI9PPu++sQ9zjm1k9NiOrf3riY4OkK0zSGqfvJyJsgCBQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + caller-callsite@2.0.0: + resolution: {integrity: sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==} + engines: {node: '>=4'} + + caller-path@2.0.0: + resolution: {integrity: sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==} + engines: {node: '>=4'} + + callsites@2.0.0: + resolution: {integrity: sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==} + engines: {node: '>=4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001737: + resolution: {integrity: sha512-BiloLiXtQNrY5UyF0+1nSJLXUENuhka2pzy2Fx5pGxqavdrxSCW4U6Pn/PoG3Efspi2frRbHpBV2XsrPE6EDlw==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + chrome-launcher@0.15.2: + resolution: {integrity: sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==} + engines: {node: '>=12.13.0'} + hasBin: true + + chromium-edge-launcher@0.2.0: + resolution: {integrity: sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==} + + ci-info@2.0.0: + resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + ci-info@4.2.0: + resolution: {integrity: sha512-cYY9mypksY8NRqgDB1XD1RiJL338v/551niynFTGkZOO2LHuB2OmOYxDIe/ttN9AHwrqdum1360G3ald0W9kCg==} + engines: {node: '>=8'} + + cjs-module-lexer@2.1.0: + resolution: {integrity: sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA==} + + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + cliui@6.0.0: + resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} + + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + collect-v8-coverage@1.0.2: + resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + + color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + + colorette@1.4.0: + resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + + command-exists@1.2.9: + resolution: {integrity: sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w==} + + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@9.5.0: + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} + engines: {node: ^12.20.0 || >=14} + + compressible@2.0.18: + resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} + engines: {node: '>= 0.6'} + + compression@1.8.1: + resolution: {integrity: sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==} + engines: {node: '>= 0.8.0'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + connect@3.7.0: + resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} + engines: {node: '>= 0.10.0'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + core-js-compat@3.45.1: + resolution: {integrity: sha512-tqTt5T4PzsMIZ430XGviK4vzYSoeNJ6CXODi6c/voxOT6IZqBht5/EKaSNnYiEjjRYxjVz7DQIsOsY0XNi8PIA==} + + cosmiconfig@5.2.1: + resolution: {integrity: sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==} + engines: {node: '>=4'} + + cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + + cosmiconfig@9.0.0: + resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + csstype@3.1.2: + resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} + + dayjs@1.11.15: + resolution: {integrity: sha512-MC+DfnSWiM9APs7fpiurHGCoeIx0Gdl6QZBy+5lu8MbYKN5FZEXqOgrundfibdfhGZ15o9hzmZ2xJjZnbvgKXQ==} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decamelize@1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + + decode-uri-component@0.2.2: + resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==} + engines: {node: '>=0.10'} + + dedent@0.7.0: + resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} + + dedent@1.6.0: + resolution: {integrity: sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + del@6.1.1: + resolution: {integrity: sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==} + engines: {node: '>=10'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.209: + resolution: {integrity: sha512-Xoz0uMrim9ZETCQt8UgM5FxQF9+imA7PBpokoGcZloA1uw2LeHzTlip5cb5KOAsXZLjh/moN2vReN3ZjJmjI9A==} + + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encodeurl@1.0.2: + resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} + engines: {node: '>= 0.8'} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + envinfo@7.14.0: + resolution: {integrity: sha512-CO40UI41xDQzhLB1hWyqUKgFhs250pNcGbyGKe1l/e4FSaI/+YE4IMG76GDt0In67WLPACIITC+sOi08x4wIvg==} + engines: {node: '>=4'} + hasBin: true + + error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + + error-stack-parser@2.1.4: + resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} + + errorhandler@1.5.1: + resolution: {integrity: sha512-rcOwbfvP1WTViVoUjcfZicVzjhjTuhSMntHh6mW3IrEiyE6mJyXvsToJUJGlGlw/2xU9P5whlWNGlIDVeCiT4A==} + engines: {node: '>= 0.8'} + + es-abstract@1.22.3: + resolution: {integrity: sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-iterator-helpers@1.0.15: + resolution: {integrity: sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.0.2: + resolution: {integrity: sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.0.2: + resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} + + es-to-primitive@1.2.1: + resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-config-prettier@8.10.0: + resolution: {integrity: sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==} + hasBin: true + peerDependencies: + eslint: '>=7.0.0' + + eslint-plugin-eslint-comments@3.2.0: + resolution: {integrity: sha512-0jkOl0hfojIHHmEHgmNdqv4fmh7300NdpA9FFpF7zaoLvB/QeXOGNLIo86oAveJFrfB1p05kC8hpEMHM8DwWVQ==} + engines: {node: '>=6.5.0'} + peerDependencies: + eslint: '>=4.19.1' + + eslint-plugin-ft-flow@2.0.3: + resolution: {integrity: sha512-Vbsd/b+LYA99jUbsL6viEUWShFaYQt2YQs3QN3f+aeszOhh2sgdcU0mjzDyD4yyBvMc8qy2uwvBBWfMzEX06tg==} + engines: {node: '>=12.22.0'} + peerDependencies: + '@babel/eslint-parser': ^7.12.0 + eslint: ^8.1.0 + + eslint-plugin-jest@27.9.0: + resolution: {integrity: sha512-QIT7FH7fNmd9n4se7FFKHbsLKGQiw885Ds6Y/sxKgCZ6natwCsXdgPOADnYVxN2QrRweF0FZWbJ6S7Rsn7llug==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/eslint-plugin': ^5.0.0 || ^6.0.0 || ^7.0.0 + eslint: ^7.0.0 || ^8.0.0 + jest: '*' + peerDependenciesMeta: + '@typescript-eslint/eslint-plugin': + optional: true + jest: + optional: true + + eslint-plugin-prettier@5.5.4: + resolution: {integrity: sha512-swNtI95SToIz05YINMA6Ox5R057IMAmWZ26GqPxusAp1TZzj+IdY9tXNWWD3vkF/wEqydCONcwjTFpxybBqZsg==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + '@types/eslint': '>=8.0.0' + eslint: '>=8.0.0' + eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0' + prettier: '>=3.0.0' + peerDependenciesMeta: + '@types/eslint': + optional: true + eslint-config-prettier: + optional: true + + eslint-plugin-react-hooks@5.2.0: + resolution: {integrity: sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + + eslint-plugin-react-native-globals@0.1.2: + resolution: {integrity: sha512-9aEPf1JEpiTjcFAmmyw8eiIXmcNZOqaZyHO77wgm0/dWfT/oxC1SrIq8ET38pMxHYrcB6Uew+TzUVsBeczF88g==} + + eslint-plugin-react-native@4.1.0: + resolution: {integrity: sha512-QLo7rzTBOl43FvVqDdq5Ql9IoElIuTdjrz9SKAXCvULvBoRZ44JGSkx9z4999ZusCsb4rK3gjS8gOGyeYqZv2Q==} + peerDependencies: + eslint: ^3.17.0 || ^4 || ^5 || ^6 || ^7 || ^8 + + eslint-plugin-react@7.33.2: + resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@2.1.0: + resolution: {integrity: sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==} + engines: {node: '>=10'} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esquery@1.5.0: + resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + execa@4.1.0: + resolution: {integrity: sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==} + engines: {node: '>=10'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + exit-x@0.2.2: + resolution: {integrity: sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==} + engines: {node: '>= 0.8.0'} + + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + expect@30.0.5: + resolution: {integrity: sha512-P0te2pt+hHI5qLJkIR+iMvS+lYUZml8rKKsohVHAGY+uClp9XVbdyYNJOIjSRpHVp8s8YqxJCiHUkSYZGr8rtQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + exponential-backoff@3.1.1: + resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-diff@1.3.0: + resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fast-xml-parser@4.5.3: + resolution: {integrity: sha512-RKihhV+SHsIUGXObeVy9AXiBbFwkVk7Syp8XgwN5U3JV416+Gwp/GO9i0JYKmikykgz/UHRrrV4ROuZEo/T0ig==} + hasBin: true + + fastq@1.15.0: + resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} + + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + filter-obj@1.1.0: + resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==} + engines: {node: '>=0.10.0'} + + finalhandler@1.1.2: + resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} + engines: {node: '>= 0.8'} + + find-babel-config@2.1.2: + resolution: {integrity: sha512-ZfZp1rQyp4gyuxqt1ZqjFGVeVBvmpURMqdIWXbPRfB97Bf6BzdK/xSIbylEINzQ0kB5tlDQfn9HkNXXWsqTqLg==} + + find-up@3.0.0: + resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} + engines: {node: '>=6'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.2.9: + resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} + + flow-enums-runtime@0.0.6: + resolution: {integrity: sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + foreground-child@3.1.1: + resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + engines: {node: '>=14'} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.6: + resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-symbol-description@1.0.0: + resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} + engines: {node: '>= 0.4'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.3.10: + resolution: {integrity: sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==} + engines: {node: '>=16 || 14 >=14.17'} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + glob@9.3.5: + resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==} + engines: {node: '>=16 || 14 >=14.17'} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + globals@13.23.0: + resolution: {integrity: sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==} + engines: {node: '>=8'} + + globalthis@1.0.3: + resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} + engines: {node: '>= 0.4'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + graphql-tag@2.12.6: + resolution: {integrity: sha512-FdSNcu2QQcWnM2VNvSCCDCVS5PpPqpzgFT8+GXzqJuoDd0CBncxCY278u4mhRO7tMgo2JjgJA5aZ+nWSQ/Z+xg==} + engines: {node: '>=10'} + peerDependencies: + graphql: ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 + + graphql@16.13.2: + resolution: {integrity: sha512-5bJ+nf/UCpAjHM8i06fl7eLyVC9iuNAjm9qzkiu2ZGhM0VscSvS6WDPfAwkdkBuoXGM9FJSbKl6wylMwP9Ktig==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + + handlebars@4.7.8: + resolution: {integrity: sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==} + engines: {node: '>=0.4.7'} + hasBin: true + + has-bigints@1.0.2: + resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.0.1: + resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hermes-estree@0.28.1: + resolution: {integrity: sha512-w3nxl/RGM7LBae0v8LH2o36+8VqwOZGv9rX1wyoWT6YaKZLqpJZ0YQ5P0LVr3tuRpf7vCx0iIG4i/VmBJejxTQ==} + + hermes-estree@0.29.1: + resolution: {integrity: sha512-jl+x31n4/w+wEqm0I2r4CMimukLbLQEYpisys5oCre611CI5fc9TxhqkBBCJ1edDG4Kza0f7CgNz8xVMLZQOmQ==} + + hermes-parser@0.28.1: + resolution: {integrity: sha512-nf8o+hE8g7UJWParnccljHumE9Vlq8F7MqIdeahl+4x0tvCUJYRrT0L7h0MMg/X9YJmkNwsfbaNNrzPtFXOscg==} + + hermes-parser@0.29.1: + resolution: {integrity: sha512-xBHWmUtRC5e/UL0tI7Ivt2riA/YBq9+SiYFU7C1oBa/j2jYGlIF9043oak1F47ihuDIxQ5nbsKueYJDRY02UgA==} + + hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + http-errors@2.0.0: + resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==} + engines: {node: '>= 0.8'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + human-signals@1.1.1: + resolution: {integrity: sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==} + engines: {node: '>=8.12.0'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + image-size@1.0.2: + resolution: {integrity: sha512-xfOoWjceHntRb3qFCrh5ZFORYH8XCdYpASltMhZ/Q0KZiOwjdE/Yl2QCiWdwD+lygV5bMCvauzgu5PxBX/Yerg==} + engines: {node: '>=14.0.0'} + hasBin: true + + import-fresh@2.0.0: + resolution: {integrity: sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==} + engines: {node: '>=4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + inflight@1.0.6: + resolution: {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. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + internal-slot@1.0.6: + resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==} + engines: {node: '>= 0.4'} + + invariant@2.2.4: + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + + is-absolute@1.0.0: + resolution: {integrity: sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==} + engines: {node: '>=0.10.0'} + + is-arguments@1.2.0: + resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.2: + resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-arrayish@0.3.4: + resolution: {integrity: sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==} + + is-async-function@2.0.0: + resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} + engines: {node: '>= 0.4'} + + is-bigint@1.0.4: + resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} + + is-boolean-object@1.1.2: + resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-date-object@1.0.5: + resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} + engines: {node: '>= 0.4'} + + is-directory@0.3.1: + resolution: {integrity: sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==} + engines: {node: '>=0.10.0'} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.0.2: + resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} + + is-fullwidth-code-point@2.0.0: + resolution: {integrity: sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==} + engines: {node: '>=4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-git-dirty@2.0.2: + resolution: {integrity: sha512-U3YCo+GKR/rDsY7r0v/LBICbQwsx859tDQnAT+v0E/zCDeWbQ1TUt1FtyExeyik7VIJlYOLHCIifLdz71HDalg==} + engines: {node: '>=10'} + + is-git-repository@2.0.0: + resolution: {integrity: sha512-HDO50CG5suIAcmqG4F1buqVXEZRPn+RaXIn9pFKq/947FBo2bCRwK7ZluEVZOy99a4IQyqsjbKEpAiOXCccOHQ==} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + + is-map@2.0.2: + resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} + + is-negative-zero@2.0.2: + resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} + engines: {node: '>= 0.4'} + + is-number-object@1.0.7: + resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-cwd@2.2.0: + resolution: {integrity: sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==} + engines: {node: '>=6'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-relative@1.0.0: + resolution: {integrity: sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==} + engines: {node: '>=0.10.0'} + + is-set@2.0.2: + resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} + + is-shared-array-buffer@1.0.2: + resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-string@1.0.7: + resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} + engines: {node: '>= 0.4'} + + is-symbol@1.0.4: + resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-unc-path@1.0.0: + resolution: {integrity: sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==} + engines: {node: '>=0.10.0'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + is-weakmap@2.0.1: + resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} + + is-weakref@1.0.2: + resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} + + is-weakset@2.0.2: + resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==} + + is-windows@1.0.2: + resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} + engines: {node: '>=0.10.0'} + + is-wsl@1.1.0: + resolution: {integrity: sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==} + engines: {node: '>=4'} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@5.0.6: + resolution: {integrity: sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==} + engines: {node: '>=10'} + + istanbul-reports@3.1.6: + resolution: {integrity: sha512-TLgnMkKg3iTDsQ9PbPTdpfAK2DzjF9mqUG7RMgcQl8oFjad8ob4laGxv5XV5U9MAfx8D6tSJiUyuAwzLicaxlg==} + engines: {node: '>=8'} + + iterator.prototype@1.1.2: + resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} + + jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} + + jest-changed-files@30.0.5: + resolution: {integrity: sha512-bGl2Ntdx0eAwXuGpdLdVYVr5YQHnSZlQ0y9HVDu565lCUAe9sj6JOtBbMmBBikGIegne9piDDIOeiLVoqTkz4A==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-circus@30.0.5: + resolution: {integrity: sha512-h/sjXEs4GS+NFFfqBDYT7y5Msfxh04EwWLhQi0F8kuWpe+J/7tICSlswU8qvBqumR3kFgHbfu7vU6qruWWBPug==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-cli@30.0.5: + resolution: {integrity: sha512-Sa45PGMkBZzF94HMrlX4kUyPOwUpdZasaliKN3mifvDmkhLYqLLg8HQTzn6gq7vJGahFYMQjXgyJWfYImKZzOw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@30.0.5: + resolution: {integrity: sha512-aIVh+JNOOpzUgzUnPn5FLtyVnqc3TQHVMupYtyeURSb//iLColiMIR8TxCIDKyx9ZgjKnXGucuW68hCxgbrwmA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + peerDependencies: + '@types/node': '*' + esbuild-register: '>=3.4.0' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + esbuild-register: + optional: true + ts-node: + optional: true + + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-diff@30.0.5: + resolution: {integrity: sha512-1UIqE9PoEKaHcIKvq2vbibrCog4Y8G0zmOxgQUVEiTqwR5hJVMCoDsN1vFvI5JvwD37hjueZ1C4l2FyGnfpE0A==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-docblock@30.0.1: + resolution: {integrity: sha512-/vF78qn3DYphAaIc3jy4gA7XSAz167n9Bm/wn/1XhTLW7tTBIzXtCJpb/vcmc73NIIeeohCbdL94JasyXUZsGA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-each@30.0.5: + resolution: {integrity: sha512-dKjRsx1uZ96TVyejD3/aAWcNKy6ajMaN531CwWIsrazIqIoXI9TnnpPlkrEYku/8rkS3dh2rbH+kMOyiEIv0xQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-environment-node@30.0.5: + resolution: {integrity: sha512-ppYizXdLMSvciGsRsMEnv/5EFpvOdXBaXRBzFUDPWrsfmog4kYrOGWXarLllz6AXan6ZAA/kYokgDWuos1IKDA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-haste-map@30.0.5: + resolution: {integrity: sha512-dkmlWNlsTSR0nH3nRfW5BKbqHefLZv0/6LCccG0xFCTWcJu8TuEwG+5Cm75iBfjVoockmO6J35o5gxtFSn5xeg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-leak-detector@30.0.5: + resolution: {integrity: sha512-3Uxr5uP8jmHMcsOtYMRB/zf1gXN3yUIc+iPorhNETG54gErFIiUhLvyY/OggYpSMOEYqsmRxmuU4ZOoX5jpRFg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-matcher-utils@30.0.5: + resolution: {integrity: sha512-uQgGWt7GOrRLP1P7IwNWwK1WAQbq+m//ZY0yXygyfWp0rJlksMSLQAA4wYQC3b6wl3zfnchyTx+k3HZ5aPtCbQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-message-util@30.0.5: + resolution: {integrity: sha512-NAiDOhsK3V7RU0Aa/HnrQo+E4JlbarbmI3q6Pi4KcxicdtjV82gcIUrejOtczChtVQR4kddu1E1EJlW6EN9IyA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-mock@30.0.5: + resolution: {integrity: sha512-Od7TyasAAQX/6S+QCbN6vZoWOMwlTtzzGuxJku1GhGanAjz9y+QsQkpScDmETvdc9aSXyJ/Op4rhpMYBWW91wQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-pnp-resolver@1.2.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-regex-util@30.0.1: + resolution: {integrity: sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-resolve-dependencies@30.0.5: + resolution: {integrity: sha512-/xMvBR4MpwkrHW4ikZIWRttBBRZgWK4d6xt3xW1iRDSKt4tXzYkMkyPfBnSCgv96cpkrctfXs6gexeqMYqdEpw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-resolve@30.0.5: + resolution: {integrity: sha512-d+DjBQ1tIhdz91B79mywH5yYu76bZuE96sSbxj8MkjWVx5WNdt1deEFRONVL4UkKLSrAbMkdhb24XN691yDRHg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-runner@30.0.5: + resolution: {integrity: sha512-JcCOucZmgp+YuGgLAXHNy7ualBx4wYSgJVWrYMRBnb79j9PD0Jxh0EHvR5Cx/r0Ce+ZBC4hCdz2AzFFLl9hCiw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-runtime@30.0.5: + resolution: {integrity: sha512-7oySNDkqpe4xpX5PPiJTe5vEa+Ak/NnNz2bGYZrA1ftG3RL3EFlHaUkA1Cjx+R8IhK0Vg43RML5mJedGTPNz3A==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-snapshot@30.0.5: + resolution: {integrity: sha512-T00dWU/Ek3LqTp4+DcW6PraVxjk28WY5Ua/s+3zUKSERZSNyxTqhDXCWKG5p2HAJ+crVQ3WJ2P9YVHpj1tkW+g==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@30.0.5: + resolution: {integrity: sha512-pvyPWssDZR0FlfMxCBoc0tvM8iUEskaRFALUtGQYzVEAqisAztmy+R8LnU14KT4XA0H/a5HMVTXat1jLne010g==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-validate@30.0.5: + resolution: {integrity: sha512-ouTm6VFHaS2boyl+k4u+Qip4TSH7Uld5tyD8psQ8abGgt2uYYB8VwVfAHWHjHc0NWmGGbwO5h0sCPOGHHevefw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-watcher@30.0.5: + resolution: {integrity: sha512-z9slj/0vOwBDBjN3L4z4ZYaA+pG56d6p3kTUhFRYGvXbXMWhXmb/FIxREZCD06DYUwDKKnj2T80+Pb71CQ0KEg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-worker@30.0.5: + resolution: {integrity: sha512-ojRXsWzEP16NdUuBw/4H/zkZdHOa7MMYCk4E430l+8fELeLg/mqmMlRhjL7UNZvQrDmnovWZV4DxX03fZF48fQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest@30.0.5: + resolution: {integrity: sha512-y2mfcJywuTUkvLm2Lp1/pFX8kTgMO5yyQGq/Sk/n2mN7XWYp4JsCZ/QXW34M8YScgk8bPZlREH04f6blPnoHnQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + joi@17.13.3: + resolution: {integrity: sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==} + + jotai@2.19.1: + resolution: {integrity: sha512-sqm9lVZiqBHZH8aSRk32DSiZDHY3yUIlulXYn9GQj7/LvoUdYXSMti7ZPJGo+6zjzKFt5a25k/I6iBCi43PJcw==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@babel/core': '>=7.0.0' + '@babel/template': '>=7.0.0' + '@types/react': '>=17.0.0' + react: '>=17.0.0' + peerDependenciesMeta: + '@babel/core': + optional: true + '@babel/template': + optional: true + '@types/react': + optional: true + react: + optional: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.14.1: + resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} + hasBin: true + + js-yaml@4.1.0: + resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + hasBin: true + + jsc-safe-url@0.2.4: + resolution: {integrity: sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==} + + jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-better-errors@1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + + jsonfile@6.2.1: + resolution: {integrity: sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==} + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + launch-editor@2.11.1: + resolution: {integrity: sha512-SEET7oNfgSaB6Ym0jufAdCeo3meJVeCaaDyzRygy0xsp2BFKCprcfHljTq4QkzTLUxEKkFK6OK4811YM2oSrRg==} + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lighthouse-logger@1.4.2: + resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + locate-path@3.0.0: + resolution: {integrity: sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==} + engines: {node: '>=6'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.throttle@4.1.1: + resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + logkitty@0.7.1: + resolution: {integrity: sha512-/3ER20CTTbahrCrpYfPn7Xavv9diBROZpoXGVZDWMw4b/X4uuUwAC0ki85tgsdMRONURyIJbcOvS94QsUBYPbQ==} + hasBin: true + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@10.0.3: + resolution: {integrity: sha512-B7gr+F6MkqB3uzINHXNctGieGsRTMwIBgxkp0yq/5BwcuDzD4A8wQpHQW6vDAm1uKSLQghmRdD9sKqf2vJ1cEg==} + engines: {node: 14 || >=16.14} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + + marky@1.2.5: + resolution: {integrity: sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + memoize-one@5.2.1: + resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + metro-babel-transformer@0.82.5: + resolution: {integrity: sha512-W/scFDnwJXSccJYnOFdGiYr9srhbHPdxX9TvvACOFsIXdLilh3XuxQl/wXW6jEJfgIb0jTvoTlwwrqvuwymr6Q==} + engines: {node: '>=18.18'} + + metro-cache-key@0.82.5: + resolution: {integrity: sha512-qpVmPbDJuRLrT4kcGlUouyqLGssJnbTllVtvIgXfR7ZuzMKf0mGS+8WzcqzNK8+kCyakombQWR0uDd8qhWGJcA==} + engines: {node: '>=18.18'} + + metro-cache@0.82.5: + resolution: {integrity: sha512-AwHV9607xZpedu1NQcjUkua8v7HfOTKfftl6Vc9OGr/jbpiJX6Gpy8E/V9jo/U9UuVYX2PqSUcVNZmu+LTm71Q==} + engines: {node: '>=18.18'} + + metro-config@0.82.5: + resolution: {integrity: sha512-/r83VqE55l0WsBf8IhNmc/3z71y2zIPe5kRSuqA5tY/SL/ULzlHUJEMd1szztd0G45JozLwjvrhAzhDPJ/Qo/g==} + engines: {node: '>=18.18'} + + metro-core@0.82.5: + resolution: {integrity: sha512-OJL18VbSw2RgtBm1f2P3J5kb892LCVJqMvslXxuxjAPex8OH7Eb8RBfgEo7VZSjgb/LOf4jhC4UFk5l5tAOHHA==} + engines: {node: '>=18.18'} + + metro-file-map@0.82.5: + resolution: {integrity: sha512-vpMDxkGIB+MTN8Af5hvSAanc6zXQipsAUO+XUx3PCQieKUfLwdoa8qaZ1WAQYRpaU+CJ8vhBcxtzzo3d9IsCIQ==} + engines: {node: '>=18.18'} + + metro-minify-terser@0.82.5: + resolution: {integrity: sha512-v6Nx7A4We6PqPu/ta1oGTqJ4Usz0P7c+3XNeBxW9kp8zayS3lHUKR0sY0wsCHInxZlNAEICx791x+uXytFUuwg==} + engines: {node: '>=18.18'} + + metro-resolver@0.82.5: + resolution: {integrity: sha512-kFowLnWACt3bEsuVsaRNgwplT8U7kETnaFHaZePlARz4Fg8tZtmRDUmjaD68CGAwc0rwdwNCkWizLYpnyVcs2g==} + engines: {node: '>=18.18'} + + metro-runtime@0.82.5: + resolution: {integrity: sha512-rQZDoCUf7k4Broyw3Ixxlq5ieIPiR1ULONdpcYpbJQ6yQ5GGEyYjtkztGD+OhHlw81LCR2SUAoPvtTus2WDK5g==} + engines: {node: '>=18.18'} + + metro-source-map@0.82.5: + resolution: {integrity: sha512-wH+awTOQJVkbhn2SKyaw+0cd+RVSCZ3sHVgyqJFQXIee/yLs3dZqKjjeKKhhVeudgjXo7aE/vSu/zVfcQEcUfw==} + engines: {node: '>=18.18'} + + metro-symbolicate@0.82.5: + resolution: {integrity: sha512-1u+07gzrvYDJ/oNXuOG1EXSvXZka/0JSW1q2EYBWerVKMOhvv9JzDGyzmuV7hHbF2Hg3T3S2uiM36sLz1qKsiw==} + engines: {node: '>=18.18'} + hasBin: true + + metro-transform-plugins@0.82.5: + resolution: {integrity: sha512-57Bqf3rgq9nPqLrT2d9kf/2WVieTFqsQ6qWHpEng5naIUtc/Iiw9+0bfLLWSAw0GH40iJ4yMjFcFJDtNSYynMA==} + engines: {node: '>=18.18'} + + metro-transform-worker@0.82.5: + resolution: {integrity: sha512-mx0grhAX7xe+XUQH6qoHHlWedI8fhSpDGsfga7CpkO9Lk9W+aPitNtJWNGrW8PfjKEWbT9Uz9O50dkI8bJqigw==} + engines: {node: '>=18.18'} + + metro@0.82.5: + resolution: {integrity: sha512-8oAXxL7do8QckID/WZEKaIFuQJFUTLzfVcC48ghkHhNK2RGuQq8Xvf4AVd+TUA0SZtX0q8TGNXZ/eba1ckeGCg==} + engines: {node: '>=18.18'} + hasBin: true + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + mime@2.6.0: + resolution: {integrity: sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==} + engines: {node: '>=4.0.0'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + + minimatch@8.0.7: + resolution: {integrity: sha512-V+1uQNdzybxa14e/p00HZnQNNcTjnRJjDxg2V8wtkjFctq4M7hXFws4oekyTP0Jebeq7QYtpFyOeBAjc88zvYg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@4.2.8: + resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==} + engines: {node: '>=8'} + + minipass@7.0.4: + resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} + engines: {node: '>=16 || 14 >=14.17'} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + napi-postinstall@0.2.5: + resolution: {integrity: sha512-kmsgUvCRIJohHjbZ3V8avP0I1Pekw329MVAMDzVxsrkjgdnqiwvMX5XwR+hWV66vsAtZ+iM+fVnq8RTQawUmCQ==} + engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + negotiator@0.6.4: + resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} + engines: {node: '>= 0.6'} + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + nocache@3.0.4: + resolution: {integrity: sha512-WDD0bdg9mbq6F4mRxEYcPWwfA1vxd0mrvKOyxI7Xj/atfRHVeutzuWByG//jfm4uPzp0y4Kj051EORCBSQMycw==} + engines: {node: '>=12.0.0'} + + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + node-releases@2.0.19: + resolution: {integrity: sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==} + + node-stream-zip@1.15.0: + resolution: {integrity: sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==} + engines: {node: '>=0.12.0'} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + nullthrows@1.1.1: + resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} + + ob1@0.82.5: + resolution: {integrity: sha512-QyQQ6e66f+Ut/qUVjEce0E/wux5nAGLXYZDn1jr15JWstHsCH3l6VVrg8NKDptW9NEiBXKOJeGF/ydxeSDF3IQ==} + engines: {node: '>=18.18'} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.4: + resolution: {integrity: sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==} + engines: {node: '>= 0.4'} + + object.entries@1.1.7: + resolution: {integrity: sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.7: + resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==} + engines: {node: '>= 0.4'} + + object.hasown@1.1.3: + resolution: {integrity: sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==} + + object.values@1.1.7: + resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==} + engines: {node: '>= 0.4'} + + on-finished@2.3.0: + resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} + engines: {node: '>= 0.8'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + on-headers@1.1.0: + resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + open@6.4.0: + resolution: {integrity: sha512-IFenVPgF70fSm1keSd2iDBIDIBZkroLeuffXq+wKTzTJlBpesFWojV9lb8mzOfaAzM1sr7HQHuO0vtV0zYekGg==} + engines: {node: '>=8'} + + open@7.4.2: + resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} + engines: {node: '>=8'} + + optimism@0.18.1: + resolution: {integrity: sha512-mLXNwWPa9dgFyDqkNi54sjDyNJ9/fTI6WGBLgnXku1vdKY/jovHfZT5r+aiVeFFLOz+foPNOm5YJ4mqgld2GBQ==} + + optionator@0.9.3: + resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} + engines: {node: '>= 0.8.0'} + + ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@3.0.0: + resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} + engines: {node: '>=6'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.10.1: + resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} + engines: {node: '>=16 || 14 >=14.17'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + + pkg-up@3.1.0: + resolution: {integrity: sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==} + engines: {node: '>=8'} + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier-linter-helpers@1.0.0: + resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==} + engines: {node: '>=6.0.0'} + + prettier@3.2.5: + resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} + engines: {node: '>=14'} + hasBin: true + + pretty-format@26.6.2: + resolution: {integrity: sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==} + engines: {node: '>= 10'} + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + pretty-format@30.0.5: + resolution: {integrity: sha512-D1tKtYvByrBkFLe2wHJl2bwMJIiT8rW+XA+TiataH79/FszLQMrpGEvzUVkzPau7OCO0Qnrhpe87PqtOAIB8Yw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + + promise@8.3.0: + resolution: {integrity: sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + pure-rand@7.0.1: + resolution: {integrity: sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==} + + qs@6.13.0: + resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} + engines: {node: '>=0.6'} + + query-string@7.1.3: + resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==} + engines: {node: '>=6'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + queue@6.0.2: + resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.5.2: + resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} + engines: {node: '>= 0.8'} + + react-devtools-core@6.1.5: + resolution: {integrity: sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==} + + react-freeze@1.0.4: + resolution: {integrity: sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==} + engines: {node: '>=10'} + peerDependencies: + react: '>=17.0.0' + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-is@19.1.1: + resolution: {integrity: sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==} + + react-native-builder-bob@0.23.2: + resolution: {integrity: sha512-ehv2XKS9cvPR5JR7FIpSx3qY7tULkljT2Kb82FBAxXsFLjqlRU1WfqHRLh6lytL2XqAxLQODpPfHUH53SsXnag==} + engines: {node: '>= 18.0.0'} + hasBin: true + + react-native-config@1.5.6: + resolution: {integrity: sha512-UB3LEco0FGGbbGvS+DfH2VmGKiP/y5C2MkmfBmfsIaxHSbM1KOTMKYG7YRf6xFhZbJ/01BedHG7SIny5i7N9BQ==} + peerDependencies: + react-native-windows: '>=0.61' + peerDependenciesMeta: + react-native-windows: + optional: true + + react-native-dotenv@3.4.11: + resolution: {integrity: sha512-6vnIE+WHABSeHCaYP6l3O1BOEhWxKH6nHAdV7n/wKn/sciZ64zPPp2NUdEUf1m7g4uuzlLbjgr+6uDt89q2DOg==} + peerDependencies: + '@babel/runtime': ^7.20.6 + + react-native-encrypted-storage@4.0.3: + resolution: {integrity: sha512-0pJA4Aj2S1PIJEbU7pN/Qvf7JIJx3hFywx+i+bLHtgK0/6Zryf1V2xVsWcrD50dfiu3jY1eN2gesQ5osGxE7jA==} + peerDependencies: + react: '*' + react-native: '*' + + react-native-gesture-handler@2.26.0: + resolution: {integrity: sha512-pfE1j9Vzu0qpWj/Aq1IK+cYnougN69mCKvWuq1rdNjH2zs1WIszF0Mum9/oGQTemgjyc/JgiqOOTgwcleAMAGg==} + peerDependencies: + react: '*' + react-native: '*' + + react-native-gradle-plugin@0.71.19: + resolution: {integrity: sha512-1dVk9NwhoyKHCSxcrM6vY6cxmojeATsBobDicX0ZKr7DgUF2cBQRTKsimQFvzH8XhOVXyH8p4HyDSZNIFI8OlQ==} + + react-native-is-edge-to-edge@1.1.7: + resolution: {integrity: sha512-EH6i7E8epJGIcu7KpfXYXiV2JFIYITtq+rVS8uEb+92naMRBdxhTuS8Wn2Q7j9sqyO0B+Xbaaf9VdipIAmGW4w==} + peerDependencies: + react: '*' + react-native: '*' + + react-native-is-edge-to-edge@1.3.1: + resolution: {integrity: sha512-NIXU/iT5+ORyCc7p0z2nnlkouYKX425vuU1OEm6bMMtWWR9yvb+Xg5AZmImTKoF9abxCPqrKC3rOZsKzUYgYZA==} + peerDependencies: + react: '*' + react-native: '*' + + react-native-nitro-modules@0.33.9: + resolution: {integrity: sha512-BM9C5mCGYYjrc8CDWZZ0anLWU/knH2xaEuFzvzogKTOW6fzgS6mmsCdM3ty+AhImJNSYwK19DLrHaqwnrrwEzw==} + peerDependencies: + react: '*' + react-native: '*' + + react-native-quick-base64@2.2.2: + resolution: {integrity: sha512-WLHSifHLoamr2kF00Gov0W9ud6CfPshe1rmqWTquVIi9c62qxOaJCFVDrXFZhEBU8B8PvGLVuOlVKH78yhY0Fg==} + peerDependencies: + react: '*' + react-native: '*' + + react-native-quick-crypto@1.0.9: + resolution: {integrity: sha512-SAugK0DZg8Q0ELkPWZnzFabbRngZQh0BjwXEMpXH3YQgiM1vVHnb83YOdOTcBmUnM/mAKz+PW9lrvccloNwjbA==} + peerDependencies: + expo: '>=48.0.0' + expo-build-properties: '*' + react: '*' + react-native: '*' + react-native-nitro-modules: '>=0.29.1' + react-native-quick-base64: '>=2.1.0' + peerDependenciesMeta: + expo: + optional: true + expo-build-properties: + optional: true + + react-native-reanimated@3.18.0: + resolution: {integrity: sha512-eVcNcqeOkMW+BUWAHdtvN3FKgC8J8wiEJkX6bNGGQaLS7m7e4amTfjIcqf/Ta+lerZLurmDaQ0lICI1CKPrb1Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + react: '*' + react-native: '*' + + react-native-safe-area-context@5.7.0: + resolution: {integrity: sha512-/9/MtQz8ODphjsLdZ+GZAIcC/RtoqW9EeShf7Uvnfgm/pzYrJ75y3PV/J1wuAV1T5Dye5ygq4EAW20RoBq0ABQ==} + peerDependencies: + react: '*' + react-native: '*' + + react-native-screens@4.16.0: + resolution: {integrity: sha512-yIAyh7F/9uWkOzCi1/2FqvNvK6Wb9Y1+Kzn16SuGfN9YFJDTbwlzGRvePCNTOX0recpLQF3kc2FmvMUhyTCH1Q==} + peerDependencies: + react: '*' + react-native: '*' + + react-native-vector-icons@10.3.0: + resolution: {integrity: sha512-IFQ0RE57819hOUdFvgK4FowM5aMXg7C7XKsuGLevqXkkIJatc3QopN0wYrb2IrzUgmdpfP+QVIbI3S6h7M0btw==} + deprecated: react-native-vector-icons package has moved to a new model of per-icon-family packages. See the https://github.com/oblador/react-native-vector-icons/blob/master/MIGRATION.md on how to migrate + hasBin: true + + react-native-webview@13.16.1: + resolution: {integrity: sha512-If0eHhoEdOYDcHsX+xBFwHMbWBGK1BvGDQDQdVkwtSIXiq1uiqjkpWVP2uQ1as94J0CzvFE9PUNDuhiX0Z6ubw==} + peerDependencies: + react: '*' + react-native: '*' + + react-native@0.80.2: + resolution: {integrity: sha512-6ySV4qTJo/To3lgpG/9Mcg/ZtvExqOVZuT7JVGcO5rS2Bjvl/yUAkQF0hTnbRb2Ch6T5MlKghrM4OeHX+KA9Pg==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + '@types/react': ^19.1.0 + react: ^19.1.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-refresh@0.14.0: + resolution: {integrity: sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==} + engines: {node: '>=0.10.0'} + + react-test-renderer@19.1.0: + resolution: {integrity: sha512-jXkSl3CpvPYEF+p/eGDLB4sPoDX8pKkYvRl9+rR8HxLY0X04vW7hCm1/0zHoUSjPZ3bDa+wXWNTDVIw/R8aDVw==} + peerDependencies: + react: ^19.1.0 + + react@19.1.0: + resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} + engines: {node: '>=0.10.0'} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readable-stream@4.5.2: + resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + reflect.getprototypeof@1.0.4: + resolution: {integrity: sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==} + engines: {node: '>= 0.4'} + + regenerate-unicode-properties@10.2.0: + resolution: {integrity: sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==} + engines: {node: '>=4'} + + regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + + regenerator-runtime@0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + + regexp.prototype.flags@1.5.1: + resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} + engines: {node: '>= 0.4'} + + regexpu-core@6.2.0: + resolution: {integrity: sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==} + engines: {node: '>=4'} + + regjsgen@0.8.0: + resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} + + regjsparser@0.12.0: + resolution: {integrity: sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==} + hasBin: true + + rehackt@0.1.0: + resolution: {integrity: sha512-7kRDOuLHB87D/JESKxQoRwv4DzbIdwkAGQ7p6QKGdVlY1IZheUnVhlk/4UZlNUVxdAXpyxikE3URsG067ybVzw==} + peerDependencies: + '@types/react': '*' + react: '*' + peerDependenciesMeta: + '@types/react': + optional: true + react: + optional: true + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-main-filename@2.0.0: + resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} + + reselect@4.1.8: + resolution: {integrity: sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ==} + + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + + resolve-from@3.0.0: + resolution: {integrity: sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==} + engines: {node: '>=4'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve@1.22.10: + resolution: {integrity: sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==} + engines: {node: '>= 0.4'} + hasBin: true + + resolve@2.0.0-next.5: + resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} + hasBin: true + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + + reusify@1.0.4: + resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-array-concat@1.0.1: + resolution: {integrity: sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==} + engines: {node: '>=0.4'} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + scheduler@0.26.0: + resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.2: + resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + engines: {node: '>=10'} + hasBin: true + + send@0.19.0: + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} + engines: {node: '>= 0.8.0'} + + serialize-error@2.1.0: + resolution: {integrity: sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==} + engines: {node: '>=0.10.0'} + + serve-static@1.16.2: + resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} + engines: {node: '>= 0.8.0'} + + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.1: + resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} + engines: {node: '>= 0.4'} + + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + sf-symbols-typescript@2.2.0: + resolution: {integrity: sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw==} + engines: {node: '>=10'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + simple-swizzle@0.2.4: + resolution: {integrity: sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + slice-ansi@2.1.0: + resolution: {integrity: sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==} + engines: {node: '>=6'} + + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + split-on-first@1.1.0: + resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==} + engines: {node: '>=6'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + stackframe@1.3.4: + resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} + + stacktrace-parser@0.1.10: + resolution: {integrity: sha512-KJP1OCML99+8fhOHxwwzyWrlUuVX5GQ0ZpJTd1DFXhdkrvg1szxfHhawXUZ3g9TkXORQd4/WG68jMlQZ2p8wlg==} + engines: {node: '>=6'} + + statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + + strict-uri-encode@2.0.0: + resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==} + engines: {node: '>=4'} + + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + + string-natural-compare@3.0.1: + resolution: {integrity: sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string.prototype.matchall@4.0.10: + resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==} + + string.prototype.trim@1.2.8: + resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.7: + resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==} + + string.prototype.trimstart@1.0.7: + resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@5.2.0: + resolution: {integrity: sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==} + engines: {node: '>=6'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + strnum@1.1.2: + resolution: {integrity: sha512-vrN+B7DBIoTTZjnPNewwhx6cBA/H+IS7rfW68n7XxC1y7uoiGQBxaKzqucGUgavX15dJgiGztLJ8vxuEzwqBdA==} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + symbol-observable@4.0.0: + resolution: {integrity: sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==} + engines: {node: '>=0.10'} + + synckit@0.11.8: + resolution: {integrity: sha512-+XZ+r1XGIJGeQk3VvXhT6xx/VpbHsRzsTkGgF6E5RX9TTXD0118l87puaEBZ566FhqblC6U0d4XnubznJDm30A==} + engines: {node: ^14.18.0 || >=16.0.0} + + terser@5.24.0: + resolution: {integrity: sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==} + engines: {node: '>=10'} + hasBin: true + + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + throat@5.0.0: + resolution: {integrity: sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==} + + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + ts-api-utils@1.4.3: + resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + ts-invariant@0.10.3: + resolution: {integrity: sha512-uivwYcQaxAucv1CzRp2n/QdYPo4ILf9VXgH19zEIjFx2EJufV16P0JtJVpYHy89DItG6Kwj2oIUjrcK5au+4tQ==} + engines: {node: '>=8'} + + ts-jest@29.4.1: + resolution: {integrity: sha512-SaeUtjfpg9Uqu8IbeDKtdaS0g8lS6FT6OzM3ezrDfErPJPHNDo/Ey+VFGP1bQIDfagYDLyRpd7O15XpG1Es2Uw==} + engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/transform': ^29.0.0 || ^30.0.0 + '@jest/types': ^29.0.0 || ^30.0.0 + babel-jest: ^29.0.0 || ^30.0.0 + esbuild: '*' + jest: ^29.0.0 || ^30.0.0 + jest-util: ^29.0.0 || ^30.0.0 + typescript: '>=4.3 <6' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/transform': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + jest-util: + optional: true + + tslib@1.14.1: + resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsutils@3.21.0: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + 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' + + turbo-darwin-64@1.13.4: + resolution: {integrity: sha512-A0eKd73R7CGnRinTiS7txkMElg+R5rKFp9HV7baDiEL4xTG1FIg/56Vm7A5RVgg8UNgG2qNnrfatJtb+dRmNdw==} + cpu: [x64] + os: [darwin] + + turbo-darwin-arm64@1.13.4: + resolution: {integrity: sha512-eG769Q0NF6/Vyjsr3mKCnkG/eW6dKMBZk6dxWOdrHfrg6QgfkBUk0WUUujzdtVPiUIvsh4l46vQrNVd9EOtbyA==} + cpu: [arm64] + os: [darwin] + + turbo-linux-64@1.13.4: + resolution: {integrity: sha512-Bq0JphDeNw3XEi+Xb/e4xoKhs1DHN7OoLVUbTIQz+gazYjigVZvtwCvgrZI7eW9Xo1eOXM2zw2u1DGLLUfmGkQ==} + cpu: [x64] + os: [linux] + + turbo-linux-arm64@1.13.4: + resolution: {integrity: sha512-BJcXw1DDiHO/okYbaNdcWN6szjXyHWx9d460v6fCHY65G8CyqGU3y2uUTPK89o8lq/b2C8NK0yZD+Vp0f9VoIg==} + cpu: [arm64] + os: [linux] + + turbo-windows-64@1.13.4: + resolution: {integrity: sha512-OFFhXHOFLN7A78vD/dlVuuSSVEB3s9ZBj18Tm1hk3aW1HTWTuAw0ReN6ZNlVObZUHvGy8d57OAGGxf2bT3etQw==} + cpu: [x64] + os: [win32] + + turbo-windows-arm64@1.13.4: + resolution: {integrity: sha512-u5A+VOKHswJJmJ8o8rcilBfU5U3Y1TTAfP9wX8bFh8teYF1ghP0EhtMRLjhtp6RPa+XCxHHVA2CiC3gbh5eg5g==} + cpu: [arm64] + os: [win32] + + turbo@1.13.4: + resolution: {integrity: sha512-1q7+9UJABuBAHrcC4Sxp5lOqYS5mvxRrwa33wpIyM18hlOCpRD/fTJNxZ0vhbMcJmz15o9kkVm743mPn7p6jpQ==} + hasBin: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@0.7.1: + resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} + engines: {node: '>=8'} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + typed-array-buffer@1.0.0: + resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.0: + resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.0: + resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.4: + resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} + + typescript@5.9.2: + resolution: {integrity: sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==} + engines: {node: '>=14.17'} + hasBin: true + + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + + unbox-primitive@1.0.2: + resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + + unc-path-regex@0.1.2: + resolution: {integrity: sha512-eXL4nmJT7oCpkZsHZUOJo8hcX3GbsiDOa0Qu9F646fi8dT3XuSVopVqAcEiVzSKKH7UoDti23wNX3qGFxcW5Qg==} + engines: {node: '>=0.10.0'} + + undici-types@5.26.5: + resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} + + unicode-canonical-property-names-ecmascript@2.0.0: + resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} + engines: {node: '>=4'} + + unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + + unicode-match-property-value-ecmascript@2.1.0: + resolution: {integrity: sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA==} + engines: {node: '>=4'} + + unicode-property-aliases-ecmascript@2.1.0: + resolution: {integrity: sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==} + engines: {node: '>=4'} + + universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + unrs-resolver@1.9.2: + resolution: {integrity: sha512-VUyWiTNQD7itdiMuJy+EuLEErLj3uwX/EpHQF8EOf33Dq3Ju6VW1GXm+swk6+1h7a49uv9fKZ+dft9jU7esdLA==} + + update-browserslist-db@1.1.3: + resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + use-latest-callback@0.2.6: + resolution: {integrity: sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg==} + peerDependencies: + react: '>=16.8' + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + util@0.12.5: + resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + v8-to-istanbul@9.1.3: + resolution: {integrity: sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==} + engines: {node: '>=10.12.0'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vlq@1.0.1: + resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} + + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + + warn-once@0.1.1: + resolution: {integrity: sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==} + + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + + whatwg-fetch@3.6.19: + resolution: {integrity: sha512-d67JP4dHSbm2TrpFj8AbO8DnL1JXL5J9u0Kq2xW6d0TFDbCA3Muhdt8orXC22utleTVj7Prqt82baN6RBvnEgw==} + + which-boxed-primitive@1.0.2: + resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} + + which-builtin-type@1.1.3: + resolution: {integrity: sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==} + engines: {node: '>= 0.4'} + + which-collection@1.0.1: + resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} + + which-module@2.0.1: + resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} + + which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + write-file-atomic@5.0.1: + resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + ws@6.2.3: + resolution: {integrity: sha512-jmTjYU0j60B+vHey6TfR3Z7RD61z/hmxBS3VMSGIrroOWXQEneK1zNuotOUrGyBHQj0yrpsLHPWtigEFd13ndA==} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + 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 + + y18n@4.0.3: + resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@1.10.3: + resolution: {integrity: sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==} + engines: {node: '>= 6'} + + yaml@2.8.1: + resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@18.1.3: + resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} + engines: {node: '>=6'} + + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@15.4.1: + resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==} + engines: {node: '>=8'} + + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zen-observable-ts@1.2.5: + resolution: {integrity: sha512-QZWQekv6iB72Naeake9hS1KxHlotfRpe+WGNbNx5/ta+R3DNjVO2bswf63gXlWDcs+EMd7XY8HfVQyP1X6T4Zg==} + + zen-observable@0.8.15: + resolution: {integrity: sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ==} + +snapshots: + + '@aashutoshrathi/word-wrap@1.2.6': {} + + '@ampproject/remapping@2.2.1': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 + + '@apollo/client@3.14.1(@types/react@19.1.12)(graphql@16.13.2)(react@19.1.0)': + dependencies: + '@graphql-typed-document-node/core': 3.2.0(graphql@16.13.2) + '@wry/caches': 1.0.1 + '@wry/equality': 0.5.7 + '@wry/trie': 0.5.0 + graphql: 16.13.2 + graphql-tag: 2.12.6(graphql@16.13.2) + hoist-non-react-statics: 3.3.2 + optimism: 0.18.1 + prop-types: 15.8.1 + rehackt: 0.1.0(@types/react@19.1.12)(react@19.1.0) + symbol-observable: 4.0.0 + ts-invariant: 0.10.3 + tslib: 2.8.1 + zen-observable-ts: 1.2.5 + optionalDependencies: + react: 19.1.0 + transitivePeerDependencies: + - '@types/react' + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.27.1 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.0': {} + + '@babel/core@7.28.3': + dependencies: + '@ampproject/remapping': 2.2.1 + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) + '@babel/helpers': 7.28.3 + '@babel/parser': 7.28.3 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 + convert-source-map: 2.0.0 + debug: 4.4.1 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/eslint-parser@7.28.0(@babel/core@7.28.3)(eslint@8.57.1)': + dependencies: + '@babel/core': 7.28.3 + '@nicolo-ribaudo/eslint-scope-5-internals': 5.1.1-v1 + eslint: 8.57.1 + eslint-visitor-keys: 2.1.0 + semver: 6.3.1 + + '@babel/generator@7.28.3': + dependencies: + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 + jsesc: 3.0.2 + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 + jsesc: 3.0.2 + + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.28.2 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.25.3 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.28.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.3) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.28.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.28.3) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.29.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-regexp-features-plugin@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-annotate-as-pure': 7.27.3 + regexpu-core: 6.2.0 + semver: 6.3.1 + + '@babel/helper-define-polyfill-provider@0.6.5(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-plugin-utils': 7.27.1 + debug: 4.4.1 + lodash.debounce: 4.0.8 + resolve: 1.22.10 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-member-expression-to-functions@7.27.1': + dependencies: + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 + transitivePeerDependencies: + - supports-color + + '@babel/helper-member-expression-to-functions@7.28.5': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.28.2 + + '@babel/helper-plugin-utils@7.27.1': {} + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-wrap-function': 7.28.3 + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-member-expression-to-functions': 7.27.1 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.28.6(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helper-wrap-function@7.28.3': + dependencies: + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 + transitivePeerDependencies: + - supports-color + + '@babel/helpers@7.28.3': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + + '@babel/parser@7.28.3': + dependencies: + '@babel/types': 7.28.2 + + '@babel/parser@7.29.2': + dependencies: + '@babel/types': 7.29.0 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.28.3) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-class-properties@7.18.6(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-export-default-from@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-proposal-nullish-coalescing-operator@7.18.6(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.3) + + '@babel/plugin-proposal-optional-chaining@7.21.0(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.3) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-dynamic-import@7.8.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-export-default-from@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-flow@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-import-assertions@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-async-generator-functions@7.28.0(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.3) + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.28.3) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-block-scoping@7.28.0(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-class-properties@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.28.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.28.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-globals': 7.28.0 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.3) + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-computed-properties@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/template': 7.27.2 + + '@babel/plugin-transform-destructuring@7.28.0(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-dotall-regex@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-explicit-resource-management@7.28.0(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-transform-destructuring': 7.28.0(@babel/core@7.28.3) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-exponentiation-operator@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-flow-strip-types@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-flow': 7.27.1(@babel/core@7.28.3) + + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-json-strings@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-logical-assignment-operators@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-commonjs@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-systemjs@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-named-capturing-groups-regex@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-nullish-coalescing-operator@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-numeric-separator@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-object-rest-spread@7.28.0(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-transform-destructuring': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.3) + '@babel/traverse': 7.28.3 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.3) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-catch-binding@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-optional-chaining@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-private-methods@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.3) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.3) + '@babel/types': 7.28.2 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-regenerator@7.28.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-regexp-modifiers@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-runtime@7.28.3(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-plugin-utils': 7.27.1 + babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.3) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.3) + babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.3) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-spread@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-typescript@7.28.0(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.3(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.3) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.28.3) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-unicode-property-regex@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-unicode-sets-regex@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-create-regexp-features-plugin': 7.27.1(@babel/core@7.28.3) + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/preset-env@7.28.3(@babel/core@7.28.3)': + dependencies: + '@babel/compat-data': 7.28.0 + '@babel/core': 7.28.3 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.3(@babel/core@7.28.3) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.28.3) + '@babel/plugin-syntax-import-assertions': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.28.3) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-async-generator-functions': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-async-to-generator': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-block-scoping': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-class-static-block': 7.28.3(@babel/core@7.28.3) + '@babel/plugin-transform-classes': 7.28.3(@babel/core@7.28.3) + '@babel/plugin-transform-computed-properties': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-destructuring': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-dotall-regex': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-explicit-resource-management': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-exponentiation-operator': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-json-strings': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-logical-assignment-operators': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-modules-systemjs': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-nullish-coalescing-operator': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-numeric-separator': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-object-rest-spread': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-optional-catch-binding': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.3) + '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-regenerator': 7.28.3(@babel/core@7.28.3) + '@babel/plugin-transform-regexp-modifiers': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-spread': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-unicode-property-regex': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-unicode-sets-regex': 7.27.1(@babel/core@7.28.3) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.28.3) + babel-plugin-polyfill-corejs2: 0.4.14(@babel/core@7.28.3) + babel-plugin-polyfill-corejs3: 0.13.0(@babel/core@7.28.3) + babel-plugin-polyfill-regenerator: 0.6.5(@babel/core@7.28.3) + core-js-compat: 3.45.1 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/preset-flow@7.27.1(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.28.3) + + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/types': 7.28.2 + esutils: 2.0.3 + + '@babel/preset-react@7.28.5(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.28.3) + transitivePeerDependencies: + - supports-color + + '@babel/preset-typescript@7.28.5(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.28.3) + transitivePeerDependencies: + - supports-color + + '@babel/runtime@7.28.3': {} + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + + '@babel/traverse@7.28.3': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.3 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.3 + '@babel/template': 7.27.2 + '@babel/types': 7.28.2 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.2': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.27.1 + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bcoe/v8-coverage@0.2.3': {} + + '@craftzdog/react-native-buffer@6.1.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)': + dependencies: + ieee754: 1.2.1 + react-native-quick-base64: 2.2.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + transitivePeerDependencies: + - react + - react-native + + '@egjs/hammerjs@2.0.17': + dependencies: + '@types/hammerjs': 2.0.46 + + '@emnapi/core@1.4.3': + dependencies: + '@emnapi/wasi-threads': 1.0.2 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.4.3': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.0.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@eslint-community/eslint-utils@4.4.0(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.1': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.12.6 + debug: 4.4.1 + espree: 9.6.1 + globals: 13.23.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.1': {} + + '@graphql-typed-document-node/core@3.2.0(graphql@16.13.2)': + dependencies: + graphql: 16.13.2 + + '@hapi/hoek@9.3.0': {} + + '@hapi/topo@5.1.0': + dependencies: + '@hapi/hoek': 9.3.0 + + '@humanwhocodes/config-array@0.13.0': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.1 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@isaacs/ttlcache@1.4.1': {} + + '@istanbuljs/load-nyc-config@1.1.0': + dependencies: + camelcase: 5.3.1 + find-up: 4.1.0 + get-package-type: 0.1.0 + js-yaml: 3.14.1 + resolve-from: 5.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jest/console@30.0.5': + dependencies: + '@jest/types': 30.0.5 + '@types/node': 20.9.3 + chalk: 4.1.2 + jest-message-util: 30.0.5 + jest-util: 30.0.5 + slash: 3.0.0 + + '@jest/core@30.0.5': + dependencies: + '@jest/console': 30.0.5 + '@jest/pattern': 30.0.1 + '@jest/reporters': 30.0.5 + '@jest/test-result': 30.0.5 + '@jest/transform': 30.0.5 + '@jest/types': 30.0.5 + '@types/node': 20.9.3 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 4.2.0 + exit-x: 0.2.2 + graceful-fs: 4.2.11 + jest-changed-files: 30.0.5 + jest-config: 30.0.5(@types/node@20.9.3) + jest-haste-map: 30.0.5 + jest-message-util: 30.0.5 + jest-regex-util: 30.0.1 + jest-resolve: 30.0.5 + jest-resolve-dependencies: 30.0.5 + jest-runner: 30.0.5 + jest-runtime: 30.0.5 + jest-snapshot: 30.0.5 + jest-util: 30.0.5 + jest-validate: 30.0.5 + jest-watcher: 30.0.5 + micromatch: 4.0.8 + pretty-format: 30.0.5 + slash: 3.0.0 + transitivePeerDependencies: + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + + '@jest/create-cache-key-function@29.7.0': + dependencies: + '@jest/types': 29.6.3 + + '@jest/diff-sequences@30.0.1': {} + + '@jest/environment@29.7.0': + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.9.3 + jest-mock: 29.7.0 + + '@jest/environment@30.0.5': + dependencies: + '@jest/fake-timers': 30.0.5 + '@jest/types': 30.0.5 + '@types/node': 20.9.3 + jest-mock: 30.0.5 + + '@jest/expect-utils@29.7.0': + dependencies: + jest-get-type: 29.6.3 + + '@jest/expect-utils@30.0.5': + dependencies: + '@jest/get-type': 30.0.1 + + '@jest/expect@30.0.5': + dependencies: + expect: 30.0.5 + jest-snapshot: 30.0.5 + transitivePeerDependencies: + - supports-color + + '@jest/fake-timers@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 20.9.3 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + '@jest/fake-timers@30.0.5': + dependencies: + '@jest/types': 30.0.5 + '@sinonjs/fake-timers': 13.0.5 + '@types/node': 20.9.3 + jest-message-util: 30.0.5 + jest-mock: 30.0.5 + jest-util: 30.0.5 + + '@jest/get-type@30.0.1': {} + + '@jest/globals@30.0.5': + dependencies: + '@jest/environment': 30.0.5 + '@jest/expect': 30.0.5 + '@jest/types': 30.0.5 + jest-mock: 30.0.5 + transitivePeerDependencies: + - supports-color + + '@jest/pattern@30.0.1': + dependencies: + '@types/node': 20.9.3 + jest-regex-util: 30.0.1 + + '@jest/reporters@30.0.5': + dependencies: + '@bcoe/v8-coverage': 0.2.3 + '@jest/console': 30.0.5 + '@jest/test-result': 30.0.5 + '@jest/transform': 30.0.5 + '@jest/types': 30.0.5 + '@jridgewell/trace-mapping': 0.3.30 + '@types/node': 20.9.3 + chalk: 4.1.2 + collect-v8-coverage: 1.0.2 + exit-x: 0.2.2 + glob: 10.3.10 + graceful-fs: 4.2.11 + 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.6 + jest-message-util: 30.0.5 + jest-util: 30.0.5 + jest-worker: 30.0.5 + slash: 3.0.0 + string-length: 4.0.2 + v8-to-istanbul: 9.1.3 + transitivePeerDependencies: + - supports-color + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.8 + + '@jest/schemas@30.0.5': + dependencies: + '@sinclair/typebox': 0.34.37 + + '@jest/snapshot-utils@30.0.5': + dependencies: + '@jest/types': 30.0.5 + chalk: 4.1.2 + graceful-fs: 4.2.11 + natural-compare: 1.4.0 + + '@jest/source-map@30.0.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.30 + callsites: 3.1.0 + graceful-fs: 4.2.11 + + '@jest/test-result@30.0.5': + dependencies: + '@jest/console': 30.0.5 + '@jest/types': 30.0.5 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.2 + + '@jest/test-sequencer@30.0.5': + dependencies: + '@jest/test-result': 30.0.5 + graceful-fs: 4.2.11 + jest-haste-map: 30.0.5 + slash: 3.0.0 + + '@jest/transform@29.7.0': + dependencies: + '@babel/core': 7.28.3 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.30 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + micromatch: 4.0.8 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + + '@jest/transform@30.0.5': + dependencies: + '@babel/core': 7.28.3 + '@jest/types': 30.0.5 + '@jridgewell/trace-mapping': 0.3.30 + babel-plugin-istanbul: 7.0.0 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 30.0.5 + jest-regex-util: 30.0.1 + jest-util: 30.0.5 + micromatch: 4.0.8 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 5.0.1 + transitivePeerDependencies: + - supports-color + + '@jest/types@26.6.2': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 20.9.3 + '@types/yargs': 15.0.19 + chalk: 4.1.2 + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 20.9.3 + '@types/yargs': 17.0.33 + chalk: 4.1.2 + + '@jest/types@30.0.5': + dependencies: + '@jest/pattern': 30.0.1 + '@jest/schemas': 30.0.5 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 20.9.3 + '@types/yargs': 17.0.33 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.30 + + '@jridgewell/resolve-uri@3.1.1': {} + + '@jridgewell/source-map@0.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.30 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.30': + dependencies: + '@jridgewell/resolve-uri': 3.1.1 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@0.2.11': + dependencies: + '@emnapi/core': 1.4.3 + '@emnapi/runtime': 1.4.3 + '@tybys/wasm-util': 0.9.0 + optional: true + + '@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1': + dependencies: + eslint-scope: 5.1.1 + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.15.0 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@pkgr/core@0.2.7': {} + + '@react-native-community/cli-clean@19.1.1': + dependencies: + '@react-native-community/cli-tools': 19.1.1 + chalk: 4.1.2 + execa: 5.1.1 + fast-glob: 3.3.3 + + '@react-native-community/cli-config-android@19.1.1': + dependencies: + '@react-native-community/cli-tools': 19.1.1 + chalk: 4.1.2 + fast-glob: 3.3.3 + fast-xml-parser: 4.5.3 + + '@react-native-community/cli-config-apple@19.1.1': + dependencies: + '@react-native-community/cli-tools': 19.1.1 + chalk: 4.1.2 + execa: 5.1.1 + fast-glob: 3.3.3 + + '@react-native-community/cli-config@19.1.1(typescript@5.9.2)': + dependencies: + '@react-native-community/cli-tools': 19.1.1 + chalk: 4.1.2 + cosmiconfig: 9.0.0(typescript@5.9.2) + deepmerge: 4.3.1 + fast-glob: 3.3.3 + joi: 17.13.3 + transitivePeerDependencies: + - typescript + + '@react-native-community/cli-doctor@19.1.1(typescript@5.9.2)': + dependencies: + '@react-native-community/cli-config': 19.1.1(typescript@5.9.2) + '@react-native-community/cli-platform-android': 19.1.1 + '@react-native-community/cli-platform-apple': 19.1.1 + '@react-native-community/cli-platform-ios': 19.1.1 + '@react-native-community/cli-tools': 19.1.1 + chalk: 4.1.2 + command-exists: 1.2.9 + deepmerge: 4.3.1 + envinfo: 7.14.0 + execa: 5.1.1 + node-stream-zip: 1.15.0 + ora: 5.4.1 + semver: 7.7.2 + wcwidth: 1.0.1 + yaml: 2.8.1 + transitivePeerDependencies: + - typescript + + '@react-native-community/cli-platform-android@19.1.1': + dependencies: + '@react-native-community/cli-config-android': 19.1.1 + '@react-native-community/cli-tools': 19.1.1 + chalk: 4.1.2 + execa: 5.1.1 + logkitty: 0.7.1 + + '@react-native-community/cli-platform-apple@19.1.1': + dependencies: + '@react-native-community/cli-config-apple': 19.1.1 + '@react-native-community/cli-tools': 19.1.1 + chalk: 4.1.2 + execa: 5.1.1 + fast-xml-parser: 4.5.3 + + '@react-native-community/cli-platform-ios@19.1.1': + dependencies: + '@react-native-community/cli-platform-apple': 19.1.1 + + '@react-native-community/cli-server-api@19.1.1': + dependencies: + '@react-native-community/cli-tools': 19.1.1 + body-parser: 1.20.3 + compression: 1.8.1 + connect: 3.7.0 + errorhandler: 1.5.1 + nocache: 3.0.4 + open: 6.4.0 + pretty-format: 26.6.2 + serve-static: 1.16.2 + ws: 6.2.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@react-native-community/cli-tools@19.1.1': + dependencies: + '@vscode/sudo-prompt': 9.3.1 + appdirsjs: 1.2.7 + chalk: 4.1.2 + execa: 5.1.1 + find-up: 5.0.0 + launch-editor: 2.11.1 + mime: 2.6.0 + ora: 5.4.1 + prompts: 2.4.2 + semver: 7.7.2 + + '@react-native-community/cli-types@19.1.1': + dependencies: + joi: 17.13.3 + + '@react-native-community/cli@19.1.1(typescript@5.9.2)': + dependencies: + '@react-native-community/cli-clean': 19.1.1 + '@react-native-community/cli-config': 19.1.1(typescript@5.9.2) + '@react-native-community/cli-doctor': 19.1.1(typescript@5.9.2) + '@react-native-community/cli-server-api': 19.1.1 + '@react-native-community/cli-tools': 19.1.1 + '@react-native-community/cli-types': 19.1.1 + chalk: 4.1.2 + commander: 9.5.0 + deepmerge: 4.3.1 + execa: 5.1.1 + find-up: 5.0.0 + fs-extra: 8.1.0 + graceful-fs: 4.2.11 + prompts: 2.4.2 + semver: 7.7.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - typescript + - utf-8-validate + + '@react-native-masked-view/masked-view@0.3.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)': + dependencies: + react: 19.1.0 + react-native: 0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + + '@react-native/assets-registry@0.80.2': {} + + '@react-native/babel-plugin-codegen@0.80.2(@babel/core@7.28.3)': + dependencies: + '@babel/traverse': 7.28.3 + '@react-native/codegen': 0.80.2(@babel/core@7.28.3) + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@react-native/babel-preset@0.80.2(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@babel/plugin-proposal-export-default-from': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-syntax-dynamic-import': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-export-default-from': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-async-generator-functions': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-async-to-generator': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-block-scoping': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-classes': 7.28.3(@babel/core@7.28.3) + '@babel/plugin-transform-computed-properties': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-destructuring': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-flow-strip-types': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-logical-assignment-operators': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-modules-commonjs': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-named-capturing-groups-regex': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-nullish-coalescing-operator': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-numeric-separator': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-object-rest-spread': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-optional-catch-binding': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.28.3) + '@babel/plugin-transform-private-methods': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-private-property-in-object': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-regenerator': 7.28.3(@babel/core@7.28.3) + '@babel/plugin-transform-runtime': 7.28.3(@babel/core@7.28.3) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-spread': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.3) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.28.3) + '@babel/template': 7.27.2 + '@react-native/babel-plugin-codegen': 0.80.2(@babel/core@7.28.3) + babel-plugin-syntax-hermes-parser: 0.28.1 + babel-plugin-transform-flow-enums: 0.0.2(@babel/core@7.28.3) + react-refresh: 0.14.0 + transitivePeerDependencies: + - supports-color + + '@react-native/codegen@0.80.2(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + glob: 7.2.3 + hermes-parser: 0.28.1 + invariant: 2.2.4 + nullthrows: 1.1.1 + yargs: 17.7.2 + + '@react-native/community-cli-plugin@0.80.2(@react-native-community/cli@19.1.1(typescript@5.9.2))': + dependencies: + '@react-native/dev-middleware': 0.80.2 + chalk: 4.1.2 + debug: 4.4.1 + invariant: 2.2.4 + metro: 0.82.5 + metro-config: 0.82.5 + metro-core: 0.82.5 + semver: 7.7.2 + optionalDependencies: + '@react-native-community/cli': 19.1.1(typescript@5.9.2) + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@react-native/debugger-frontend@0.80.2': {} + + '@react-native/dev-middleware@0.80.2': + dependencies: + '@isaacs/ttlcache': 1.4.1 + '@react-native/debugger-frontend': 0.80.2 + chrome-launcher: 0.15.2 + chromium-edge-launcher: 0.2.0 + connect: 3.7.0 + debug: 4.4.1 + invariant: 2.2.4 + nullthrows: 1.1.1 + open: 7.4.2 + serve-static: 1.16.2 + ws: 6.2.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@react-native/eslint-config@0.80.2(eslint@8.57.1)(jest@30.0.5(@types/node@20.9.3))(prettier@3.2.5)(typescript@5.9.2)': + dependencies: + '@babel/core': 7.28.3 + '@babel/eslint-parser': 7.28.0(@babel/core@7.28.3)(eslint@8.57.1) + '@react-native/eslint-plugin': 0.80.2 + '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.9.2) + eslint: 8.57.1 + eslint-config-prettier: 8.10.0(eslint@8.57.1) + eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.1) + eslint-plugin-ft-flow: 2.0.3(@babel/eslint-parser@7.28.0(@babel/core@7.28.3)(eslint@8.57.1))(eslint@8.57.1) + eslint-plugin-jest: 27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(jest@30.0.5(@types/node@20.9.3))(typescript@5.9.2) + eslint-plugin-react: 7.33.2(eslint@8.57.1) + eslint-plugin-react-hooks: 5.2.0(eslint@8.57.1) + eslint-plugin-react-native: 4.1.0(eslint@8.57.1) + prettier: 3.2.5 + transitivePeerDependencies: + - jest + - supports-color + - typescript + + '@react-native/eslint-plugin@0.80.2': {} + + '@react-native/gradle-plugin@0.80.2': {} + + '@react-native/js-polyfills@0.80.2': {} + + '@react-native/metro-babel-transformer@0.80.2(@babel/core@7.28.3)': + dependencies: + '@babel/core': 7.28.3 + '@react-native/babel-preset': 0.80.2(@babel/core@7.28.3) + hermes-parser: 0.28.1 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + '@react-native/metro-config@0.80.2(@babel/core@7.28.3)': + dependencies: + '@react-native/js-polyfills': 0.80.2 + '@react-native/metro-babel-transformer': 0.80.2(@babel/core@7.28.3) + metro-config: 0.82.5 + metro-runtime: 0.82.5 + transitivePeerDependencies: + - '@babel/core' + - supports-color + + '@react-native/normalize-colors@0.80.2': {} + + '@react-native/typescript-config@0.80.2': {} + + '@react-native/virtualized-lists@0.80.2(@types/react@19.1.12)(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)': + dependencies: + invariant: 2.2.4 + nullthrows: 1.1.1 + react: 19.1.0 + react-native: 0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.12 + + '@react-navigation/bottom-tabs@7.15.9(@react-native-masked-view/masked-view@0.3.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(@react-navigation/native@7.2.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.7.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)': + dependencies: + '@react-navigation/elements': 2.9.14(@react-native-masked-view/masked-view@0.3.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(@react-navigation/native@7.2.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.7.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + '@react-navigation/native': 7.2.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + color: 4.2.3 + react: 19.1.0 + react-native: 0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + react-native-safe-area-context: 5.7.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + react-native-screens: 4.16.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + sf-symbols-typescript: 2.2.0 + transitivePeerDependencies: + - '@react-native-masked-view/masked-view' + + '@react-navigation/core@7.17.2(react@19.1.0)': + dependencies: + '@react-navigation/routers': 7.5.3 + escape-string-regexp: 4.0.0 + fast-deep-equal: 3.1.3 + nanoid: 3.3.11 + query-string: 7.1.3 + react: 19.1.0 + react-is: 19.1.1 + use-latest-callback: 0.2.6(react@19.1.0) + use-sync-external-store: 1.6.0(react@19.1.0) + + '@react-navigation/elements@2.9.14(@react-native-masked-view/masked-view@0.3.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(@react-navigation/native@7.2.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.7.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)': + dependencies: + '@react-navigation/native': 7.2.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + color: 4.2.3 + react: 19.1.0 + react-native: 0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + react-native-safe-area-context: 5.7.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + use-latest-callback: 0.2.6(react@19.1.0) + use-sync-external-store: 1.6.0(react@19.1.0) + optionalDependencies: + '@react-native-masked-view/masked-view': 0.3.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + + '@react-navigation/native-stack@7.14.11(@react-native-masked-view/masked-view@0.3.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(@react-navigation/native@7.2.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.7.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-screens@4.16.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)': + dependencies: + '@react-navigation/elements': 2.9.14(@react-native-masked-view/masked-view@0.3.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(@react-navigation/native@7.2.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.7.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + '@react-navigation/native': 7.2.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + color: 4.2.3 + react: 19.1.0 + react-native: 0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + react-native-safe-area-context: 5.7.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + react-native-screens: 4.16.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + sf-symbols-typescript: 2.2.0 + warn-once: 0.1.1 + transitivePeerDependencies: + - '@react-native-masked-view/masked-view' + + '@react-navigation/native@7.2.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0)': + dependencies: + '@react-navigation/core': 7.17.2(react@19.1.0) + escape-string-regexp: 4.0.0 + fast-deep-equal: 3.1.3 + nanoid: 3.3.11 + react: 19.1.0 + react-native: 0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + use-latest-callback: 0.2.6(react@19.1.0) + + '@react-navigation/routers@7.5.3': + dependencies: + nanoid: 3.3.11 + + '@react-navigation/stack@7.8.10(4bd8dd23d724a396af48ae32b42242ae)': + dependencies: + '@react-navigation/elements': 2.9.14(@react-native-masked-view/masked-view@0.3.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(@react-navigation/native@7.2.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-safe-area-context@5.7.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + '@react-navigation/native': 7.2.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + color: 4.2.3 + react: 19.1.0 + react-native: 0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + react-native-gesture-handler: 2.26.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + react-native-safe-area-context: 5.7.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + react-native-screens: 4.16.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + use-latest-callback: 0.2.6(react@19.1.0) + transitivePeerDependencies: + - '@react-native-masked-view/masked-view' + + '@sideway/address@4.1.5': + dependencies: + '@hapi/hoek': 9.3.0 + + '@sideway/formula@3.0.1': {} + + '@sideway/pinpoint@2.0.0': {} + + '@sinclair/typebox@0.27.8': {} + + '@sinclair/typebox@0.34.37': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@10.3.0': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@sinonjs/fake-timers@13.0.5': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@testing-library/react-native@13.3.1(jest@30.0.5(@types/node@20.9.3))(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react-test-renderer@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + jest-matcher-utils: 30.0.5 + picocolors: 1.1.1 + pretty-format: 30.0.5 + react: 19.1.0 + react-native: 0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + react-test-renderer: 19.1.0(react@19.1.0) + redent: 3.0.0 + optionalDependencies: + jest: 30.0.5(@types/node@20.9.3) + + '@tsconfig/react-native@3.0.6': {} + + '@tybys/wasm-util@0.9.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.28.3 + '@babel/types': 7.28.2 + '@types/babel__generator': 7.6.7 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.20.4 + + '@types/babel__generator@7.6.7': + dependencies: + '@babel/types': 7.29.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + + '@types/babel__traverse@7.20.4': + dependencies: + '@babel/types': 7.29.0 + + '@types/graceful-fs@4.1.9': + dependencies: + '@types/node': 20.9.3 + + '@types/hammerjs@2.0.46': {} + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/jest@29.5.14': + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + + '@types/json-schema@7.0.15': {} + + '@types/node@20.9.3': + dependencies: + undici-types: 5.26.5 + + '@types/parse-json@4.0.2': {} + + '@types/react-native-dotenv@0.2.2': {} + + '@types/react-native-vector-icons@6.4.18': + dependencies: + '@types/react': 19.1.12 + '@types/react-native': 0.70.19 + + '@types/react-native@0.70.19': + dependencies: + '@types/react': 19.1.12 + + '@types/react-native@0.73.0(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0)': + dependencies: + react-native: 0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + transitivePeerDependencies: + - '@babel/core' + - '@react-native-community/cli' + - '@types/react' + - bufferutil + - react + - supports-color + - utf-8-validate + + '@types/react-test-renderer@19.1.0': + dependencies: + '@types/react': 19.1.12 + + '@types/react@19.1.12': + dependencies: + csstype: 3.1.2 + + '@types/semver@7.5.6': {} + + '@types/setimmediate@1.0.4': {} + + '@types/stack-utils@2.0.3': {} + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@15.0.19': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@types/yargs@17.0.33': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 7.18.0(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/type-utils': 7.18.0(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 7.18.0 + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + ts-api-utils: 1.4.3(typescript@5.9.2) + optionalDependencies: + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2)': + dependencies: + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.2) + '@typescript-eslint/visitor-keys': 7.18.0 + debug: 4.4.1 + eslint: 8.57.1 + optionalDependencies: + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@5.62.0': + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + + '@typescript-eslint/scope-manager@7.18.0': + dependencies: + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/visitor-keys': 7.18.0 + + '@typescript-eslint/type-utils@7.18.0(eslint@8.57.1)(typescript@5.9.2)': + dependencies: + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.2) + '@typescript-eslint/utils': 7.18.0(eslint@8.57.1)(typescript@5.9.2) + debug: 4.4.1 + eslint: 8.57.1 + ts-api-utils: 1.4.3(typescript@5.9.2) + optionalDependencies: + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@5.62.0': {} + + '@typescript-eslint/types@7.18.0': {} + + '@typescript-eslint/typescript-estree@5.62.0(typescript@5.9.2)': + dependencies: + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/visitor-keys': 5.62.0 + debug: 4.4.1 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.7.2 + tsutils: 3.21.0(typescript@5.9.2) + optionalDependencies: + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/typescript-estree@7.18.0(typescript@5.9.2)': + dependencies: + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/visitor-keys': 7.18.0 + debug: 4.4.1 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.2 + ts-api-utils: 1.4.3(typescript@5.9.2) + optionalDependencies: + typescript: 5.9.2 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@5.62.0(eslint@8.57.1)(typescript@5.9.2)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) + '@types/json-schema': 7.0.15 + '@types/semver': 7.5.6 + '@typescript-eslint/scope-manager': 5.62.0 + '@typescript-eslint/types': 5.62.0 + '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.9.2) + eslint: 8.57.1 + eslint-scope: 5.1.1 + semver: 7.7.2 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/utils@7.18.0(eslint@8.57.1)(typescript@5.9.2)': + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) + '@typescript-eslint/scope-manager': 7.18.0 + '@typescript-eslint/types': 7.18.0 + '@typescript-eslint/typescript-estree': 7.18.0(typescript@5.9.2) + eslint: 8.57.1 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@5.62.0': + dependencies: + '@typescript-eslint/types': 5.62.0 + eslint-visitor-keys: 3.4.3 + + '@typescript-eslint/visitor-keys@7.18.0': + dependencies: + '@typescript-eslint/types': 7.18.0 + eslint-visitor-keys: 3.4.3 + + '@ungap/structured-clone@1.3.0': {} + + '@unrs/resolver-binding-android-arm-eabi@1.9.2': + optional: true + + '@unrs/resolver-binding-android-arm64@1.9.2': + optional: true + + '@unrs/resolver-binding-darwin-arm64@1.9.2': + optional: true + + '@unrs/resolver-binding-darwin-x64@1.9.2': + optional: true + + '@unrs/resolver-binding-freebsd-x64@1.9.2': + optional: true + + '@unrs/resolver-binding-linux-arm-gnueabihf@1.9.2': + optional: true + + '@unrs/resolver-binding-linux-arm-musleabihf@1.9.2': + optional: true + + '@unrs/resolver-binding-linux-arm64-gnu@1.9.2': + optional: true + + '@unrs/resolver-binding-linux-arm64-musl@1.9.2': + optional: true + + '@unrs/resolver-binding-linux-ppc64-gnu@1.9.2': + optional: true + + '@unrs/resolver-binding-linux-riscv64-gnu@1.9.2': + optional: true + + '@unrs/resolver-binding-linux-riscv64-musl@1.9.2': + optional: true + + '@unrs/resolver-binding-linux-s390x-gnu@1.9.2': + optional: true + + '@unrs/resolver-binding-linux-x64-gnu@1.9.2': + optional: true + + '@unrs/resolver-binding-linux-x64-musl@1.9.2': + optional: true + + '@unrs/resolver-binding-wasm32-wasi@1.9.2': + dependencies: + '@napi-rs/wasm-runtime': 0.2.11 + optional: true + + '@unrs/resolver-binding-win32-arm64-msvc@1.9.2': + optional: true + + '@unrs/resolver-binding-win32-ia32-msvc@1.9.2': + optional: true + + '@unrs/resolver-binding-win32-x64-msvc@1.9.2': + optional: true + + '@vscode/sudo-prompt@9.3.1': {} + + '@wry/caches@1.0.1': + dependencies: + tslib: 2.8.1 + + '@wry/context@0.7.4': + dependencies: + tslib: 2.8.1 + + '@wry/equality@0.5.7': + dependencies: + tslib: 2.8.1 + + '@wry/trie@0.5.0': + dependencies: + tslib: 2.8.1 + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + acorn-jsx@5.3.2(acorn@8.11.2): + dependencies: + acorn: 8.11.2 + + acorn@8.11.2: {} + + agent-base@7.1.4: {} + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + + ajv@6.12.6: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + anser@1.4.10: {} + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-fragments@0.2.1: + dependencies: + colorette: 1.4.0 + slice-ansi: 2.1.0 + strip-ansi: 5.2.0 + + ansi-regex@4.1.1: {} + + ansi-regex@5.0.1: {} + + ansi-regex@6.0.1: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.1: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + appdirsjs@1.2.7: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + array-buffer-byte-length@1.0.0: + dependencies: + call-bind: 1.0.8 + is-array-buffer: 3.0.2 + + array-includes@3.1.7: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.22.3 + get-intrinsic: 1.3.0 + is-string: 1.0.7 + + array-union@2.1.0: {} + + array.prototype.flat@1.3.2: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.22.3 + es-shim-unscopables: 1.0.2 + + array.prototype.flatmap@1.3.2: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.22.3 + es-shim-unscopables: 1.0.2 + + array.prototype.tosorted@1.1.2: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.22.3 + es-shim-unscopables: 1.0.2 + get-intrinsic: 1.3.0 + + arraybuffer.prototype.slice@1.0.2: + dependencies: + array-buffer-byte-length: 1.0.0 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.22.3 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.2 + is-shared-array-buffer: 1.0.2 + + asap@2.0.6: {} + + astral-regex@1.0.0: {} + + async-limiter@1.0.1: {} + + asynciterator.prototype@1.0.0: + dependencies: + has-symbols: 1.1.0 + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + babel-jest@29.7.0(@babel/core@7.28.3): + dependencies: + '@babel/core': 7.28.3 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.28.3) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-jest@30.0.5(@babel/core@7.28.3): + dependencies: + '@babel/core': 7.28.3 + '@jest/transform': 30.0.5 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 7.0.0 + babel-preset-jest: 30.0.1(@babel/core@7.28.3) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@6.1.1: + dependencies: + '@babel/helper-plugin-utils': 7.27.1 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-istanbul@7.0.0: + dependencies: + '@babel/helper-plugin-utils': 7.27.1 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-instrument: 6.0.3 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@29.6.3: + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.20.4 + + babel-plugin-jest-hoist@30.0.1: + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + '@types/babel__core': 7.20.5 + + babel-plugin-module-resolver@5.0.3: + dependencies: + find-babel-config: 2.1.2 + glob: 9.3.5 + pkg-up: 3.1.0 + reselect: 4.1.8 + resolve: 1.22.10 + + babel-plugin-polyfill-corejs2@0.4.14(@babel/core@7.28.3): + dependencies: + '@babel/compat-data': 7.28.0 + '@babel/core': 7.28.3 + '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.3) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.13.0(@babel/core@7.28.3): + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.3) + core-js-compat: 3.45.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.5(@babel/core@7.28.3): + dependencies: + '@babel/core': 7.28.3 + '@babel/helper-define-polyfill-provider': 0.6.5(@babel/core@7.28.3) + transitivePeerDependencies: + - supports-color + + babel-plugin-syntax-hermes-parser@0.28.1: + dependencies: + hermes-parser: 0.28.1 + + babel-plugin-transform-flow-enums@0.0.2(@babel/core@7.28.3): + dependencies: + '@babel/plugin-syntax-flow': 7.27.1(@babel/core@7.28.3) + transitivePeerDependencies: + - '@babel/core' + + babel-preset-current-node-syntax@1.1.0(@babel/core@7.28.3): + dependencies: + '@babel/core': 7.28.3 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.3) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.3) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.3) + '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.3) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.3) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.3) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.3) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.3) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.3) + + babel-preset-jest@29.6.3(@babel/core@7.28.3): + dependencies: + '@babel/core': 7.28.3 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.28.3) + + babel-preset-jest@30.0.1(@babel/core@7.28.3): + dependencies: + '@babel/core': 7.28.3 + babel-plugin-jest-hoist: 30.0.1 + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.28.3) + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + body-parser@1.20.3: + 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 + transitivePeerDependencies: + - supports-color + + brace-expansion@1.1.11: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.25.3: + dependencies: + caniuse-lite: 1.0.30001737 + electron-to-chromium: 1.5.209 + node-releases: 2.0.19 + update-browserslist-db: 1.1.3(browserslist@4.25.3) + + bs-logger@0.2.6: + dependencies: + fast-json-stable-stringify: 2.1.0 + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + caller-callsite@2.0.0: + dependencies: + callsites: 2.0.0 + + caller-path@2.0.0: + dependencies: + caller-callsite: 2.0.0 + + callsites@2.0.0: {} + + callsites@3.1.0: {} + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001737: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + char-regex@1.0.2: {} + + chrome-launcher@0.15.2: + dependencies: + '@types/node': 20.9.3 + escape-string-regexp: 4.0.0 + is-wsl: 2.2.0 + lighthouse-logger: 1.4.2 + transitivePeerDependencies: + - supports-color + + chromium-edge-launcher@0.2.0: + dependencies: + '@types/node': 20.9.3 + escape-string-regexp: 4.0.0 + is-wsl: 2.2.0 + lighthouse-logger: 1.4.2 + mkdirp: 1.0.4 + rimraf: 3.0.2 + transitivePeerDependencies: + - supports-color + + ci-info@2.0.0: {} + + ci-info@3.9.0: {} + + ci-info@4.2.0: {} + + cjs-module-lexer@2.1.0: {} + + clean-stack@2.2.0: {} + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-spinners@2.9.2: {} + + cliui@6.0.0: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 6.2.0 + + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone@1.0.4: {} + + co@4.6.0: {} + + collect-v8-coverage@1.0.2: {} + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.3: {} + + color-name@1.1.4: {} + + color-string@1.9.1: + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.4 + + color@4.2.3: + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + + colorette@1.4.0: {} + + command-exists@1.2.9: {} + + commander@12.1.0: {} + + commander@2.20.3: {} + + commander@9.5.0: {} + + compressible@2.0.18: + dependencies: + mime-db: 1.54.0 + + compression@1.8.1: + dependencies: + bytes: 3.1.2 + compressible: 2.0.18 + debug: 2.6.9 + negotiator: 0.6.4 + on-headers: 1.1.0 + safe-buffer: 5.2.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + concat-map@0.0.1: {} + + connect@3.7.0: + dependencies: + debug: 2.6.9 + finalhandler: 1.1.2 + parseurl: 1.3.3 + utils-merge: 1.0.1 + transitivePeerDependencies: + - supports-color + + content-type@1.0.5: {} + + convert-source-map@2.0.0: {} + + core-js-compat@3.45.1: + dependencies: + browserslist: 4.25.3 + + cosmiconfig@5.2.1: + dependencies: + import-fresh: 2.0.0 + is-directory: 0.3.1 + js-yaml: 3.14.1 + parse-json: 4.0.0 + + cosmiconfig@7.1.0: + dependencies: + '@types/parse-json': 4.0.2 + import-fresh: 3.3.1 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.3 + + cosmiconfig@9.0.0(typescript@5.9.2): + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.1.0 + parse-json: 5.2.0 + optionalDependencies: + typescript: 5.9.2 + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.1.2: {} + + dayjs@1.11.15: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@4.4.1: + dependencies: + ms: 2.1.3 + + decamelize@1.2.0: {} + + decode-uri-component@0.2.2: {} + + dedent@0.7.0: {} + + dedent@1.6.0: {} + + deep-is@0.1.4: {} + + deepmerge@4.3.1: {} + + defaults@1.0.4: + dependencies: + clone: 1.0.4 + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + del@6.1.1: + dependencies: + globby: 11.1.0 + graceful-fs: 4.2.11 + is-glob: 4.0.3 + is-path-cwd: 2.2.0 + is-path-inside: 3.0.3 + p-map: 4.0.0 + rimraf: 3.0.2 + slash: 3.0.0 + + depd@2.0.0: {} + + destroy@1.2.0: {} + + detect-newline@3.1.0: {} + + diff-sequences@29.6.3: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dotenv@16.6.1: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + eastasianwidth@0.2.0: {} + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.209: {} + + emittery@0.13.1: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + encodeurl@1.0.2: {} + + encodeurl@2.0.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + env-paths@2.2.1: {} + + envinfo@7.14.0: {} + + error-ex@1.3.2: + dependencies: + is-arrayish: 0.2.1 + + error-stack-parser@2.1.4: + dependencies: + stackframe: 1.3.4 + + errorhandler@1.5.1: + dependencies: + accepts: 1.3.8 + escape-html: 1.0.3 + + es-abstract@1.22.3: + dependencies: + array-buffer-byte-length: 1.0.0 + arraybuffer.prototype.slice: 1.0.2 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + es-set-tostringtag: 2.0.2 + es-to-primitive: 1.2.1 + function.prototype.name: 1.1.6 + get-intrinsic: 1.3.0 + get-symbol-description: 1.0.0 + globalthis: 1.0.3 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.0.1 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.0.6 + is-array-buffer: 3.0.2 + is-callable: 1.2.7 + is-negative-zero: 2.0.2 + is-regex: 1.2.1 + is-shared-array-buffer: 1.0.2 + is-string: 1.0.7 + is-typed-array: 1.1.15 + is-weakref: 1.0.2 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.4 + regexp.prototype.flags: 1.5.1 + safe-array-concat: 1.0.1 + safe-regex-test: 1.1.0 + string.prototype.trim: 1.2.8 + string.prototype.trimend: 1.0.7 + string.prototype.trimstart: 1.0.7 + typed-array-buffer: 1.0.0 + typed-array-byte-length: 1.0.0 + typed-array-byte-offset: 1.0.0 + typed-array-length: 1.0.4 + unbox-primitive: 1.0.2 + which-typed-array: 1.1.20 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-iterator-helpers@1.0.15: + dependencies: + asynciterator.prototype: 1.0.0 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.22.3 + es-set-tostringtag: 2.0.2 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.3 + has-property-descriptors: 1.0.2 + has-proto: 1.0.1 + has-symbols: 1.1.0 + internal-slot: 1.0.6 + iterator.prototype: 1.1.2 + safe-array-concat: 1.0.1 + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.0.2: + dependencies: + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.0.2: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.2.1: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.0.5 + is-symbol: 1.0.4 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@1.0.5: {} + + escape-string-regexp@2.0.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-config-prettier@8.10.0(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + + eslint-plugin-eslint-comments@3.2.0(eslint@8.57.1): + dependencies: + escape-string-regexp: 1.0.5 + eslint: 8.57.1 + ignore: 5.3.2 + + eslint-plugin-ft-flow@2.0.3(@babel/eslint-parser@7.28.0(@babel/core@7.28.3)(eslint@8.57.1))(eslint@8.57.1): + dependencies: + '@babel/eslint-parser': 7.28.0(@babel/core@7.28.3)(eslint@8.57.1) + eslint: 8.57.1 + lodash: 4.17.21 + string-natural-compare: 3.0.1 + + eslint-plugin-jest@27.9.0(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(jest@30.0.5(@types/node@20.9.3))(typescript@5.9.2): + dependencies: + '@typescript-eslint/utils': 5.62.0(eslint@8.57.1)(typescript@5.9.2) + eslint: 8.57.1 + optionalDependencies: + '@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.1)(typescript@5.9.2))(eslint@8.57.1)(typescript@5.9.2) + jest: 30.0.5(@types/node@20.9.3) + transitivePeerDependencies: + - supports-color + - typescript + + eslint-plugin-prettier@5.5.4(eslint-config-prettier@8.10.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.2.5): + dependencies: + eslint: 8.57.1 + prettier: 3.2.5 + prettier-linter-helpers: 1.0.0 + synckit: 0.11.8 + optionalDependencies: + eslint-config-prettier: 8.10.0(eslint@8.57.1) + + eslint-plugin-react-hooks@5.2.0(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + + eslint-plugin-react-native-globals@0.1.2: {} + + eslint-plugin-react-native@4.1.0(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + eslint-plugin-react-native-globals: 0.1.2 + + eslint-plugin-react@7.33.2(eslint@8.57.1): + dependencies: + array-includes: 3.1.7 + array.prototype.flatmap: 1.3.2 + array.prototype.tosorted: 1.1.2 + doctrine: 2.1.0 + es-iterator-helpers: 1.0.15 + eslint: 8.57.1 + estraverse: 5.3.0 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.5 + object.entries: 1.1.7 + object.fromentries: 2.0.7 + object.hasown: 1.1.3 + object.values: 1.1.7 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.10 + + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@2.1.0: {} + + eslint-visitor-keys@3.4.3: {} + + eslint@8.57.1: + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.1 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.3.0 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.1 + 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.5.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.23.0 + graphemer: 1.4.0 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + 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.5 + natural-compare: 1.4.0 + optionator: 0.9.3 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + espree@9.6.1: + dependencies: + acorn: 8.11.2 + acorn-jsx: 5.3.2(acorn@8.11.2) + eslint-visitor-keys: 3.4.3 + + esprima@4.0.1: {} + + esquery@1.5.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + event-target-shim@5.0.1: {} + + events@3.3.0: {} + + execa@4.1.0: + dependencies: + cross-spawn: 7.0.6 + get-stream: 5.2.0 + human-signals: 1.1.1 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + exit-x@0.2.2: {} + + expect@29.7.0: + dependencies: + '@jest/expect-utils': 29.7.0 + jest-get-type: 29.6.3 + jest-matcher-utils: 29.7.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + + expect@30.0.5: + dependencies: + '@jest/expect-utils': 30.0.5 + '@jest/get-type': 30.0.1 + jest-matcher-utils: 30.0.5 + jest-message-util: 30.0.5 + jest-mock: 30.0.5 + jest-util: 30.0.5 + + exponential-backoff@3.1.1: {} + + fast-deep-equal@3.1.3: {} + + fast-diff@1.3.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fast-xml-parser@4.5.3: + dependencies: + strnum: 1.1.2 + + fastq@1.15.0: + dependencies: + reusify: 1.0.4 + + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + filter-obj@1.1.0: {} + + finalhandler@1.1.2: + 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 + transitivePeerDependencies: + - supports-color + + find-babel-config@2.1.2: + dependencies: + json5: 2.2.3 + + find-up@3.0.0: + dependencies: + locate-path: 3.0.0 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@3.2.0: + dependencies: + flatted: 3.2.9 + keyv: 4.5.4 + rimraf: 3.0.2 + + flatted@3.2.9: {} + + flow-enums-runtime@0.0.6: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.1.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + fresh@0.5.2: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.1 + universalify: 2.0.1 + + fs-extra@8.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.6: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.22.3 + functions-have-names: 1.2.3 + + functions-have-names@1.2.3: {} + + generator-function@2.0.1: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-intrinsic@1.3.0: + 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 + + get-package-type@0.1.0: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@5.2.0: + dependencies: + pump: 3.0.4 + + get-stream@6.0.1: {} + + get-symbol-description@1.0.0: + dependencies: + call-bind: 1.0.8 + get-intrinsic: 1.3.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.3.10: + dependencies: + foreground-child: 3.1.1 + jackspeak: 2.3.6 + minimatch: 9.0.5 + minipass: 7.0.4 + path-scurry: 1.10.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.9 + once: 1.4.0 + + glob@9.3.5: + dependencies: + fs.realpath: 1.0.0 + minimatch: 8.0.7 + minipass: 4.2.8 + path-scurry: 1.10.1 + + globals@13.23.0: + dependencies: + type-fest: 0.20.2 + + globalthis@1.0.3: + dependencies: + define-properties: 1.2.1 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + graphql-tag@2.12.6(graphql@16.13.2): + dependencies: + graphql: 16.13.2 + tslib: 2.8.1 + + graphql@16.13.2: {} + + handlebars@4.7.8: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + + has-bigints@1.0.2: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.0.1: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hermes-estree@0.28.1: {} + + hermes-estree@0.29.1: {} + + hermes-parser@0.28.1: + dependencies: + hermes-estree: 0.28.1 + + hermes-parser@0.29.1: + dependencies: + hermes-estree: 0.29.1 + + hoist-non-react-statics@3.3.2: + dependencies: + react-is: 16.13.1 + + html-escaper@2.0.2: {} + + http-errors@2.0.0: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.1 + toidentifier: 1.0.1 + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + + human-signals@1.1.1: {} + + human-signals@2.1.0: {} + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + image-size@1.0.2: + dependencies: + queue: 6.0.2 + + import-fresh@2.0.0: + dependencies: + caller-path: 2.0.0 + resolve-from: 3.0.0 + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + internal-slot@1.0.6: + dependencies: + get-intrinsic: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + invariant@2.2.4: + dependencies: + loose-envify: 1.4.0 + + is-absolute@1.0.0: + dependencies: + is-relative: 1.0.0 + is-windows: 1.0.2 + + is-arguments@1.2.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-array-buffer@3.0.2: + dependencies: + call-bind: 1.0.8 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-arrayish@0.2.1: {} + + is-arrayish@0.3.4: {} + + is-async-function@2.0.0: + dependencies: + has-tostringtag: 1.0.2 + + is-bigint@1.0.4: + dependencies: + has-bigints: 1.0.2 + + is-boolean-object@1.1.2: + dependencies: + call-bind: 1.0.8 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-date-object@1.0.5: + dependencies: + has-tostringtag: 1.0.2 + + is-directory@0.3.1: {} + + is-docker@2.2.1: {} + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.0.2: + dependencies: + call-bind: 1.0.8 + + is-fullwidth-code-point@2.0.0: {} + + is-fullwidth-code-point@3.0.0: {} + + is-generator-fn@2.1.0: {} + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-git-dirty@2.0.2: + dependencies: + execa: 4.1.0 + is-git-repository: 2.0.0 + + is-git-repository@2.0.0: + dependencies: + execa: 4.1.0 + is-absolute: 1.0.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-interactive@1.0.0: {} + + is-map@2.0.2: {} + + is-negative-zero@2.0.2: {} + + is-number-object@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-path-cwd@2.2.0: {} + + is-path-inside@3.0.3: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-relative@1.0.0: + dependencies: + is-unc-path: 1.0.0 + + is-set@2.0.2: {} + + is-shared-array-buffer@1.0.2: + dependencies: + call-bind: 1.0.8 + + is-stream@2.0.1: {} + + is-string@1.0.7: + dependencies: + has-tostringtag: 1.0.2 + + is-symbol@1.0.4: + dependencies: + has-symbols: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.20 + + is-unc-path@1.0.0: + dependencies: + unc-path-regex: 0.1.2 + + is-unicode-supported@0.1.0: {} + + is-weakmap@2.0.1: {} + + is-weakref@1.0.2: + dependencies: + call-bind: 1.0.8 + + is-weakset@2.0.2: + dependencies: + call-bind: 1.0.8 + get-intrinsic: 1.3.0 + + is-windows@1.0.2: {} + + is-wsl@1.1.0: {} + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@5.2.1: + dependencies: + '@babel/core': 7.28.3 + '@babel/parser': 7.29.2 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.28.3 + '@babel/parser': 7.29.2 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 7.7.2 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@5.0.6: + dependencies: + '@jridgewell/trace-mapping': 0.3.30 + debug: 4.4.1 + istanbul-lib-coverage: 3.2.2 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.1.6: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + iterator.prototype@1.1.2: + dependencies: + define-properties: 1.2.1 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + reflect.getprototypeof: 1.0.4 + set-function-name: 2.0.1 + + jackspeak@2.3.6: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + jest-changed-files@30.0.5: + dependencies: + execa: 5.1.1 + jest-util: 30.0.5 + p-limit: 3.1.0 + + jest-circus@30.0.5: + dependencies: + '@jest/environment': 30.0.5 + '@jest/expect': 30.0.5 + '@jest/test-result': 30.0.5 + '@jest/types': 30.0.5 + '@types/node': 20.9.3 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.6.0 + is-generator-fn: 2.1.0 + jest-each: 30.0.5 + jest-matcher-utils: 30.0.5 + jest-message-util: 30.0.5 + jest-runtime: 30.0.5 + jest-snapshot: 30.0.5 + jest-util: 30.0.5 + p-limit: 3.1.0 + pretty-format: 30.0.5 + pure-rand: 7.0.1 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-cli@30.0.5(@types/node@20.9.3): + dependencies: + '@jest/core': 30.0.5 + '@jest/test-result': 30.0.5 + '@jest/types': 30.0.5 + chalk: 4.1.2 + exit-x: 0.2.2 + import-local: 3.2.0 + jest-config: 30.0.5(@types/node@20.9.3) + jest-util: 30.0.5 + jest-validate: 30.0.5 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + + jest-config@30.0.5(@types/node@20.9.3): + dependencies: + '@babel/core': 7.28.3 + '@jest/get-type': 30.0.1 + '@jest/pattern': 30.0.1 + '@jest/test-sequencer': 30.0.5 + '@jest/types': 30.0.5 + babel-jest: 30.0.5(@babel/core@7.28.3) + chalk: 4.1.2 + ci-info: 4.2.0 + deepmerge: 4.3.1 + glob: 10.3.10 + graceful-fs: 4.2.11 + jest-circus: 30.0.5 + jest-docblock: 30.0.1 + jest-environment-node: 30.0.5 + jest-regex-util: 30.0.1 + jest-resolve: 30.0.5 + jest-runner: 30.0.5 + jest-util: 30.0.5 + jest-validate: 30.0.5 + micromatch: 4.0.8 + parse-json: 5.2.0 + pretty-format: 30.0.5 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 20.9.3 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-diff@29.7.0: + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-diff@30.0.5: + dependencies: + '@jest/diff-sequences': 30.0.1 + '@jest/get-type': 30.0.1 + chalk: 4.1.2 + pretty-format: 30.0.5 + + jest-docblock@30.0.1: + dependencies: + detect-newline: 3.1.0 + + jest-each@30.0.5: + dependencies: + '@jest/get-type': 30.0.1 + '@jest/types': 30.0.5 + chalk: 4.1.2 + jest-util: 30.0.5 + pretty-format: 30.0.5 + + jest-environment-node@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 20.9.3 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + jest-environment-node@30.0.5: + dependencies: + '@jest/environment': 30.0.5 + '@jest/fake-timers': 30.0.5 + '@jest/types': 30.0.5 + '@types/node': 20.9.3 + jest-mock: 30.0.5 + jest-util: 30.0.5 + jest-validate: 30.0.5 + + jest-get-type@29.6.3: {} + + jest-haste-map@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.9 + '@types/node': 20.9.3 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + jest-worker: 29.7.0 + micromatch: 4.0.8 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-haste-map@30.0.5: + dependencies: + '@jest/types': 30.0.5 + '@types/node': 20.9.3 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 30.0.1 + jest-util: 30.0.5 + jest-worker: 30.0.5 + micromatch: 4.0.8 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-leak-detector@30.0.5: + dependencies: + '@jest/get-type': 30.0.1 + pretty-format: 30.0.5 + + jest-matcher-utils@29.7.0: + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-matcher-utils@30.0.5: + dependencies: + '@jest/get-type': 30.0.1 + chalk: 4.1.2 + jest-diff: 30.0.5 + pretty-format: 30.0.5 + + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.27.1 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-message-util@30.0.5: + dependencies: + '@babel/code-frame': 7.27.1 + '@jest/types': 30.0.5 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 30.0.5 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.9.3 + jest-util: 29.7.0 + + jest-mock@30.0.5: + dependencies: + '@jest/types': 30.0.5 + '@types/node': 20.9.3 + jest-util: 30.0.5 + + jest-pnp-resolver@1.2.3(jest-resolve@30.0.5): + optionalDependencies: + jest-resolve: 30.0.5 + + jest-regex-util@29.6.3: {} + + jest-regex-util@30.0.1: {} + + jest-resolve-dependencies@30.0.5: + dependencies: + jest-regex-util: 30.0.1 + jest-snapshot: 30.0.5 + transitivePeerDependencies: + - supports-color + + jest-resolve@30.0.5: + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 30.0.5 + jest-pnp-resolver: 1.2.3(jest-resolve@30.0.5) + jest-util: 30.0.5 + jest-validate: 30.0.5 + slash: 3.0.0 + unrs-resolver: 1.9.2 + + jest-runner@30.0.5: + dependencies: + '@jest/console': 30.0.5 + '@jest/environment': 30.0.5 + '@jest/test-result': 30.0.5 + '@jest/transform': 30.0.5 + '@jest/types': 30.0.5 + '@types/node': 20.9.3 + chalk: 4.1.2 + emittery: 0.13.1 + exit-x: 0.2.2 + graceful-fs: 4.2.11 + jest-docblock: 30.0.1 + jest-environment-node: 30.0.5 + jest-haste-map: 30.0.5 + jest-leak-detector: 30.0.5 + jest-message-util: 30.0.5 + jest-resolve: 30.0.5 + jest-runtime: 30.0.5 + jest-util: 30.0.5 + jest-watcher: 30.0.5 + jest-worker: 30.0.5 + p-limit: 3.1.0 + source-map-support: 0.5.13 + transitivePeerDependencies: + - supports-color + + jest-runtime@30.0.5: + dependencies: + '@jest/environment': 30.0.5 + '@jest/fake-timers': 30.0.5 + '@jest/globals': 30.0.5 + '@jest/source-map': 30.0.1 + '@jest/test-result': 30.0.5 + '@jest/transform': 30.0.5 + '@jest/types': 30.0.5 + '@types/node': 20.9.3 + chalk: 4.1.2 + cjs-module-lexer: 2.1.0 + collect-v8-coverage: 1.0.2 + glob: 10.3.10 + graceful-fs: 4.2.11 + jest-haste-map: 30.0.5 + jest-message-util: 30.0.5 + jest-mock: 30.0.5 + jest-regex-util: 30.0.1 + jest-resolve: 30.0.5 + jest-snapshot: 30.0.5 + jest-util: 30.0.5 + slash: 3.0.0 + strip-bom: 4.0.0 + transitivePeerDependencies: + - supports-color + + jest-snapshot@30.0.5: + dependencies: + '@babel/core': 7.28.3 + '@babel/generator': 7.28.3 + '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.3) + '@babel/types': 7.28.2 + '@jest/expect-utils': 30.0.5 + '@jest/get-type': 30.0.1 + '@jest/snapshot-utils': 30.0.5 + '@jest/transform': 30.0.5 + '@jest/types': 30.0.5 + babel-preset-current-node-syntax: 1.1.0(@babel/core@7.28.3) + chalk: 4.1.2 + expect: 30.0.5 + graceful-fs: 4.2.11 + jest-diff: 30.0.5 + jest-matcher-utils: 30.0.5 + jest-message-util: 30.0.5 + jest-util: 30.0.5 + pretty-format: 30.0.5 + semver: 7.7.2 + synckit: 0.11.8 + transitivePeerDependencies: + - supports-color + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 20.9.3 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + + jest-util@30.0.5: + dependencies: + '@jest/types': 30.0.5 + '@types/node': 20.9.3 + chalk: 4.1.2 + ci-info: 4.2.0 + graceful-fs: 4.2.11 + picomatch: 4.0.2 + + jest-validate@29.7.0: + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + + jest-validate@30.0.5: + dependencies: + '@jest/get-type': 30.0.1 + '@jest/types': 30.0.5 + camelcase: 6.3.0 + chalk: 4.1.2 + leven: 3.1.0 + pretty-format: 30.0.5 + + jest-watcher@30.0.5: + dependencies: + '@jest/test-result': 30.0.5 + '@jest/types': 30.0.5 + '@types/node': 20.9.3 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 30.0.5 + string-length: 4.0.2 + + jest-worker@29.7.0: + dependencies: + '@types/node': 20.9.3 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest-worker@30.0.5: + dependencies: + '@types/node': 20.9.3 + '@ungap/structured-clone': 1.3.0 + jest-util: 30.0.5 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest@30.0.5(@types/node@20.9.3): + dependencies: + '@jest/core': 30.0.5 + '@jest/types': 30.0.5 + import-local: 3.2.0 + jest-cli: 30.0.5(@types/node@20.9.3) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - esbuild-register + - supports-color + - ts-node + + joi@17.13.3: + 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 + + jotai@2.19.1(@babel/core@7.28.3)(@babel/template@7.28.6)(@types/react@19.1.12)(react@19.1.0): + optionalDependencies: + '@babel/core': 7.28.3 + '@babel/template': 7.28.6 + '@types/react': 19.1.12 + react: 19.1.0 + + js-tokens@4.0.0: {} + + js-yaml@3.14.1: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.0: + dependencies: + argparse: 2.0.1 + + jsc-safe-url@0.2.4: {} + + jsesc@3.0.2: {} + + json-buffer@3.0.1: {} + + json-parse-better-errors@1.0.2: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsonfile@4.0.0: + optionalDependencies: + graceful-fs: 4.2.11 + + jsonfile@6.2.1: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.7 + array.prototype.flat: 1.3.2 + object.assign: 4.1.4 + object.values: 1.1.7 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kleur@3.0.3: {} + + kleur@4.1.5: {} + + launch-editor@2.11.1: + dependencies: + picocolors: 1.1.1 + shell-quote: 1.8.3 + + leven@3.1.0: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lighthouse-logger@1.4.2: + dependencies: + debug: 2.6.9 + marky: 1.2.5 + transitivePeerDependencies: + - supports-color + + lines-and-columns@1.2.4: {} + + locate-path@3.0.0: + dependencies: + p-locate: 3.0.0 + path-exists: 3.0.0 + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.debounce@4.0.8: {} + + lodash.memoize@4.1.2: {} + + lodash.merge@4.6.2: {} + + lodash.throttle@4.1.1: {} + + lodash@4.17.21: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + logkitty@0.7.1: + dependencies: + ansi-fragments: 0.2.1 + dayjs: 1.11.15 + yargs: 15.4.1 + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@10.0.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + make-dir@4.0.0: + dependencies: + semver: 7.7.2 + + make-error@1.3.6: {} + + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + + marky@1.2.5: {} + + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + memoize-one@5.2.1: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + metro-babel-transformer@0.82.5: + dependencies: + '@babel/core': 7.28.3 + flow-enums-runtime: 0.0.6 + hermes-parser: 0.29.1 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + metro-cache-key@0.82.5: + dependencies: + flow-enums-runtime: 0.0.6 + + metro-cache@0.82.5: + dependencies: + exponential-backoff: 3.1.1 + flow-enums-runtime: 0.0.6 + https-proxy-agent: 7.0.6 + metro-core: 0.82.5 + transitivePeerDependencies: + - supports-color + + metro-config@0.82.5: + dependencies: + connect: 3.7.0 + cosmiconfig: 5.2.1 + flow-enums-runtime: 0.0.6 + jest-validate: 29.7.0 + metro: 0.82.5 + metro-cache: 0.82.5 + metro-core: 0.82.5 + metro-runtime: 0.82.5 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + metro-core@0.82.5: + dependencies: + flow-enums-runtime: 0.0.6 + lodash.throttle: 4.1.1 + metro-resolver: 0.82.5 + + metro-file-map@0.82.5: + dependencies: + debug: 4.4.1 + fb-watchman: 2.0.2 + flow-enums-runtime: 0.0.6 + graceful-fs: 4.2.11 + invariant: 2.2.4 + jest-worker: 29.7.0 + micromatch: 4.0.8 + nullthrows: 1.1.1 + walker: 1.0.8 + transitivePeerDependencies: + - supports-color + + metro-minify-terser@0.82.5: + dependencies: + flow-enums-runtime: 0.0.6 + terser: 5.24.0 + + metro-resolver@0.82.5: + dependencies: + flow-enums-runtime: 0.0.6 + + metro-runtime@0.82.5: + dependencies: + '@babel/runtime': 7.28.3 + flow-enums-runtime: 0.0.6 + + metro-source-map@0.82.5: + dependencies: + '@babel/traverse': 7.28.3 + '@babel/traverse--for-generate-function-map': '@babel/traverse@7.29.0' + '@babel/types': 7.28.2 + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + metro-symbolicate: 0.82.5 + nullthrows: 1.1.1 + ob1: 0.82.5 + source-map: 0.5.7 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + + metro-symbolicate@0.82.5: + dependencies: + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + metro-source-map: 0.82.5 + nullthrows: 1.1.1 + source-map: 0.5.7 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + + metro-transform-plugins@0.82.5: + dependencies: + '@babel/core': 7.28.3 + '@babel/generator': 7.29.1 + '@babel/template': 7.28.6 + '@babel/traverse': 7.28.3 + flow-enums-runtime: 0.0.6 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + metro-transform-worker@0.82.5: + dependencies: + '@babel/core': 7.28.3 + '@babel/generator': 7.29.1 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + flow-enums-runtime: 0.0.6 + metro: 0.82.5 + metro-babel-transformer: 0.82.5 + metro-cache: 0.82.5 + metro-cache-key: 0.82.5 + metro-minify-terser: 0.82.5 + metro-source-map: 0.82.5 + metro-transform-plugins: 0.82.5 + nullthrows: 1.1.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + metro@0.82.5: + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/core': 7.28.3 + '@babel/generator': 7.28.3 + '@babel/parser': 7.28.3 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.3 + '@babel/types': 7.28.2 + accepts: 1.3.8 + chalk: 4.1.2 + ci-info: 2.0.0 + connect: 3.7.0 + debug: 4.4.1 + error-stack-parser: 2.1.4 + flow-enums-runtime: 0.0.6 + graceful-fs: 4.2.11 + hermes-parser: 0.29.1 + image-size: 1.0.2 + invariant: 2.2.4 + jest-worker: 29.7.0 + jsc-safe-url: 0.2.4 + lodash.throttle: 4.1.1 + metro-babel-transformer: 0.82.5 + metro-cache: 0.82.5 + metro-cache-key: 0.82.5 + metro-config: 0.82.5 + metro-core: 0.82.5 + metro-file-map: 0.82.5 + metro-resolver: 0.82.5 + metro-runtime: 0.82.5 + metro-source-map: 0.82.5 + metro-symbolicate: 0.82.5 + metro-transform-plugins: 0.82.5 + metro-transform-worker: 0.82.5 + mime-types: 2.1.35 + nullthrows: 1.1.1 + serialize-error: 2.1.0 + source-map: 0.5.7 + throat: 5.0.0 + ws: 7.5.10 + yargs: 17.7.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + + mime@2.6.0: {} + + mimic-fn@2.1.0: {} + + min-indent@1.0.1: {} + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.11 + + minimatch@5.1.9: + dependencies: + brace-expansion: 2.0.1 + + minimatch@8.0.7: + dependencies: + brace-expansion: 2.0.1 + + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + + minimist@1.2.8: {} + + minipass@4.2.8: {} + + minipass@7.0.4: {} + + mkdirp@1.0.4: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + napi-postinstall@0.2.5: {} + + natural-compare@1.4.0: {} + + negotiator@0.6.3: {} + + negotiator@0.6.4: {} + + neo-async@2.6.2: {} + + nocache@3.0.4: {} + + node-int64@0.4.0: {} + + node-releases@2.0.19: {} + + node-stream-zip@1.15.0: {} + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + nullthrows@1.1.1: {} + + ob1@0.82.5: + dependencies: + flow-enums-runtime: 0.0.6 + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.7: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.22.3 + + object.fromentries@2.0.7: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.22.3 + + object.hasown@1.1.3: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.22.3 + + object.values@1.1.7: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.22.3 + + on-finished@2.3.0: + dependencies: + ee-first: 1.1.1 + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + on-headers@1.1.0: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + open@6.4.0: + dependencies: + is-wsl: 1.1.0 + + open@7.4.2: + dependencies: + is-docker: 2.2.1 + is-wsl: 2.2.0 + + optimism@0.18.1: + dependencies: + '@wry/caches': 1.0.1 + '@wry/context': 0.7.4 + '@wry/trie': 0.5.0 + tslib: 2.8.1 + + optionator@0.9.3: + dependencies: + '@aashutoshrathi/word-wrap': 1.2.6 + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + + ora@5.4.1: + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@3.0.0: + dependencies: + p-limit: 2.3.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-map@4.0.0: + dependencies: + aggregate-error: 3.1.0 + + p-try@2.2.0: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@4.0.0: + dependencies: + error-ex: 1.3.2 + json-parse-better-errors: 1.0.2 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.0 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parseurl@1.3.3: {} + + path-exists@3.0.0: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.10.1: + dependencies: + lru-cache: 10.0.3 + minipass: 7.0.4 + + path-type@4.0.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.2: {} + + pirates@4.0.7: {} + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + + pkg-up@3.1.0: + dependencies: + find-up: 3.0.0 + + possible-typed-array-names@1.1.0: {} + + prelude-ls@1.2.1: {} + + prettier-linter-helpers@1.0.0: + dependencies: + fast-diff: 1.3.0 + + prettier@3.2.5: {} + + pretty-format@26.6.2: + dependencies: + '@jest/types': 26.6.2 + ansi-regex: 5.0.1 + ansi-styles: 4.3.0 + react-is: 17.0.2 + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + pretty-format@30.0.5: + dependencies: + '@jest/schemas': 30.0.5 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + process@0.11.10: {} + + promise@8.3.0: + dependencies: + asap: 2.0.6 + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + punycode@2.3.1: {} + + pure-rand@7.0.1: {} + + qs@6.13.0: + dependencies: + side-channel: 1.1.0 + + query-string@7.1.3: + dependencies: + decode-uri-component: 0.2.2 + filter-obj: 1.1.0 + split-on-first: 1.1.0 + strict-uri-encode: 2.0.0 + + queue-microtask@1.2.3: {} + + queue@6.0.2: + dependencies: + inherits: 2.0.4 + + range-parser@1.2.1: {} + + raw-body@2.5.2: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.0 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + react-devtools-core@6.1.5: + dependencies: + shell-quote: 1.8.3 + ws: 7.5.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + react-freeze@1.0.4(react@19.1.0): + dependencies: + react: 19.1.0 + + react-is@16.13.1: {} + + react-is@17.0.2: {} + + react-is@18.3.1: {} + + react-is@19.1.1: {} + + react-native-builder-bob@0.23.2: + dependencies: + '@babel/core': 7.28.3 + '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.28.3) + '@babel/preset-env': 7.28.3(@babel/core@7.28.3) + '@babel/preset-flow': 7.27.1(@babel/core@7.28.3) + '@babel/preset-react': 7.28.5(@babel/core@7.28.3) + '@babel/preset-typescript': 7.28.5(@babel/core@7.28.3) + browserslist: 4.25.3 + cosmiconfig: 7.1.0 + cross-spawn: 7.0.6 + dedent: 0.7.0 + del: 6.1.1 + fs-extra: 10.1.0 + glob: 8.1.0 + is-git-dirty: 2.0.2 + json5: 2.2.3 + kleur: 4.1.5 + prompts: 2.4.2 + which: 2.0.2 + yargs: 17.7.2 + transitivePeerDependencies: + - supports-color + + react-native-config@1.5.6: {} + + react-native-dotenv@3.4.11(@babel/runtime@7.28.3): + dependencies: + '@babel/runtime': 7.28.3 + dotenv: 16.6.1 + + react-native-encrypted-storage@4.0.3(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + react-native: 0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + + react-native-gesture-handler@2.26.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): + dependencies: + '@egjs/hammerjs': 2.0.17 + hoist-non-react-statics: 3.3.2 + invariant: 2.2.4 + react: 19.1.0 + react-native: 0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + + react-native-gradle-plugin@0.71.19: {} + + react-native-is-edge-to-edge@1.1.7(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + react-native: 0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + + react-native-is-edge-to-edge@1.3.1(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + react-native: 0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + + react-native-nitro-modules@0.33.9(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + react-native: 0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + + react-native-quick-base64@2.2.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + react-native: 0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + + react-native-quick-crypto@1.0.9(react-native-nitro-modules@0.33.9(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native-quick-base64@2.2.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0))(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): + dependencies: + '@craftzdog/react-native-buffer': 6.1.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + events: 3.3.0 + react: 19.1.0 + react-native: 0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + react-native-nitro-modules: 0.33.9(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + react-native-quick-base64: 2.2.2(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + readable-stream: 4.5.2 + safe-buffer: 5.2.1 + util: 0.12.5 + + react-native-reanimated@3.18.0(@babel/core@7.28.3)(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): + dependencies: + '@babel/core': 7.28.3 + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-class-properties': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-classes': 7.28.3(@babel/core@7.28.3) + '@babel/plugin-transform-nullish-coalescing-operator': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-optional-chaining': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.28.3) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.28.3) + '@babel/preset-typescript': 7.28.5(@babel/core@7.28.3) + convert-source-map: 2.0.0 + invariant: 2.2.4 + react: 19.1.0 + react-native: 0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + react-native-is-edge-to-edge: 1.1.7(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + transitivePeerDependencies: + - supports-color + + react-native-safe-area-context@5.7.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + react-native: 0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + + react-native-screens@4.16.0(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): + dependencies: + react: 19.1.0 + react-freeze: 1.0.4(react@19.1.0) + react-native: 0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + react-native-is-edge-to-edge: 1.3.1(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + warn-once: 0.1.1 + + react-native-vector-icons@10.3.0: + dependencies: + prop-types: 15.8.1 + yargs: 16.2.0 + + react-native-webview@13.16.1(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0): + dependencies: + escape-string-regexp: 4.0.0 + invariant: 2.2.4 + react: 19.1.0 + react-native: 0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0) + + react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0): + dependencies: + '@jest/create-cache-key-function': 29.7.0 + '@react-native/assets-registry': 0.80.2 + '@react-native/codegen': 0.80.2(@babel/core@7.28.3) + '@react-native/community-cli-plugin': 0.80.2(@react-native-community/cli@19.1.1(typescript@5.9.2)) + '@react-native/gradle-plugin': 0.80.2 + '@react-native/js-polyfills': 0.80.2 + '@react-native/normalize-colors': 0.80.2 + '@react-native/virtualized-lists': 0.80.2(@types/react@19.1.12)(react-native@0.80.2(@babel/core@7.28.3)(@react-native-community/cli@19.1.1(typescript@5.9.2))(@types/react@19.1.12)(react@19.1.0))(react@19.1.0) + abort-controller: 3.0.0 + anser: 1.4.10 + ansi-regex: 5.0.1 + babel-jest: 29.7.0(@babel/core@7.28.3) + babel-plugin-syntax-hermes-parser: 0.28.1 + base64-js: 1.5.1 + chalk: 4.1.2 + commander: 12.1.0 + flow-enums-runtime: 0.0.6 + glob: 7.2.3 + invariant: 2.2.4 + jest-environment-node: 29.7.0 + memoize-one: 5.2.1 + metro-runtime: 0.82.5 + metro-source-map: 0.82.5 + nullthrows: 1.1.1 + pretty-format: 29.7.0 + promise: 8.3.0 + react: 19.1.0 + react-devtools-core: 6.1.5 + react-refresh: 0.14.0 + regenerator-runtime: 0.13.11 + scheduler: 0.26.0 + semver: 7.7.2 + stacktrace-parser: 0.1.10 + whatwg-fetch: 3.6.19 + ws: 6.2.3 + yargs: 17.7.2 + optionalDependencies: + '@types/react': 19.1.12 + transitivePeerDependencies: + - '@babel/core' + - '@react-native-community/cli' + - bufferutil + - supports-color + - utf-8-validate + + react-refresh@0.14.0: {} + + react-test-renderer@19.1.0(react@19.1.0): + dependencies: + react: 19.1.0 + react-is: 19.1.1 + scheduler: 0.26.0 + + react@19.1.0: {} + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readable-stream@4.5.2: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + reflect.getprototypeof@1.0.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.22.3 + get-intrinsic: 1.3.0 + globalthis: 1.0.3 + which-builtin-type: 1.1.3 + + regenerate-unicode-properties@10.2.0: + dependencies: + regenerate: 1.4.2 + + regenerate@1.4.2: {} + + regenerator-runtime@0.13.11: {} + + regexp.prototype.flags@1.5.1: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + set-function-name: 2.0.1 + + regexpu-core@6.2.0: + dependencies: + regenerate: 1.4.2 + regenerate-unicode-properties: 10.2.0 + regjsgen: 0.8.0 + regjsparser: 0.12.0 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.1.0 + + regjsgen@0.8.0: {} + + regjsparser@0.12.0: + dependencies: + jsesc: 3.0.2 + + rehackt@0.1.0(@types/react@19.1.12)(react@19.1.0): + optionalDependencies: + '@types/react': 19.1.12 + react: 19.1.0 + + require-directory@2.1.1: {} + + require-main-filename@2.0.0: {} + + reselect@4.1.8: {} + + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + + resolve-from@3.0.0: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve@1.22.10: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + resolve@2.0.0-next.5: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + reusify@1.0.4: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-array-concat@1.0.1: + dependencies: + call-bind: 1.0.8 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-buffer@5.2.1: {} + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safer-buffer@2.1.2: {} + + scheduler@0.26.0: {} + + semver@6.3.1: {} + + semver@7.7.2: {} + + send@0.19.0: + 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 + transitivePeerDependencies: + - supports-color + + serialize-error@2.1.0: {} + + serve-static@1.16.2: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.0 + transitivePeerDependencies: + - supports-color + + set-blocking@2.0.0: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.1: + dependencies: + define-data-property: 1.1.4 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + setimmediate@1.0.5: {} + + setprototypeof@1.2.0: {} + + sf-symbols-typescript@2.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shell-quote@1.8.3: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + simple-swizzle@0.2.4: + dependencies: + is-arrayish: 0.3.4 + + sisteransi@1.0.5: {} + + slash@3.0.0: {} + + slice-ansi@2.1.0: + dependencies: + ansi-styles: 3.2.1 + astral-regex: 1.0.0 + is-fullwidth-code-point: 2.0.0 + + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.5.7: {} + + source-map@0.6.1: {} + + split-on-first@1.1.0: {} + + sprintf-js@1.0.3: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + stackframe@1.3.4: {} + + stacktrace-parser@0.1.10: + dependencies: + type-fest: 0.7.1 + + statuses@1.5.0: {} + + statuses@2.0.1: {} + + strict-uri-encode@2.0.0: {} + + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + + string-natural-compare@3.0.1: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + + string.prototype.matchall@4.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.22.3 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + internal-slot: 1.0.6 + regexp.prototype.flags: 1.5.1 + set-function-name: 2.0.1 + side-channel: 1.1.0 + + string.prototype.trim@1.2.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.22.3 + + string.prototype.trimend@1.0.7: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.22.3 + + string.prototype.trimstart@1.0.7: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.22.3 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@5.2.0: + dependencies: + ansi-regex: 4.1.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.0: + dependencies: + ansi-regex: 6.0.1 + + strip-bom@4.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + + strnum@1.1.2: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + symbol-observable@4.0.0: {} + + synckit@0.11.8: + dependencies: + '@pkgr/core': 0.2.7 + + terser@5.24.0: + dependencies: + '@jridgewell/source-map': 0.3.5 + acorn: 8.11.2 + commander: 2.20.3 + source-map-support: 0.5.21 + + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.3 + glob: 7.2.3 + minimatch: 3.1.5 + + text-table@0.2.0: {} + + throat@5.0.0: {} + + tmpl@1.0.5: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + ts-api-utils@1.4.3(typescript@5.9.2): + dependencies: + typescript: 5.9.2 + + ts-invariant@0.10.3: + dependencies: + tslib: 2.8.1 + + ts-jest@29.4.1(@babel/core@7.28.3)(@jest/transform@30.0.5)(@jest/types@30.0.5)(babel-jest@30.0.5(@babel/core@7.28.3))(jest-util@30.0.5)(jest@30.0.5(@types/node@20.9.3))(typescript@5.9.2): + dependencies: + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + handlebars: 4.7.8 + jest: 30.0.5(@types/node@20.9.3) + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.7.2 + type-fest: 4.41.0 + typescript: 5.9.2 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.28.3 + '@jest/transform': 30.0.5 + '@jest/types': 30.0.5 + babel-jest: 30.0.5(@babel/core@7.28.3) + jest-util: 30.0.5 + + tslib@1.14.1: {} + + tslib@2.8.1: {} + + tsutils@3.21.0(typescript@5.9.2): + dependencies: + tslib: 1.14.1 + typescript: 5.9.2 + + turbo-darwin-64@1.13.4: + optional: true + + turbo-darwin-arm64@1.13.4: + optional: true + + turbo-linux-64@1.13.4: + optional: true + + turbo-linux-arm64@1.13.4: + optional: true + + turbo-windows-64@1.13.4: + optional: true + + turbo-windows-arm64@1.13.4: + optional: true + + turbo@1.13.4: + optionalDependencies: + turbo-darwin-64: 1.13.4 + turbo-darwin-arm64: 1.13.4 + turbo-linux-64: 1.13.4 + turbo-linux-arm64: 1.13.4 + turbo-windows-64: 1.13.4 + turbo-windows-arm64: 1.13.4 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-detect@4.0.8: {} + + type-fest@0.20.2: {} + + type-fest@0.21.3: {} + + type-fest@0.7.1: {} + + type-fest@4.41.0: {} + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + typed-array-buffer@1.0.0: + dependencies: + call-bind: 1.0.8 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.0: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + has-proto: 1.0.1 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.0: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + has-proto: 1.0.1 + is-typed-array: 1.1.15 + + typed-array-length@1.0.4: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + is-typed-array: 1.1.15 + + typescript@5.9.2: {} + + uglify-js@3.19.3: + optional: true + + unbox-primitive@1.0.2: + dependencies: + call-bind: 1.0.8 + has-bigints: 1.0.2 + has-symbols: 1.1.0 + which-boxed-primitive: 1.0.2 + + unc-path-regex@0.1.2: {} + + undici-types@5.26.5: {} + + unicode-canonical-property-names-ecmascript@2.0.0: {} + + unicode-match-property-ecmascript@2.0.0: + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.0 + unicode-property-aliases-ecmascript: 2.1.0 + + unicode-match-property-value-ecmascript@2.1.0: {} + + unicode-property-aliases-ecmascript@2.1.0: {} + + universalify@0.1.2: {} + + universalify@2.0.1: {} + + unpipe@1.0.0: {} + + unrs-resolver@1.9.2: + dependencies: + napi-postinstall: 0.2.5 + optionalDependencies: + '@unrs/resolver-binding-android-arm-eabi': 1.9.2 + '@unrs/resolver-binding-android-arm64': 1.9.2 + '@unrs/resolver-binding-darwin-arm64': 1.9.2 + '@unrs/resolver-binding-darwin-x64': 1.9.2 + '@unrs/resolver-binding-freebsd-x64': 1.9.2 + '@unrs/resolver-binding-linux-arm-gnueabihf': 1.9.2 + '@unrs/resolver-binding-linux-arm-musleabihf': 1.9.2 + '@unrs/resolver-binding-linux-arm64-gnu': 1.9.2 + '@unrs/resolver-binding-linux-arm64-musl': 1.9.2 + '@unrs/resolver-binding-linux-ppc64-gnu': 1.9.2 + '@unrs/resolver-binding-linux-riscv64-gnu': 1.9.2 + '@unrs/resolver-binding-linux-riscv64-musl': 1.9.2 + '@unrs/resolver-binding-linux-s390x-gnu': 1.9.2 + '@unrs/resolver-binding-linux-x64-gnu': 1.9.2 + '@unrs/resolver-binding-linux-x64-musl': 1.9.2 + '@unrs/resolver-binding-wasm32-wasi': 1.9.2 + '@unrs/resolver-binding-win32-arm64-msvc': 1.9.2 + '@unrs/resolver-binding-win32-ia32-msvc': 1.9.2 + '@unrs/resolver-binding-win32-x64-msvc': 1.9.2 + + update-browserslist-db@1.1.3(browserslist@4.25.3): + dependencies: + browserslist: 4.25.3 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-latest-callback@0.2.6(react@19.1.0): + dependencies: + react: 19.1.0 + + use-sync-external-store@1.6.0(react@19.1.0): + dependencies: + react: 19.1.0 + + util-deprecate@1.0.2: {} + + util@0.12.5: + dependencies: + inherits: 2.0.4 + is-arguments: 1.2.0 + is-generator-function: 1.1.2 + is-typed-array: 1.1.15 + which-typed-array: 1.1.20 + + utils-merge@1.0.1: {} + + v8-to-istanbul@9.1.3: + dependencies: + '@jridgewell/trace-mapping': 0.3.30 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + + vary@1.1.2: {} + + vlq@1.0.1: {} + + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + warn-once@0.1.1: {} + + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + + whatwg-fetch@3.6.19: {} + + which-boxed-primitive@1.0.2: + dependencies: + is-bigint: 1.0.4 + is-boolean-object: 1.1.2 + is-number-object: 1.0.7 + is-string: 1.0.7 + is-symbol: 1.0.4 + + which-builtin-type@1.1.3: + dependencies: + function.prototype.name: 1.1.6 + has-tostringtag: 1.0.2 + is-async-function: 2.0.0 + is-date-object: 1.0.5 + is-finalizationregistry: 1.0.2 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.0.2 + isarray: 2.0.5 + which-boxed-primitive: 1.0.2 + which-collection: 1.0.1 + which-typed-array: 1.1.20 + + which-collection@1.0.1: + dependencies: + is-map: 2.0.2 + is-set: 2.0.2 + is-weakmap: 2.0.1 + is-weakset: 2.0.2 + + which-module@2.0.1: {} + + which-typed-array@1.1.20: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wordwrap@1.0.0: {} + + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + + wrappy@1.0.2: {} + + write-file-atomic@4.0.2: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + + write-file-atomic@5.0.1: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 4.1.0 + + ws@6.2.3: + dependencies: + async-limiter: 1.0.1 + + ws@7.5.10: {} + + y18n@4.0.3: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yaml@1.10.3: {} + + yaml@2.8.1: {} + + yargs-parser@18.1.3: + dependencies: + camelcase: 5.3.1 + decamelize: 1.2.0 + + yargs-parser@20.2.9: {} + + yargs-parser@21.1.1: {} + + yargs@15.4.1: + dependencies: + cliui: 6.0.0 + decamelize: 1.2.0 + find-up: 4.1.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + require-main-filename: 2.0.0 + set-blocking: 2.0.0 + string-width: 4.2.3 + which-module: 2.0.1 + y18n: 4.0.3 + yargs-parser: 18.1.3 + + yargs@16.2.0: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} + + zen-observable-ts@1.2.5: + dependencies: + zen-observable: 0.8.15 + + zen-observable@0.8.15: {} diff --git a/react-native/pnpm-workspace.yaml b/react-native/pnpm-workspace.yaml new file mode 100644 index 00000000..98ea88fc --- /dev/null +++ b/react-native/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + - sample + - 'modules/**/*' diff --git a/react-native/sample/.bundle/config b/react-native/sample/.bundle/config new file mode 100644 index 00000000..848943bb --- /dev/null +++ b/react-native/sample/.bundle/config @@ -0,0 +1,2 @@ +BUNDLE_PATH: "vendor/bundle" +BUNDLE_FORCE_RUBY_PLATFORM: 1 diff --git a/react-native/sample/.env.example b/react-native/sample/.env.example new file mode 100644 index 00000000..adaf5d9f --- /dev/null +++ b/react-native/sample/.env.example @@ -0,0 +1,24 @@ +# Storefront Details +STOREFRONT_DOMAIN="YOUR_STORE.myshopify.com" +STOREFRONT_ACCESS_TOKEN="YOUR_PUBLIC_STOREFRONT_ACCESS_TOKEN" +STOREFRONT_MERCHANT_IDENTIFIER=example.merchant.com.shopify + +# Storefront API version +STOREFRONT_VERSION="2025-07" + +# Prefilled buyer information +EMAIL="checkout-kit@shopify.com" +ADDRESS_1="650 King Street" +ADDRESS_2="Shopify HQ" +CITY="Toronto" +COMPANY="Shopify" +COUNTRY="CA" +FIRST_NAME="Evelyn" +LAST_NAME="Hartley" +PROVINCE="ON" +ZIP="M5V 1M7" +PHONE="1-888-746-7439" + +# Customer Account API (for OAuth authentication) +CUSTOMER_ACCOUNT_API_CLIENT_ID="YOUR_CUSTOMER_ACCOUNT_API_CLIENT_ID" +CUSTOMER_ACCOUNT_API_SHOP_ID="YOUR_SHOP_ID" diff --git a/react-native/sample/.eslintignore b/react-native/sample/.eslintignore new file mode 100644 index 00000000..ba885ebe --- /dev/null +++ b/react-native/sample/.eslintignore @@ -0,0 +1,2 @@ +android/build +android/app/build diff --git a/react-native/sample/@types/env.d.ts b/react-native/sample/@types/env.d.ts new file mode 100644 index 00000000..d995b75e --- /dev/null +++ b/react-native/sample/@types/env.d.ts @@ -0,0 +1,20 @@ +declare module '@env' { + export const STOREFRONT_DOMAIN: string; + export const STOREFRONT_ACCESS_TOKEN: string; + export const STOREFRONT_VERSION: string; + + export const EMAIL: string; + export const ADDRESS_1: string; + export const ADDRESS_2: string; + export const CITY: string; + export const COMPANY: string; + export const COUNTRY: string; + export const FIRST_NAME: string; + export const LAST_NAME: string; + export const PROVINCE: string; + export const ZIP: string; + export const PHONE: string; + + export const CUSTOMER_ACCOUNT_API_CLIENT_ID: string; + export const CUSTOMER_ACCOUNT_API_SHOP_ID: string; +} diff --git a/react-native/sample/@types/index.d.ts b/react-native/sample/@types/index.d.ts new file mode 100644 index 00000000..d3afdf6c --- /dev/null +++ b/react-native/sample/@types/index.d.ts @@ -0,0 +1,66 @@ +export type Edges = { + edges: {node: T}[]; +}; + +interface Price { + amount: string; + currencyCode: string; +} + +interface CartCost { + subtotalAmount: Price; + totalAmount: Price; + totalTaxAmount: Price; +} + +export interface ProductVariant { + id: string; + price: Price; +} + +export interface ShopifyProduct { + id: string; + title: string; + description: string; + images: Edges<{ + id: string; + altText: string; + url: string; + thumbnailUrl: string; + }>; + variants: Edges; +} + +export interface CartItem { + id: string; + price: Price; + product: { + title: string; + }; + image: { + id: string; + width: number; + height: number; + url: string; + thumbnailUrl: string; + altText: string; + }; +} + +export interface CartLineItem { + id: string; + merchandise: CartItem; + quantity: number; + cost: { + totalAmount: { + currencyCode: string; + amount: string; + }; + }; +} + +export interface ShopifyCart { + cost: CartCost; + lines: Edges; + totalQuantity: number; +} diff --git a/react-native/sample/Gemfile b/react-native/sample/Gemfile new file mode 100644 index 00000000..d69241c4 --- /dev/null +++ b/react-native/sample/Gemfile @@ -0,0 +1,17 @@ +source 'https://rubygems.org' + +# You may use http://rbenv.org/ or https://rvm.io/ to install and use this version +ruby ">= 2.6.10" + +# Exclude problematic versions of cocoapods and activesupport that causes build failures. +gem 'cocoapods', '>= 1.13', '!= 1.15.0', '!= 1.15.1' +gem 'activesupport', '>= 6.1.7.5', '!= 7.1.0' +gem 'xcodeproj', '< 1.26.0' +gem 'concurrent-ruby', '< 1.3.4' +gem 'cocoapods-check', '1.1.0' + +# Ruby 3.4.0 has removed some libraries from the standard library. +gem 'bigdecimal' +gem 'logger' +gem 'benchmark' +gem 'mutex_m' diff --git a/react-native/sample/Gemfile.lock b/react-native/sample/Gemfile.lock new file mode 100644 index 00000000..13b9ab76 --- /dev/null +++ b/react-native/sample/Gemfile.lock @@ -0,0 +1,127 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.7) + base64 + nkf + rexml + activesupport (7.2.2.2) + base64 + benchmark (>= 0.3) + bigdecimal + concurrent-ruby (~> 1.0, >= 1.3.1) + connection_pool (>= 2.2.5) + drb + i18n (>= 1.6, < 2) + logger (>= 1.4.2) + minitest (>= 5.1) + securerandom (>= 0.3) + tzinfo (~> 2.0, >= 2.0.5) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) + algoliasearch (1.27.5) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) + atomos (0.1.3) + base64 (0.3.0) + benchmark (0.4.1) + bigdecimal (3.2.3) + claide (1.1.0) + cocoapods (1.15.2) + addressable (~> 2.8) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.15.2) + cocoapods-deintegrate (>= 1.0.3, < 2.0) + cocoapods-downloader (>= 2.1, < 3.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.6.0, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (>= 2.3.0, < 3.0) + gh_inspector (~> 1.0) + molinillo (~> 0.8.0) + nap (~> 1.0) + ruby-macho (>= 2.3.0, < 3.0) + xcodeproj (>= 1.23.0, < 2.0) + cocoapods-check (1.1.0) + cocoapods (~> 1.0) + cocoapods-core (1.15.2) + activesupport (>= 5.0, < 8) + addressable (~> 2.8) + algoliasearch (~> 1.0) + concurrent-ruby (~> 1.1) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + netrc (~> 0.11) + public_suffix (~> 4.0) + typhoeus (~> 1.0) + cocoapods-deintegrate (1.0.5) + cocoapods-downloader (2.1) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.1) + cocoapods-trunk (1.6.0) + nap (>= 0.8, < 2.0) + netrc (~> 0.11) + cocoapods-try (1.2.0) + colored2 (3.1.2) + concurrent-ruby (1.3.3) + connection_pool (2.5.4) + drb (2.2.3) + escape (0.0.4) + ethon (0.15.0) + ffi (>= 1.15.0) + ffi (1.17.2) + fourflusher (2.3.1) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) + httpclient (2.9.0) + mutex_m + i18n (1.14.7) + concurrent-ruby (~> 1.0) + json (2.15.0) + logger (1.7.0) + minitest (5.25.5) + molinillo (0.8.0) + mutex_m (0.3.0) + nanaimo (0.3.0) + nap (1.1.0) + netrc (0.11.0) + nkf (0.2.0) + public_suffix (4.0.7) + rexml (3.4.4) + ruby-macho (2.5.1) + securerandom (0.4.1) + typhoeus (1.5.0) + ethon (>= 0.9.0, < 0.16.0) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + xcodeproj (1.25.1) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.3.0) + rexml (>= 3.3.6, < 4.0) + +PLATFORMS + ruby + +DEPENDENCIES + activesupport (>= 6.1.7.5, != 7.1.0) + benchmark + bigdecimal + cocoapods (>= 1.13, != 1.15.1, != 1.15.0) + cocoapods-check (= 1.1.0) + concurrent-ruby (< 1.3.4) + logger + mutex_m + xcodeproj (< 1.26.0) + +RUBY VERSION + ruby 3.3.6p108 + +BUNDLED WITH + 2.5.23 diff --git a/react-native/sample/android/app/build.gradle b/react-native/sample/android/app/build.gradle new file mode 100644 index 00000000..9e54dfc0 --- /dev/null +++ b/react-native/sample/android/app/build.gradle @@ -0,0 +1,208 @@ +apply plugin: "com.android.application" +apply plugin: "org.jetbrains.kotlin.android" +apply plugin: "com.facebook.react" + +def resolveNodeModuleDir(String packageName) { + def output = new ByteArrayOutputStream() + def workspaceRoot = new File(rootProject.projectDir, "../..").canonicalFile + exec { + commandLine( + "node", + "--print", + "require.resolve('${packageName}/package.json', { paths: [process.argv[1], process.argv[2]] })", + workspaceRoot.getAbsolutePath(), + rootProject.projectDir.getAbsolutePath() + ) + standardOutput = output + } + return file(output.toString().trim()).parentFile +} + +def reactNativeDirPath = resolveNodeModuleDir("react-native") +def reactNativeCodegenDirPath = resolveNodeModuleDir("@react-native/codegen") +def vectorIconsDirPath = resolveNodeModuleDir("react-native-vector-icons") + +project.ext["REACT_NATIVE_NODE_MODULES_DIR"] = reactNativeDirPath + +/** + * This is the configuration block to customize your React Native Android app. + * By default you don't need to apply any configuration, just uncomment the lines you need. + */ +react { + /* Folders */ + // The root of your project, i.e. where "package.json" lives. Default is '..' + root = file("../../") + // The folder where the react-native NPM package is. Default is ../node_modules/react-native + reactNativeDir = reactNativeDirPath + // The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen + codegenDir = reactNativeCodegenDirPath + // The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js + cliFile = new File(reactNativeDirPath, "cli.js") + + /* Variants */ + // The list of variants to that are debuggable. For those we're going to + // skip the bundling of the JS bundle and the assets. By default is just 'debug'. + // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. + // debuggableVariants = ["liteDebug", "prodDebug"] + + /* Bundling */ + // A list containing the node command and its flags. Default is just 'node'. + nodeExecutableAndArgs = ["node"] + // + // The command to run when bundling. By default is 'bundle' + // bundleCommand = "ram-bundle" + // + // The path to the CLI configuration file. Default is empty. + // bundleConfig = file(../rn-cli.config.js) + // + // The name of the generated asset file containing your JS bundle + bundleAssetName = "ReactNative.android.bundle" + // + // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' + entryFile = file("../../index.js") + // + // A list of extra flags to pass to the 'bundle' commands. + // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle + // extraPackagerArgs = [] + + /* Hermes Commands */ + // The hermes compiler command to run. By default it is 'hermesc' + hermesCommand = new File(reactNativeDirPath, "sdks/hermesc/%OS-BIN%/hermesc").getAbsolutePath() + // + // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" + hermesFlags = ["-O", "-output-source-map"] + + /* Autolinking */ + autolinkLibrariesWithApp() +} + +/** + * The preferred build flavor of JavaScriptCore (JSC) + * + * For example, to use the international variant, you can use: + * `def jscFlavor = io.github.react-native-community:jsc-android-intl:2026004.+` + * + * The international variant includes ICU i18n library and necessary data + * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that + * give correct results when using with locales other than en-US. Note that + * this variant is about 6MiB larger per architecture than default. + */ +def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+' + +android { + ndkVersion rootProject.ext.ndkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + compileSdk rootProject.ext.compileSdkVersion + + lintOptions { + checkDependencies false + } + tasks.configureEach { task -> + if (task.name == 'lintAnalyzeDebug' || task.name == 'generateReleaseLintVitalReportModel') { + task.dependsOn 'copyReactNativeVectorIconFonts' + } + } + + namespace "com.shopify.checkoutkitreactnative" + defaultConfig { + applicationId "com.shopify.checkoutkitreactnative" + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode 11 + versionName "1.1" + } + signingConfigs { + debug { + storeFile file('debug.keystore') + storePassword 'android' + keyAlias 'androiddebugkey' + keyPassword 'android' + } + release { + if (project.hasProperty('CHECKOUT_KIT_UPLOAD_STORE_FILE')) { + storeFile file(CHECKOUT_KIT_UPLOAD_STORE_FILE) + storePassword CHECKOUT_KIT_UPLOAD_STORE_PASSWORD + keyAlias CHECKOUT_KIT_UPLOAD_STORE_ALIAS + keyPassword CHECKOUT_KIT_UPLOAD_KEY_PASSWORD + } + } + } + buildTypes { + debug { + signingConfig signingConfigs.debug + } + release { + signingConfig signingConfigs.release + minifyEnabled true + proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" + } + } + + testOptions { + unitTests { + all { + testLogging { + events "failed", "skipped" + exceptionFormat "full" + showExceptions true + showCauses true + showStackTraces true + } + } + } + } +} + +dependencies { + // The version of react-native is set by the React Native Gradle Plugin + implementation("com.facebook.react:react-android") + + implementation project(":react-native-config") + + // Test dependencies + testImplementation "junit:junit:4.13.2" + testImplementation "org.mockito:mockito-core:4.11.0" + androidTestImplementation "org.mockito:mockito-android:4.11.0" + testImplementation "org.mockito:mockito-inline:5.2.0" + testImplementation "org.assertj:assertj-core:3.27.6" + testImplementation("com.shopify:checkout-sheet-kit:${SHOPIFY_CHECKOUT_SDK_VERSION}") + + if (hermesEnabled.toBoolean()) { + implementation("com.facebook.react:hermes-android") + } else { + implementation jscFlavor + } +} + +project.ext.vectoricons = [ + iconFontNames: [ 'Entypo.ttf' ] +] + +apply from: new File(vectorIconsDirPath, "fonts.gradle") + +apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle" + +def stripSurroundingQuotes(String value) { + if (value == null) { + return null + } + return value.trim()?.replaceAll(/^['"]|['"]$/, '') +} + +def properties = loadProperties() +def storefrontDomain = stripSurroundingQuotes(properties.getProperty("STOREFRONT_DOMAIN")) + +if (!storefrontDomain) { + println("**** Please add a .env file with STOREFRONT_DOMAIN set *****") +} + +task generateAndroidManifestFromTemplate { + doLast { + def templateFile = file('src/main/AndroidManifest.template.xml') + def outputFile = file('src/main/AndroidManifest.xml') + def content = templateFile.getText('UTF-8').replace('{{STOREFRONT_DOMAIN}}', "$storefrontDomain") + outputFile.write(content, 'UTF-8') + } +} + +preBuild.dependsOn(generateAndroidManifestFromTemplate) diff --git a/react-native/sample/android/app/debug.keystore b/react-native/sample/android/app/debug.keystore new file mode 100644 index 00000000..364e105e Binary files /dev/null and b/react-native/sample/android/app/debug.keystore differ diff --git a/react-native/sample/android/app/proguard-rules.pro b/react-native/sample/android/app/proguard-rules.pro new file mode 100644 index 00000000..a7136906 --- /dev/null +++ b/react-native/sample/android/app/proguard-rules.pro @@ -0,0 +1,11 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: +-keep class com.shopify.checkoutkitreactnative.BuildConfig { *; } diff --git a/react-native/sample/android/app/src/main/AndroidManifest.template.xml b/react-native/sample/android/app/src/main/AndroidManifest.template.xml new file mode 100644 index 00000000..7df6f471 --- /dev/null +++ b/react-native/sample/android/app/src/main/AndroidManifest.template.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/react-native/sample/android/app/src/main/assets/drawable-hdpi/node_modules_reactnavigation_elements_src_assets_backicon.png b/react-native/sample/android/app/src/main/assets/drawable-hdpi/node_modules_reactnavigation_elements_src_assets_backicon.png new file mode 100644 index 00000000..ad03a63b Binary files /dev/null and b/react-native/sample/android/app/src/main/assets/drawable-hdpi/node_modules_reactnavigation_elements_src_assets_backicon.png differ diff --git a/react-native/sample/android/app/src/main/assets/drawable-mdpi/node_modules_reactnavigation_elements_src_assets_backicon.png b/react-native/sample/android/app/src/main/assets/drawable-mdpi/node_modules_reactnavigation_elements_src_assets_backicon.png new file mode 100644 index 00000000..083db295 Binary files /dev/null and b/react-native/sample/android/app/src/main/assets/drawable-mdpi/node_modules_reactnavigation_elements_src_assets_backicon.png differ diff --git a/react-native/sample/android/app/src/main/assets/drawable-mdpi/node_modules_reactnavigation_elements_src_assets_backiconmask.png b/react-native/sample/android/app/src/main/assets/drawable-mdpi/node_modules_reactnavigation_elements_src_assets_backiconmask.png new file mode 100644 index 00000000..9de72420 Binary files /dev/null and b/react-native/sample/android/app/src/main/assets/drawable-mdpi/node_modules_reactnavigation_elements_src_assets_backiconmask.png differ diff --git a/react-native/sample/android/app/src/main/assets/drawable-xhdpi/node_modules_reactnavigation_elements_src_assets_backicon.png b/react-native/sample/android/app/src/main/assets/drawable-xhdpi/node_modules_reactnavigation_elements_src_assets_backicon.png new file mode 100644 index 00000000..6de0a1cb Binary files /dev/null and b/react-native/sample/android/app/src/main/assets/drawable-xhdpi/node_modules_reactnavigation_elements_src_assets_backicon.png differ diff --git a/react-native/sample/android/app/src/main/assets/drawable-xxhdpi/node_modules_reactnavigation_elements_src_assets_backicon.png b/react-native/sample/android/app/src/main/assets/drawable-xxhdpi/node_modules_reactnavigation_elements_src_assets_backicon.png new file mode 100644 index 00000000..15a983a6 Binary files /dev/null and b/react-native/sample/android/app/src/main/assets/drawable-xxhdpi/node_modules_reactnavigation_elements_src_assets_backicon.png differ diff --git a/react-native/sample/android/app/src/main/assets/drawable-xxxhdpi/node_modules_reactnavigation_elements_src_assets_backicon.png b/react-native/sample/android/app/src/main/assets/drawable-xxxhdpi/node_modules_reactnavigation_elements_src_assets_backicon.png new file mode 100644 index 00000000..17e52e85 Binary files /dev/null and b/react-native/sample/android/app/src/main/assets/drawable-xxxhdpi/node_modules_reactnavigation_elements_src_assets_backicon.png differ diff --git a/react-native/sample/android/app/src/main/ic_launcher-playstore.png b/react-native/sample/android/app/src/main/ic_launcher-playstore.png new file mode 100644 index 00000000..9e198d57 Binary files /dev/null and b/react-native/sample/android/app/src/main/ic_launcher-playstore.png differ diff --git a/react-native/sample/android/app/src/main/java/com/shopify/checkoutkitreactnative/MainActivity.kt b/react-native/sample/android/app/src/main/java/com/shopify/checkoutkitreactnative/MainActivity.kt new file mode 100644 index 00000000..1893cc62 --- /dev/null +++ b/react-native/sample/android/app/src/main/java/com/shopify/checkoutkitreactnative/MainActivity.kt @@ -0,0 +1,22 @@ +package com.shopify.checkoutkitreactnative + +import com.facebook.react.ReactActivity +import com.facebook.react.ReactActivityDelegate +import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled +import com.facebook.react.defaults.DefaultReactActivityDelegate + +class MainActivity : ReactActivity() { + + /** + * Returns the name of the main component registered from JavaScript. This is used to schedule + * rendering of the component. + */ + override fun getMainComponentName(): String = "ReactNative" + + /** + * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] + * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] + */ + override fun createReactActivityDelegate(): ReactActivityDelegate = + DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) +} diff --git a/react-native/sample/android/app/src/main/java/com/shopify/checkoutkitreactnative/MainApplication.kt b/react-native/sample/android/app/src/main/java/com/shopify/checkoutkitreactnative/MainApplication.kt new file mode 100644 index 00000000..1a051402 --- /dev/null +++ b/react-native/sample/android/app/src/main/java/com/shopify/checkoutkitreactnative/MainApplication.kt @@ -0,0 +1,38 @@ +package com.shopify.checkoutkitreactnative + +import android.app.Application +import com.facebook.react.PackageList +import com.facebook.react.ReactApplication +import com.facebook.react.ReactHost +import com.facebook.react.ReactNativeHost +import com.facebook.react.ReactPackage +import com.facebook.react.ReactNativeApplicationEntryPoint.loadReactNative +import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost +import com.facebook.react.defaults.DefaultReactNativeHost + +class MainApplication : Application(), ReactApplication { + + override val reactNativeHost: ReactNativeHost = + object : DefaultReactNativeHost(this) { + override fun getPackages(): List = + PackageList(this).packages.apply { + // Packages that cannot be autolinked yet can be added manually here, for example: + // add(MyReactNativePackage()) + } + + override fun getJSMainModuleName(): String = "index" + + override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG + + override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED + override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED + } + + override val reactHost: ReactHost + get() = getDefaultReactHost(applicationContext, reactNativeHost) + + override fun onCreate() { + super.onCreate() + loadReactNative(this) + } +} diff --git a/react-native/sample/android/app/src/main/res/drawable/rn_edit_text_material.xml b/react-native/sample/android/app/src/main/res/drawable/rn_edit_text_material.xml new file mode 100644 index 00000000..73b37e4d --- /dev/null +++ b/react-native/sample/android/app/src/main/res/drawable/rn_edit_text_material.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + diff --git a/react-native/sample/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/react-native/sample/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..484e6f9b Binary files /dev/null and b/react-native/sample/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/react-native/sample/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/react-native/sample/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png new file mode 100644 index 00000000..89a7bb49 Binary files /dev/null and b/react-native/sample/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png differ diff --git a/react-native/sample/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/react-native/sample/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..cce0ead6 Binary files /dev/null and b/react-native/sample/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/react-native/sample/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/react-native/sample/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000..b9987349 Binary files /dev/null and b/react-native/sample/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/react-native/sample/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/react-native/sample/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..0114c656 Binary files /dev/null and b/react-native/sample/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/react-native/sample/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png b/react-native/sample/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png new file mode 100644 index 00000000..d1795de4 Binary files /dev/null and b/react-native/sample/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png differ diff --git a/react-native/sample/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/react-native/sample/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..39bda3c5 Binary files /dev/null and b/react-native/sample/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/react-native/sample/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/react-native/sample/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000..d31988ab Binary files /dev/null and b/react-native/sample/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/react-native/sample/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/react-native/sample/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..91d9f28f Binary files /dev/null and b/react-native/sample/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/react-native/sample/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/react-native/sample/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png new file mode 100644 index 00000000..0463dbbd Binary files /dev/null and b/react-native/sample/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png differ diff --git a/react-native/sample/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/react-native/sample/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..89a0fb57 Binary files /dev/null and b/react-native/sample/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/react-native/sample/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/react-native/sample/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000..fe8e69a1 Binary files /dev/null and b/react-native/sample/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/react-native/sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/react-native/sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..f83824c6 Binary files /dev/null and b/react-native/sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/react-native/sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/react-native/sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png new file mode 100644 index 00000000..d749c075 Binary files /dev/null and b/react-native/sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png differ diff --git a/react-native/sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/react-native/sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..4a72d13e Binary files /dev/null and b/react-native/sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/react-native/sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/react-native/sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..97054d9a Binary files /dev/null and b/react-native/sample/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/react-native/sample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/react-native/sample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..c0e5f98e Binary files /dev/null and b/react-native/sample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/react-native/sample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/react-native/sample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png new file mode 100644 index 00000000..a38a57ab Binary files /dev/null and b/react-native/sample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png differ diff --git a/react-native/sample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/react-native/sample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..37c1b75f Binary files /dev/null and b/react-native/sample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/react-native/sample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/react-native/sample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..25784679 Binary files /dev/null and b/react-native/sample/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/react-native/sample/android/app/src/main/res/values/strings.xml b/react-native/sample/android/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..a543a232 --- /dev/null +++ b/react-native/sample/android/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + ReactNative + Checkout + diff --git a/react-native/sample/android/app/src/main/res/values/styles.xml b/react-native/sample/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..7ba83a2a --- /dev/null +++ b/react-native/sample/android/app/src/main/res/values/styles.xml @@ -0,0 +1,9 @@ + + + + + + diff --git a/react-native/sample/android/app/src/main/res/xml/network_security_config.xml b/react-native/sample/android/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 00000000..ec4e0a64 --- /dev/null +++ b/react-native/sample/android/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/react-native/sample/android/app/src/test/java/com/shopify/checkoutkitreactnative/ShopifyCheckoutSheetKitModuleTest.java b/react-native/sample/android/app/src/test/java/com/shopify/checkoutkitreactnative/ShopifyCheckoutSheetKitModuleTest.java new file mode 100644 index 00000000..790c02dc --- /dev/null +++ b/react-native/sample/android/app/src/test/java/com/shopify/checkoutkitreactnative/ShopifyCheckoutSheetKitModuleTest.java @@ -0,0 +1,743 @@ +package com.shopify.checkoutkitreactnative; + +import androidx.activity.ComponentActivity; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.bridge.JavaOnlyMap; +import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridge.WritableMap; +import com.facebook.react.modules.core.DeviceEventManagerModule; +import com.shopify.checkoutsheetkit.CheckoutException; +import com.shopify.checkoutsheetkit.CheckoutExpiredException; +import com.shopify.checkoutsheetkit.CheckoutSheetKitException; +import com.shopify.checkoutsheetkit.ClientException; +import com.shopify.checkoutsheetkit.ConfigurationException; +import com.shopify.checkoutsheetkit.HttpException; +import com.shopify.checkoutsheetkit.ShopifyCheckoutSheetKit; +import com.shopify.checkoutsheetkit.Preloading; +import com.shopify.checkoutsheetkit.ColorScheme; +import com.shopify.checkoutsheetkit.LogLevel; +import com.shopify.checkoutsheetkit.pixelevents.PixelEvent; +import com.shopify.checkoutsheetkit.pixelevents.StandardPixelEvent; +import com.shopify.checkoutsheetkit.pixelevents.CustomPixelEvent; +import com.shopify.checkoutsheetkit.pixelevents.EventType; +import com.shopify.checkoutsheetkit.lifecycleevents.CheckoutCompletedEvent; +import com.shopify.checkoutsheetkit.lifecycleevents.OrderDetails; +import com.shopify.checkoutsheetkit.lifecycleevents.CartInfo; +import com.shopify.checkoutsheetkit.lifecycleevents.Price; +import com.shopify.reactnative.checkoutsheetkit.ShopifyCheckoutSheetKitModule; +import com.shopify.reactnative.checkoutsheetkit.CustomCheckoutEventProcessor; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.*; + +import android.content.Context; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(MockitoJUnitRunner.class) +public class ShopifyCheckoutSheetKitModuleTest { + @Mock + private ReactApplicationContext mockReactContext; + @Mock + private ComponentActivity mockComponentActivity; + @Mock + private DeviceEventManagerModule.RCTDeviceEventEmitter mockEventEmitter; + @Mock + private Context mockContext; + + @Captor + ArgumentCaptor runnableCaptor; + @Captor + private ArgumentCaptor stringCaptor; + + private ShopifyCheckoutSheetKitModule shopifyCheckoutSheetKitModule; + + // Store initial configuration to restore after each test + private Preloading initialPreloading; + private ColorScheme initialColorScheme; + private LogLevel initialLogLevel; + + // Mock for Arguments.createMap() to avoid native library loading + private MockedStatic mockedArguments; + + // Test constants for color configuration + private static final String BACKGROUND_COLOR = "#FFFFFF"; + private static final String PROGRESS_INDICATOR = "#000000"; + private static final String HEADER_BACKGROUND_COLOR = "#FFFFFF"; + private static final String HEADER_TEXT_COLOR = "#000000"; + + // Dark theme colors + private static final String DARK_BACKGROUND_COLOR = "#000000"; + private static final String DARK_PROGRESS_INDICATOR = "#FFFFFF"; + private static final String DARK_HEADER_BACKGROUND_COLOR = "#000000"; + private static final String DARK_HEADER_TEXT_COLOR = "#FFFFFF"; + + @Before + public void setup() { + mockedArguments = Mockito.mockStatic(Arguments.class); + mockedArguments.when(Arguments::createMap).thenAnswer(invocation -> new JavaOnlyMap()); + + when(mockReactContext.getCurrentActivity()).thenReturn(mockComponentActivity); + when(mockReactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)) + .thenReturn(mockEventEmitter); + shopifyCheckoutSheetKitModule = new ShopifyCheckoutSheetKitModule(mockReactContext); + + // Capture initial configuration state to restore after each test + initialPreloading = ShopifyCheckoutSheetKitModule.checkoutConfig.getPreloading(); + initialColorScheme = ShopifyCheckoutSheetKitModule.checkoutConfig.getColorScheme(); + initialLogLevel = ShopifyCheckoutSheetKitModule.checkoutConfig.getLogLevel(); + } + + @After + public void tearDown() { + // Close mocked static + if (mockedArguments != null) { + mockedArguments.close(); + } + + // Reset configuration to initial state after each test + ShopifyCheckoutSheetKit.configure(configuration -> { + configuration.setPreloading(initialPreloading); + configuration.setColorScheme(initialColorScheme); + configuration.setLogLevel(initialLogLevel); + ShopifyCheckoutSheetKitModule.checkoutConfig = configuration; + }); + } + + /** + * Core Methods + */ + + @Test + public void testCanPresentCheckout() { + try (MockedStatic mockedShopifyCheckoutSheetKit = Mockito + .mockStatic(ShopifyCheckoutSheetKit.class)) { + String checkoutUrl = "https://shopify.com"; + shopifyCheckoutSheetKitModule.present(checkoutUrl); + + verify(mockComponentActivity).runOnUiThread(runnableCaptor.capture()); + runnableCaptor.getValue().run(); + + mockedShopifyCheckoutSheetKit.verify(() -> { + ShopifyCheckoutSheetKit.present(eq(checkoutUrl), any(), any()); + }); + } + } + + @Test + public void testCanPreloadCheckout() { + try (MockedStatic mockedShopifyCheckoutSheetKit = Mockito + .mockStatic(ShopifyCheckoutSheetKit.class)) { + String checkoutUrl = "https://shopify.com"; + shopifyCheckoutSheetKitModule.preload(checkoutUrl); + + mockedShopifyCheckoutSheetKit.verify(() -> { + ShopifyCheckoutSheetKit.preload(eq(checkoutUrl), any()); + }); + } + } + + /** + * Module name and version + */ + + @Test + public void testModuleName() { + assertThat(shopifyCheckoutSheetKitModule.getName()) + .isEqualTo("ShopifyCheckoutSheetKit"); + } + + @Test + public void testConstants() { + assertThat(shopifyCheckoutSheetKitModule.getConstants()) + .isNotNull() + .containsKey("version"); + } + + /** + * Configuration + */ + + @Test + public void testHasCorrectDefaultConfiguration() { + // Test that the module starts with sensible defaults + assertThat(ShopifyCheckoutSheetKitModule.checkoutConfig.getPreloading().getEnabled()) + .isTrue(); + + assertThat(ShopifyCheckoutSheetKitModule.checkoutConfig.getColorScheme().getId()) + .isEqualTo("automatic"); + } + + @Test + public void testCanDisablePreloading() { + JavaOnlyMap config = new JavaOnlyMap(); + config.putBoolean("preloading", false); + + shopifyCheckoutSheetKitModule.setConfig(config); + + assertThat(ShopifyCheckoutSheetKitModule.checkoutConfig.getPreloading().getEnabled()) + .isFalse(); + } + + @Test + public void testCanSetDarkColorScheme() { + JavaOnlyMap config = new JavaOnlyMap(); + config.putString("colorScheme", "dark"); + + shopifyCheckoutSheetKitModule.setConfig(config); + + assertThat(ShopifyCheckoutSheetKitModule.checkoutConfig.getColorScheme().getId()) + .isEqualTo("dark"); + } + + @Test + public void testCanConfigureLightColorSchemeWithValidColors() { + JavaOnlyMap androidColors = createValidLightColors(); + JavaOnlyMap config = createConfigWithAndroidColors("light", androidColors); + + shopifyCheckoutSheetKitModule.setConfig(config); + + assertThat(ShopifyCheckoutSheetKitModule.checkoutConfig.getColorScheme().getId()) + .isEqualTo("light"); + } + + @Test + public void testCanConfigureDarkColorSchemeWithValidColors() { + JavaOnlyMap androidColors = createValidDarkColors(); + JavaOnlyMap config = createConfigWithAndroidColors("dark", androidColors); + + shopifyCheckoutSheetKitModule.setConfig(config); + + assertThat(ShopifyCheckoutSheetKitModule.checkoutConfig.getColorScheme().getId()) + .isEqualTo("dark"); + } + + @Test + public void testCanConfigureAutomaticColorSchemeWithLightAndDarkColors() { + JavaOnlyMap lightColors = createValidLightColors(); + JavaOnlyMap darkColors = createValidDarkColors(); + + JavaOnlyMap androidColors = new JavaOnlyMap(); + androidColors.putMap("light", lightColors); + androidColors.putMap("dark", darkColors); + + JavaOnlyMap colorsConfig = new JavaOnlyMap(); + colorsConfig.putMap("android", androidColors); + + JavaOnlyMap config = new JavaOnlyMap(); + config.putString("colorScheme", "automatic"); + config.putMap("colors", colorsConfig); + + shopifyCheckoutSheetKitModule.setConfig(config); + + assertThat(ShopifyCheckoutSheetKitModule.checkoutConfig.getColorScheme().getId()) + .isEqualTo("automatic"); + } + + @Test + public void testInvalidColorConfigurationFallsBackToBasicScheme() { + JavaOnlyMap androidColors = new JavaOnlyMap(); + androidColors.putString("backgroundColor", "invalid-color"); + androidColors.putString("progressIndicator", PROGRESS_INDICATOR); + androidColors.putString("headerBackgroundColor", HEADER_BACKGROUND_COLOR); + androidColors.putString("headerTextColor", HEADER_TEXT_COLOR); + + JavaOnlyMap config = createConfigWithAndroidColors("light", androidColors); + + // Should not throw exception + shopifyCheckoutSheetKitModule.setConfig(config); + + // Should fall back to basic light scheme without custom colors + assertThat(ShopifyCheckoutSheetKitModule.checkoutConfig.getColorScheme().getId()) + .isEqualTo("light"); + } + + @Test + public void testPartialColorConfigurationIsRejected() { + JavaOnlyMap androidColors = new JavaOnlyMap(); + androidColors.putString("backgroundColor", BACKGROUND_COLOR); + // Missing other required colors + + JavaOnlyMap config = createConfigWithAndroidColors("light", androidColors); + + shopifyCheckoutSheetKitModule.setConfig(config); + + // Should fall back to basic scheme since colors are incomplete + assertThat(ShopifyCheckoutSheetKitModule.checkoutConfig.getColorScheme().getId()) + .isEqualTo("light"); + } + + @Test + public void testCanSetConfigWithCloseButtonColor() { + JavaOnlyMap androidColors = createValidLightColors(); + androidColors.putString("closeButtonColor", "#FF0000"); + + JavaOnlyMap config = createConfigWithAndroidColors("light", androidColors); + + shopifyCheckoutSheetKitModule.setConfig(config); + + assertThat(ShopifyCheckoutSheetKitModule.checkoutConfig.getColorScheme().getId()) + .isEqualTo("light"); + } + + @Test + public void testCanSetConfigWithMissingCloseButtonColor() { + // Missing closeButtonColor - should not crash + JavaOnlyMap androidColors = createValidLightColors(); + JavaOnlyMap config = createConfigWithAndroidColors("light", androidColors); + + shopifyCheckoutSheetKitModule.setConfig(config); + + assertThat(ShopifyCheckoutSheetKitModule.checkoutConfig.getColorScheme().getId()) + .isEqualTo("light"); + } + + @Test + public void testCanSetConfigWithInvalidCloseButtonColor() { + JavaOnlyMap androidColors = createValidLightColors(); + androidColors.putString("closeButtonColor", "invalid-color"); + JavaOnlyMap config = createConfigWithAndroidColors("light", androidColors); + + // The method should not throw an exception when given invalid close button + // color + shopifyCheckoutSheetKitModule.setConfig(config); + + // Verify the color scheme was set correctly despite invalid close button color + assertThat(ShopifyCheckoutSheetKitModule.checkoutConfig.getColorScheme().getId()) + .isEqualTo("light"); + } + + /** + * Log Level Configuration + */ + + @Test + public void testCanSetLogLevelDebug() { + JavaOnlyMap config = new JavaOnlyMap(); + config.putString("logLevel", "debug"); + + shopifyCheckoutSheetKitModule.setConfig(config); + + assertThat(ShopifyCheckoutSheetKitModule.checkoutConfig.getLogLevel()) + .isEqualTo(LogLevel.DEBUG); + } + + @Test + public void testCanSetLogLevelError() { + JavaOnlyMap config = new JavaOnlyMap(); + config.putString("logLevel", "error"); + + shopifyCheckoutSheetKitModule.setConfig(config); + + assertThat(ShopifyCheckoutSheetKitModule.checkoutConfig.getLogLevel()) + .isEqualTo(LogLevel.ERROR); + } + + @Test + public void testCanSetLogLevelNone() { + JavaOnlyMap config = new JavaOnlyMap(); + config.putString("logLevel", "none"); + + shopifyCheckoutSheetKitModule.setConfig(config); + + // "none" maps to ERROR on Android (closest equivalent) + assertThat(ShopifyCheckoutSheetKitModule.checkoutConfig.getLogLevel()) + .isEqualTo(LogLevel.ERROR); + } + + @Test + public void testInvalidLogLevelDefaultsToError() { + JavaOnlyMap config = new JavaOnlyMap(); + config.putString("logLevel", "invalid"); + + shopifyCheckoutSheetKitModule.setConfig(config); + + assertThat(ShopifyCheckoutSheetKitModule.checkoutConfig.getLogLevel()) + .isEqualTo(LogLevel.ERROR); + } + + @Test + public void testLogLevelHandlesUppercaseDebug() { + JavaOnlyMap config = new JavaOnlyMap(); + config.putString("logLevel", "DEBUG"); + + shopifyCheckoutSheetKitModule.setConfig(config); + + assertThat(ShopifyCheckoutSheetKitModule.checkoutConfig.getLogLevel()) + .isEqualTo(LogLevel.DEBUG); + } + + @Test + public void testLogLevelHandlesMixedCaseDebug() { + JavaOnlyMap config = new JavaOnlyMap(); + config.putString("logLevel", "Debug"); + + shopifyCheckoutSheetKitModule.setConfig(config); + + assertThat(ShopifyCheckoutSheetKitModule.checkoutConfig.getLogLevel()) + .isEqualTo(LogLevel.DEBUG); + } + + @Test + public void testLogLevelHandlesUppercaseError() { + JavaOnlyMap config = new JavaOnlyMap(); + config.putString("logLevel", "ERROR"); + + shopifyCheckoutSheetKitModule.setConfig(config); + + assertThat(ShopifyCheckoutSheetKitModule.checkoutConfig.getLogLevel()) + .isEqualTo(LogLevel.ERROR); + } + + @Test + public void testSetConfigWithoutLogLevelDefaultsToError() { + JavaOnlyMap config = new JavaOnlyMap(); + config.putBoolean("preloading", true); + + shopifyCheckoutSheetKitModule.setConfig(config); + + assertThat(ShopifyCheckoutSheetKitModule.checkoutConfig.getLogLevel()) + .isEqualTo(LogLevel.ERROR); + } + + @Test + public void testGetConfigReturnsDebugForDebugLogLevel() { + JavaOnlyMap config = new JavaOnlyMap(); + config.putString("logLevel", "debug"); + + shopifyCheckoutSheetKitModule.setConfig(config); + + PromiseMock promise = new PromiseMock(); + shopifyCheckoutSheetKitModule.getConfig(promise); + + assertThat(promise.resolvedValue).isNotNull(); + JavaOnlyMap result = (JavaOnlyMap) promise.resolvedValue; + assertThat(result.getString("logLevel")).isEqualTo("debug"); + } + + @Test + public void testGetConfigReturnsErrorForErrorLogLevel() { + JavaOnlyMap config = new JavaOnlyMap(); + config.putString("logLevel", "error"); + + shopifyCheckoutSheetKitModule.setConfig(config); + + PromiseMock promise = new PromiseMock(); + shopifyCheckoutSheetKitModule.getConfig(promise); + + assertThat(promise.resolvedValue).isNotNull(); + JavaOnlyMap result = (JavaOnlyMap) promise.resolvedValue; + assertThat(result.getString("logLevel")).isEqualTo("error"); + } + + @Test + public void testGetConfigReturnsErrorForNoneLogLevel() { + JavaOnlyMap config = new JavaOnlyMap(); + config.putString("logLevel", "none"); + + shopifyCheckoutSheetKitModule.setConfig(config); + + PromiseMock promise = new PromiseMock(); + shopifyCheckoutSheetKitModule.getConfig(promise); + + assertThat(promise.resolvedValue).isNotNull(); + JavaOnlyMap result = (JavaOnlyMap) promise.resolvedValue; + assertThat(result.getString("logLevel")).isEqualTo("error"); + } + + @Test + public void testGetConfigReturnsErrorForInvalidLogLevel() { + JavaOnlyMap config = new JavaOnlyMap(); + config.putString("logLevel", "invalid"); + + shopifyCheckoutSheetKitModule.setConfig(config); + + PromiseMock promise = new PromiseMock(); + shopifyCheckoutSheetKitModule.getConfig(promise); + + assertThat(promise.resolvedValue).isNotNull(); + JavaOnlyMap result = (JavaOnlyMap) promise.resolvedValue; + assertThat(result.getString("logLevel")).isEqualTo("error"); + } + + @Test + public void testGetConfigReturnsDefaultLogLevel() { + PromiseMock promise = new PromiseMock(); + shopifyCheckoutSheetKitModule.getConfig(promise); + + assertThat(promise.resolvedValue).isNotNull(); + JavaOnlyMap result = (JavaOnlyMap) promise.resolvedValue; + assertThat(result.getString("logLevel")).isEqualTo("error"); + } + + /** + * Events + */ + + @Test + public void testCanProcessStandardPixelEvents() { + CustomCheckoutEventProcessor processor = new CustomCheckoutEventProcessor(mockContext, mockReactContext); + + PixelEvent standardEvent = new StandardPixelEvent( + "test-id", + "page_viewed", + "2023-01-01T00:00:00Z", + EventType.STANDARD, + null, + null); + + processor.onWebPixelEvent(standardEvent); + + verify(mockEventEmitter).emit(eq("pixel"), stringCaptor.capture()); + + assertThat(stringCaptor.getValue()) + .contains("test-id", "page_viewed", "STANDARD"); + } + + @Test + public void testCanProcessCustomPixelEvents() { + CustomCheckoutEventProcessor processor = new CustomCheckoutEventProcessor(mockContext, mockReactContext); + + PixelEvent customEvent = new CustomPixelEvent( + "custom-id", + "custom_event", + "2023-01-01T00:00:00Z", + EventType.CUSTOM, + null, + "{\"customAttribute\":\"value\"}"); + + processor.onWebPixelEvent(customEvent); + + verify(mockEventEmitter).emit(eq("pixel"), stringCaptor.capture()); + + assertThat(stringCaptor.getValue()) + .contains("custom-id", "custom_event", "CUSTOM", "customAttribute"); + } + + @Test + public void testCanProcessCheckoutCompletedEvents() { + CustomCheckoutEventProcessor processor = new CustomCheckoutEventProcessor(mockContext, mockReactContext); + + CartInfo cartInfo = new CartInfo(new ArrayList<>(), new Price(), "cart-token"); + OrderDetails orderDetails = new OrderDetails( + null, // billingAddress + cartInfo, + new ArrayList<>(), // deliveries + "test@example.com", // email + "order-123", // id + new ArrayList<>(), // paymentMethods + "+1234567890" // phone + ); + + CheckoutCompletedEvent completedEvent = new CheckoutCompletedEvent(orderDetails); + + processor.onCheckoutCompleted(completedEvent); + + verify(mockEventEmitter).emit(eq("completed"), stringCaptor.capture()); + + assertThat(stringCaptor.getValue()) + .contains("order-123", "test@example.com", "cart-token"); + } + + /** + * Errors + */ + + @Test + public void testCanProcessCheckoutExpiredErrors() { + CustomCheckoutEventProcessor processor = new CustomCheckoutEventProcessor(mockContext, mockReactContext); + + // Use minimal mocking - just enough to test the processing logic + CheckoutExpiredException mockException = mock(CheckoutExpiredException.class); + when(mockException.getErrorDescription()).thenReturn("Cart has expired"); + when(mockException.getErrorCode()).thenReturn("cart_expired"); + when(mockException.isRecoverable()).thenReturn(false); + + processor.onCheckoutFailed(mockException); + + verify(mockEventEmitter).emit(eq("error"), stringCaptor.capture()); + + assertThat(stringCaptor.getValue()) + .contains("CheckoutExpiredError", "Cart has expired", "cart_expired", "\"recoverable\":false"); + } + + @Test + public void testCanProcessClientErrors() { + CustomCheckoutEventProcessor processor = new CustomCheckoutEventProcessor(mockContext, mockReactContext); + + ClientException mockException = mock(ClientException.class); + when(mockException.getErrorDescription()).thenReturn("Customer account required"); + when(mockException.getErrorCode()).thenReturn("customer_account_required"); + when(mockException.isRecoverable()).thenReturn(true); + + processor.onCheckoutFailed(mockException); + + verify(mockEventEmitter).emit(eq("error"), stringCaptor.capture()); + + assertThat(stringCaptor.getValue()) + .contains("CheckoutClientError", "Customer account required", "customer_account_required", + "\"recoverable\":true"); + } + + @Test + public void testCanProcessHttpErrors() { + CustomCheckoutEventProcessor processor = new CustomCheckoutEventProcessor(mockContext, mockReactContext); + + HttpException mockException = mock(HttpException.class); + when(mockException.getErrorDescription()).thenReturn("Not Found"); + when(mockException.getErrorCode()).thenReturn("http_error"); + when(mockException.isRecoverable()).thenReturn(false); + when(mockException.getStatusCode()).thenReturn(404); + + processor.onCheckoutFailed(mockException); + + verify(mockEventEmitter).emit(eq("error"), stringCaptor.capture()); + + assertThat(stringCaptor.getValue()) + .contains("CheckoutHTTPError", "Not Found", "http_error", "\"statusCode\":404", "\"recoverable\":false"); + } + + /** + * Integration + */ + + @Test + public void testCompleteConfigurationAndEventFlow() { + // Set up configuration + JavaOnlyMap config = new JavaOnlyMap(); + config.putBoolean("preloading", true); + config.putString("colorScheme", "dark"); + + shopifyCheckoutSheetKitModule.setConfig(config); + + // Verify configuration was applied + assertThat(ShopifyCheckoutSheetKitModule.checkoutConfig.getPreloading().getEnabled()) + .isTrue(); + assertThat(ShopifyCheckoutSheetKitModule.checkoutConfig.getColorScheme().getId()) + .isEqualTo("dark"); + + // Test event processing with the configured module + CustomCheckoutEventProcessor processor = new CustomCheckoutEventProcessor(mockContext, mockReactContext); + + PixelEvent event = new StandardPixelEvent("test", "page_viewed", "timestamp", EventType.STANDARD, null, null); + processor.onWebPixelEvent(event); + + verify(mockEventEmitter).emit(eq("pixel"), any(String.class)); + } + + /** + * Helpers + */ + + private JavaOnlyMap createValidLightColors() { + JavaOnlyMap colors = new JavaOnlyMap(); + colors.putString("backgroundColor", BACKGROUND_COLOR); + colors.putString("progressIndicator", PROGRESS_INDICATOR); + colors.putString("headerBackgroundColor", HEADER_BACKGROUND_COLOR); + colors.putString("headerTextColor", HEADER_TEXT_COLOR); + return colors; + } + + private JavaOnlyMap createValidDarkColors() { + JavaOnlyMap colors = new JavaOnlyMap(); + colors.putString("backgroundColor", DARK_BACKGROUND_COLOR); + colors.putString("progressIndicator", DARK_PROGRESS_INDICATOR); + colors.putString("headerBackgroundColor", DARK_HEADER_BACKGROUND_COLOR); + colors.putString("headerTextColor", DARK_HEADER_TEXT_COLOR); + return colors; + } + + private JavaOnlyMap createConfigWithAndroidColors(String colorScheme, JavaOnlyMap androidColors) { + JavaOnlyMap colorsConfig = new JavaOnlyMap(); + colorsConfig.putMap("android", androidColors); + + JavaOnlyMap config = new JavaOnlyMap(); + config.putString("colorScheme", colorScheme); + config.putMap("colors", colorsConfig); + return config; + } + + private static class PromiseMock implements Promise { + public Object resolvedValue; + public String rejectedCode; + public String rejectedMessage; + public Throwable rejectedThrowable; + + @Override + public void resolve(Object value) { + resolvedValue = value; + } + + @Override + public void reject(String code, String message) { + rejectedCode = code; + rejectedMessage = message; + } + + @Override + public void reject(String code, Throwable throwable) { + rejectedCode = code; + rejectedThrowable = throwable; + } + + @Override + public void reject(String code, String message, Throwable throwable) { + rejectedCode = code; + rejectedMessage = message; + rejectedThrowable = throwable; + } + + @Override + public void reject(Throwable throwable) { + rejectedThrowable = throwable; + } + + @Override + public void reject(Throwable throwable, WritableMap userInfo) { + rejectedThrowable = throwable; + } + + @Override + public void reject(String code, WritableMap userInfo) { + rejectedCode = code; + } + + @Override + public void reject(String code, Throwable throwable, WritableMap userInfo) { + rejectedCode = code; + rejectedThrowable = throwable; + } + + @Override + public void reject(String code, String message, WritableMap userInfo) { + rejectedCode = code; + rejectedMessage = message; + } + + @Override + public void reject(String code, String message, Throwable throwable, WritableMap userInfo) { + rejectedCode = code; + rejectedMessage = message; + rejectedThrowable = throwable; + } + + @Override + public void reject(String message) { + rejectedMessage = message; + } + } +} diff --git a/react-native/sample/android/build.gradle b/react-native/sample/android/build.gradle new file mode 100644 index 00000000..d895aa1d --- /dev/null +++ b/react-native/sample/android/build.gradle @@ -0,0 +1,38 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + ext { + buildToolsVersion = "35.0.0" + minSdkVersion = 24 + compileSdkVersion = 36 + targetSdkVersion = 35 + ndkVersion = "27.1.12297006" + kotlinVersion = "2.1.20" + } + repositories { + google() + mavenCentral() + } + dependencies { + classpath("com.android.tools.build:gradle:8.6.0") + classpath("com.facebook.react:react-native-gradle-plugin") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") + } +} + +def loadProperties() { + def props = new Properties() + file('../.env').withInputStream { + props.load(it) + } + return props +} + +apply plugin: "com.facebook.react.rootproject" + +// Configure memory for forked test JVMs to avoid OOMs in CI +subprojects { + tasks.withType(Test).configureEach { + maxHeapSize = "1536m" + jvmArgs "-XX:MaxMetaspaceSize=512m" + } +} diff --git a/react-native/sample/android/gradle.properties b/react-native/sample/android/gradle.properties new file mode 100644 index 00000000..fcae32ff --- /dev/null +++ b/react-native/sample/android/gradle.properties @@ -0,0 +1,44 @@ +# Project-wide Gradle settings. + +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. + +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html + +# IMPORTANT: This config applies ONLY to the Gradle daemon (local development). +# CI runs tests with `--no-daemon`, so this setting is IGNORED in CI. +# CI memory is configured in `.github/workflows/ci.yml` (GRADLE_OPTS) and +# forked test JVM memory is configured in `sample/android/build.gradle` (Test tasks). +org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m + + +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true + +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true + +# Use this property to specify which architecture you want to build. +# You can also override it from the CLI using +# ./gradlew -PreactNativeArchitectures=x86_64 +reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64 + +# Use this property to enable support to the new architecture. +# This will allow you to use TurboModules and the Fabric render in +# your application. You should enable this flag either if you want +# to write custom TurboModules/Fabric components OR use libraries that +# are providing them. +newArchEnabled=true + +# Use this property to enable or disable the Hermes JS engine. +# If set to false, you will be using JSC instead. +hermesEnabled=true + +# Note: only used here for testing +SHOPIFY_CHECKOUT_SDK_VERSION=3.5.3 diff --git a/react-native/sample/android/gradle/wrapper/gradle-wrapper.jar b/react-native/sample/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..1b33c55b Binary files /dev/null and b/react-native/sample/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/react-native/sample/android/gradle/wrapper/gradle-wrapper.properties b/react-native/sample/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..002b867c --- /dev/null +++ b/react-native/sample/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/react-native/sample/android/gradlew b/react-native/sample/android/gradlew new file mode 100755 index 00000000..23d15a93 --- /dev/null +++ b/react-native/sample/android/gradlew @@ -0,0 +1,251 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.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. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH="\\\"\\\"" + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/react-native/sample/android/gradlew.bat b/react-native/sample/android/gradlew.bat new file mode 100644 index 00000000..dd2b8eed --- /dev/null +++ b/react-native/sample/android/gradlew.bat @@ -0,0 +1,99 @@ +@REM Copyright (c) Meta Platforms, Inc. and affiliates. +@REM +@REM This source code is licensed under the MIT license found in the +@REM LICENSE file in the root directory of this source tree. + +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH= + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/react-native/sample/android/settings.gradle b/react-native/sample/android/settings.gradle new file mode 100644 index 00000000..4d29763b --- /dev/null +++ b/react-native/sample/android/settings.gradle @@ -0,0 +1,43 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } + includeBuild("../../node_modules/@react-native/gradle-plugin") +} +plugins { id("com.facebook.react.settings") } +extensions.configure(com.facebook.react.ReactSettingsExtension) { ex -> + ex.autolinkLibrariesFromCommand( + ["pnpm", "exec", "react-native", "config"], + file(".."), + files("../package.json", "../react-native.config.js", "../../package.json", "../../pnpm-lock.yaml") + ) +} + +rootProject.name = 'ReactNative' + +include ':app' +include ':react-native-config' + +includeBuild('../../node_modules/@react-native/gradle-plugin') + +def resolveNodeModuleDir(String packageName) { + def workspaceRoot = new File(rootProject.projectDir, "../..").canonicalFile + def process = [ + "node", + "--print", + "require.resolve('${packageName}/package.json', { paths: [process.argv[1], process.argv[2]] })", + workspaceRoot.getAbsolutePath(), + rootProject.projectDir.getAbsolutePath() + ].execute(null, rootProject.projectDir) + def output = new StringBuffer() + def error = new StringBuffer() + process.consumeProcessOutput(output, error) + if (process.waitFor() != 0) { + throw new GradleException("Failed to resolve ${packageName}: ${error}") + } + return new File(output.toString().trim()).parentFile +} + +project(':react-native-config').projectDir = new File(resolveNodeModuleDir('react-native-config'), 'android') diff --git a/react-native/sample/app.json b/react-native/sample/app.json new file mode 100644 index 00000000..8782677f --- /dev/null +++ b/react-native/sample/app.json @@ -0,0 +1,4 @@ +{ + "name": "ReactNative", + "displayName": "Shopify Checkout" +} diff --git a/react-native/sample/babel.config.js b/react-native/sample/babel.config.js new file mode 100644 index 00000000..f7b3da3b --- /dev/null +++ b/react-native/sample/babel.config.js @@ -0,0 +1,3 @@ +module.exports = { + presets: ['module:@react-native/babel-preset'], +}; diff --git a/react-native/sample/index.js b/react-native/sample/index.js new file mode 100644 index 00000000..ea6ad53d --- /dev/null +++ b/react-native/sample/index.js @@ -0,0 +1,42 @@ +/* +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import {install} from 'react-native-quick-crypto'; +install(); + +import 'setimmediate'; +import 'react-native-gesture-handler'; + +import SampleApp from './src/App'; +import {name} from './app.json'; +import {AppRegistry, LogBox} from 'react-native'; + +/** + * Suppress the RCTImageView topError warning + * This is a known React Native issue that doesn't affect functionality + */ +LogBox.ignoreLogs([ + "Component 'RCTImageView' re-registered bubbling event 'topError' as a direct event", +]); + +AppRegistry.registerComponent(name, () => SampleApp); diff --git a/react-native/sample/ios/.xcode.env b/react-native/sample/ios/.xcode.env new file mode 100644 index 00000000..3d5782c7 --- /dev/null +++ b/react-native/sample/ios/.xcode.env @@ -0,0 +1,11 @@ +# This `.xcode.env` file is versioned and is used to source the environment +# used when running script phases inside Xcode. +# To customize your local environment, you can create an `.xcode.env.local` +# file that is not versioned. + +# NODE_BINARY variable contains the PATH to the node executable. +# +# Customize the NODE_BINARY variable here. +# For example, to use nvm with brew, add the following line +# . "$(brew --prefix nvm)/nvm.sh" --no-use +export NODE_BINARY=$(command -v node) diff --git a/react-native/sample/ios/AppDelegate.swift b/react-native/sample/ios/AppDelegate.swift new file mode 100644 index 00000000..331d8544 --- /dev/null +++ b/react-native/sample/ios/AppDelegate.swift @@ -0,0 +1,48 @@ +import UIKit +import React +import React_RCTAppDelegate +import ReactAppDependencyProvider + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + var window: UIWindow? + + var reactNativeDelegate: ReactNativeDelegate? + var reactNativeFactory: RCTReactNativeFactory? + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil + ) -> Bool { + let delegate = ReactNativeDelegate() + let factory = RCTReactNativeFactory(delegate: delegate) + delegate.dependencyProvider = RCTAppDependencyProvider() + + reactNativeDelegate = delegate + reactNativeFactory = factory + + window = UIWindow(frame: UIScreen.main.bounds) + + factory.startReactNative( + withModuleName: "ReactNative", + in: window, + launchOptions: launchOptions + ) + + return true + } +} + +class ReactNativeDelegate: RCTDefaultReactNativeFactoryDelegate { + override func sourceURL(for bridge: RCTBridge) -> URL? { + self.bundleURL() + } + + override func bundleURL() -> URL? { +#if DEBUG + RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index") +#else + Bundle.main.url(forResource: "main", withExtension: "jsbundle") +#endif + } +} diff --git a/react-native/sample/ios/Podfile b/react-native/sample/ios/Podfile new file mode 100644 index 00000000..3911a0bf --- /dev/null +++ b/react-native/sample/ios/Podfile @@ -0,0 +1,46 @@ +# Resolve react_native_pods.rb with node to allow for hoisting +require Pod::Executable.execute_command('node', ['-p', + 'require.resolve( + "react-native/scripts/react_native_pods.rb", + {paths: [process.argv[1]]}, + )', __dir__]).strip + +platform :ios, '16.6' + +prepare_react_native_project! + +# Suppress warnings in dependencies when building the sample app +inhibit_all_warnings! + +linkage = ENV['USE_FRAMEWORKS'] +if linkage != nil + Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green + use_frameworks! :linkage => linkage.to_sym +end + +target 'ReactNative' do + config = use_native_modules! + + pod "RNShopifyCheckoutSheetKit", :path => "../../modules/@shopify/checkout-sheet-kit" + + use_react_native!( + :path => config[:reactNativePath], + # An absolute path to your application root. + :app_path => "#{Pod::Config.instance.installation_root}/..", + :new_arch_enabled => true + ) + + target 'ReactNativeTests' do + inherit! :search_paths + end + + post_install do |installer| + # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202 + react_native_post_install( + installer, + config[:reactNativePath], + :mac_catalyst_enabled => false, + :ccache_enabled => false + ) + end +end diff --git a/react-native/sample/ios/Podfile.lock b/react-native/sample/ios/Podfile.lock new file mode 100644 index 00000000..742b336c --- /dev/null +++ b/react-native/sample/ios/Podfile.lock @@ -0,0 +1,3007 @@ +PODS: + - boost (1.84.0) + - DoubleConversion (1.1.6) + - fast_float (8.0.0) + - FBLazyVector (0.80.2) + - fmt (11.0.2) + - glog (0.3.5) + - hermes-engine (0.80.2): + - hermes-engine/Pre-built (= 0.80.2) + - hermes-engine/Pre-built (0.80.2) + - NitroModules (0.33.9): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-callinvoker + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - QuickCrypto (1.0.9): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - NitroModules + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-callinvoker + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - RCT-Folly (2024.11.18.00): + - boost + - DoubleConversion + - fast_float (= 8.0.0) + - fmt (= 11.0.2) + - glog + - RCT-Folly/Default (= 2024.11.18.00) + - RCT-Folly/Default (2024.11.18.00): + - boost + - DoubleConversion + - fast_float (= 8.0.0) + - fmt (= 11.0.2) + - glog + - RCT-Folly/Fabric (2024.11.18.00): + - boost + - DoubleConversion + - fast_float (= 8.0.0) + - fmt (= 11.0.2) + - glog + - RCTDeprecation (0.80.2) + - RCTRequired (0.80.2) + - RCTTypeSafety (0.80.2): + - FBLazyVector (= 0.80.2) + - RCTRequired (= 0.80.2) + - React-Core (= 0.80.2) + - React (0.80.2): + - React-Core (= 0.80.2) + - React-Core/DevSupport (= 0.80.2) + - React-Core/RCTWebSocket (= 0.80.2) + - React-RCTActionSheet (= 0.80.2) + - React-RCTAnimation (= 0.80.2) + - React-RCTBlob (= 0.80.2) + - React-RCTImage (= 0.80.2) + - React-RCTLinking (= 0.80.2) + - React-RCTNetwork (= 0.80.2) + - React-RCTSettings (= 0.80.2) + - React-RCTText (= 0.80.2) + - React-RCTVibration (= 0.80.2) + - React-callinvoker (0.80.2) + - React-Core (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTDeprecation + - React-Core/Default (= 0.80.2) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket + - Yoga + - React-Core/CoreModulesHeaders (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket + - Yoga + - React-Core/Default (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTDeprecation + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket + - Yoga + - React-Core/DevSupport (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTDeprecation + - React-Core/Default (= 0.80.2) + - React-Core/RCTWebSocket (= 0.80.2) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket + - Yoga + - React-Core/RCTActionSheetHeaders (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket + - Yoga + - React-Core/RCTAnimationHeaders (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket + - Yoga + - React-Core/RCTBlobHeaders (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket + - Yoga + - React-Core/RCTImageHeaders (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket + - Yoga + - React-Core/RCTLinkingHeaders (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket + - Yoga + - React-Core/RCTNetworkHeaders (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket + - Yoga + - React-Core/RCTSettingsHeaders (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket + - Yoga + - React-Core/RCTTextHeaders (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket + - Yoga + - React-Core/RCTVibrationHeaders (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTDeprecation + - React-Core/Default + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket + - Yoga + - React-Core/RCTWebSocket (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTDeprecation + - React-Core/Default (= 0.80.2) + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsinspectorcdp + - React-jsitooling + - React-perflogger + - React-runtimescheduler + - React-utils + - SocketRocket + - Yoga + - React-CoreModules (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - RCT-Folly + - RCT-Folly/Fabric + - RCTTypeSafety (= 0.80.2) + - React-Core/CoreModulesHeaders (= 0.80.2) + - React-jsi (= 0.80.2) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-NativeModulesApple + - React-RCTBlob + - React-RCTFBReactNativeSpec + - React-RCTImage (= 0.80.2) + - ReactCommon + - SocketRocket + - React-cxxreact (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-callinvoker (= 0.80.2) + - React-debug (= 0.80.2) + - React-jsi (= 0.80.2) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-logger (= 0.80.2) + - React-perflogger (= 0.80.2) + - React-runtimeexecutor (= 0.80.2) + - React-timing (= 0.80.2) + - SocketRocket + - React-debug (0.80.2) + - React-defaultsnativemodule (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-domnativemodule + - React-featureflagsnativemodule + - React-hermes + - React-idlecallbacksnativemodule + - React-jsi + - React-jsiexecutor + - React-microtasksnativemodule + - React-RCTFBReactNativeSpec + - SocketRocket + - React-domnativemodule (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-Fabric + - React-FabricComponents + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - React-Fabric (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric/animations (= 0.80.2) + - React-Fabric/attributedstring (= 0.80.2) + - React-Fabric/componentregistry (= 0.80.2) + - React-Fabric/componentregistrynative (= 0.80.2) + - React-Fabric/components (= 0.80.2) + - React-Fabric/consistency (= 0.80.2) + - React-Fabric/core (= 0.80.2) + - React-Fabric/dom (= 0.80.2) + - React-Fabric/imagemanager (= 0.80.2) + - React-Fabric/leakchecker (= 0.80.2) + - React-Fabric/mounting (= 0.80.2) + - React-Fabric/observers (= 0.80.2) + - React-Fabric/scheduler (= 0.80.2) + - React-Fabric/telemetry (= 0.80.2) + - React-Fabric/templateprocessor (= 0.80.2) + - React-Fabric/uimanager (= 0.80.2) + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/animations (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/attributedstring (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/componentregistry (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/componentregistrynative (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/components (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric/components/legacyviewmanagerinterop (= 0.80.2) + - React-Fabric/components/root (= 0.80.2) + - React-Fabric/components/scrollview (= 0.80.2) + - React-Fabric/components/view (= 0.80.2) + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/components/legacyviewmanagerinterop (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/components/root (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/components/scrollview (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/components/view (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-renderercss + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - React-Fabric/consistency (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/core (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/dom (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/imagemanager (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/leakchecker (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/mounting (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/observers (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric/observers/events (= 0.80.2) + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/observers/events (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/scheduler (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric/observers/events + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-performancetimeline + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/telemetry (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/templateprocessor (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/uimanager (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric/uimanager/consistency (= 0.80.2) + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererconsistency + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-Fabric/uimanager/consistency (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererconsistency + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - React-FabricComponents (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-FabricComponents/components (= 0.80.2) + - React-FabricComponents/textlayoutmanager (= 0.80.2) + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - React-FabricComponents/components (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-FabricComponents/components/inputaccessory (= 0.80.2) + - React-FabricComponents/components/iostextinput (= 0.80.2) + - React-FabricComponents/components/modal (= 0.80.2) + - React-FabricComponents/components/rncore (= 0.80.2) + - React-FabricComponents/components/safeareaview (= 0.80.2) + - React-FabricComponents/components/scrollview (= 0.80.2) + - React-FabricComponents/components/text (= 0.80.2) + - React-FabricComponents/components/textinput (= 0.80.2) + - React-FabricComponents/components/unimplementedview (= 0.80.2) + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - React-FabricComponents/components/inputaccessory (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - React-FabricComponents/components/iostextinput (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - React-FabricComponents/components/modal (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - React-FabricComponents/components/rncore (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - React-FabricComponents/components/safeareaview (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - React-FabricComponents/components/scrollview (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - React-FabricComponents/components/text (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - React-FabricComponents/components/textinput (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - React-FabricComponents/components/unimplementedview (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - React-FabricComponents/textlayoutmanager (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-cxxreact + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-logger + - React-rendererdebug + - React-runtimescheduler + - React-utils + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - React-FabricImage (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired (= 0.80.2) + - RCTTypeSafety (= 0.80.2) + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-jsiexecutor (= 0.80.2) + - React-logger + - React-rendererdebug + - React-utils + - ReactCommon + - SocketRocket + - Yoga + - React-featureflags (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - RCT-Folly + - RCT-Folly/Fabric + - SocketRocket + - React-featureflagsnativemodule (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-featureflags + - React-hermes + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - ReactCommon/turbomodule/core + - SocketRocket + - React-graphics (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-hermes + - React-jsi + - React-jsiexecutor + - React-utils + - SocketRocket + - React-hermes (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-cxxreact (= 0.80.2) + - React-jsi + - React-jsiexecutor (= 0.80.2) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-perflogger (= 0.80.2) + - React-runtimeexecutor + - SocketRocket + - React-idlecallbacksnativemodule (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-hermes + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - React-runtimescheduler + - ReactCommon/turbomodule/core + - SocketRocket + - React-ImageManager (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - RCT-Folly + - RCT-Folly/Fabric + - React-Core/Default + - React-debug + - React-Fabric + - React-graphics + - React-rendererdebug + - React-utils + - SocketRocket + - React-jserrorhandler (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-cxxreact + - React-debug + - React-featureflags + - React-jsi + - ReactCommon/turbomodule/bridging + - SocketRocket + - React-jsi (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - SocketRocket + - React-jsiexecutor (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-cxxreact (= 0.80.2) + - React-jsi (= 0.80.2) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-perflogger (= 0.80.2) + - SocketRocket + - React-jsinspector (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-featureflags + - React-jsi + - React-jsinspectorcdp + - React-jsinspectornetwork + - React-jsinspectortracing + - React-perflogger (= 0.80.2) + - React-runtimeexecutor (= 0.80.2) + - SocketRocket + - React-jsinspectorcdp (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - RCT-Folly + - RCT-Folly/Fabric + - SocketRocket + - React-jsinspectornetwork (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - RCT-Folly + - RCT-Folly/Fabric + - React-jsinspectorcdp + - SocketRocket + - React-jsinspectortracing (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - RCT-Folly + - RCT-Folly/Fabric + - React-oscompat + - SocketRocket + - React-jsitooling (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - RCT-Folly + - RCT-Folly/Fabric + - React-cxxreact (= 0.80.2) + - React-jsi (= 0.80.2) + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - SocketRocket + - React-jsitracing (0.80.2): + - React-jsi + - React-logger (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - RCT-Folly + - RCT-Folly/Fabric + - SocketRocket + - React-Mapbuffer (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - RCT-Folly + - RCT-Folly/Fabric + - React-debug + - SocketRocket + - React-microtasksnativemodule (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-hermes + - React-jsi + - React-jsiexecutor + - React-RCTFBReactNativeSpec + - ReactCommon/turbomodule/core + - SocketRocket + - react-native-config (1.5.6): + - react-native-config/App (= 1.5.6) + - react-native-config/App (1.5.6): + - React-Core + - react-native-encrypted-storage (4.0.3): + - React-Core + - react-native-quick-base64 (2.2.2): + - React-Core + - react-native-safe-area-context (5.7.0): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - react-native-safe-area-context/common (= 5.7.0) + - react-native-safe-area-context/fabric (= 5.7.0) + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - react-native-safe-area-context/common (5.7.0): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - react-native-safe-area-context/fabric (5.7.0): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - react-native-safe-area-context/common + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - react-native-webview (13.16.1): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - React-NativeModulesApple (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-callinvoker + - React-Core + - React-cxxreact + - React-featureflags + - React-hermes + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-runtimeexecutor + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - SocketRocket + - React-oscompat (0.80.2) + - React-perflogger (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - RCT-Folly + - RCT-Folly/Fabric + - SocketRocket + - React-performancetimeline (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - RCT-Folly + - RCT-Folly/Fabric + - React-featureflags + - React-jsinspectortracing + - React-perflogger + - React-timing + - SocketRocket + - React-RCTActionSheet (0.80.2): + - React-Core/RCTActionSheetHeaders (= 0.80.2) + - React-RCTAnimation (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - RCT-Folly + - RCT-Folly/Fabric + - RCTTypeSafety + - React-Core/RCTAnimationHeaders + - React-featureflags + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - SocketRocket + - React-RCTAppDelegate (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-CoreModules + - React-debug + - React-defaultsnativemodule + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-jsitooling + - React-NativeModulesApple + - React-RCTFabric + - React-RCTFBReactNativeSpec + - React-RCTImage + - React-RCTNetwork + - React-RCTRuntime + - React-rendererdebug + - React-RuntimeApple + - React-RuntimeCore + - React-runtimescheduler + - React-utils + - ReactCommon + - SocketRocket + - React-RCTBlob (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-Core/RCTBlobHeaders + - React-Core/RCTWebSocket + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - React-RCTNetwork + - ReactCommon + - SocketRocket + - React-RCTFabric (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-Core + - React-debug + - React-Fabric + - React-FabricComponents + - React-FabricImage + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectornetwork + - React-jsinspectortracing + - React-performancetimeline + - React-RCTAnimation + - React-RCTImage + - React-RCTText + - React-rendererconsistency + - React-renderercss + - React-rendererdebug + - React-runtimescheduler + - React-utils + - SocketRocket + - Yoga + - React-RCTFBReactNativeSpec (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-hermes + - React-jsi + - React-jsiexecutor + - React-NativeModulesApple + - ReactCommon + - SocketRocket + - React-RCTImage (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - RCT-Folly + - RCT-Folly/Fabric + - RCTTypeSafety + - React-Core/RCTImageHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - React-RCTNetwork + - ReactCommon + - SocketRocket + - React-RCTLinking (0.80.2): + - React-Core/RCTLinkingHeaders (= 0.80.2) + - React-jsi (= 0.80.2) + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - ReactCommon/turbomodule/core (= 0.80.2) + - React-RCTNetwork (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - RCT-Folly + - RCT-Folly/Fabric + - RCTTypeSafety + - React-Core/RCTNetworkHeaders + - React-featureflags + - React-jsi + - React-jsinspectorcdp + - React-jsinspectornetwork + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - SocketRocket + - React-RCTRuntime (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-Core + - React-hermes + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-jsitooling + - React-RuntimeApple + - React-RuntimeCore + - React-RuntimeHermes + - SocketRocket + - React-RCTSettings (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - RCT-Folly + - RCT-Folly/Fabric + - RCTTypeSafety + - React-Core/RCTSettingsHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - SocketRocket + - React-RCTText (0.80.2): + - React-Core/RCTTextHeaders (= 0.80.2) + - Yoga + - React-RCTVibration (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - RCT-Folly + - RCT-Folly/Fabric + - React-Core/RCTVibrationHeaders + - React-jsi + - React-NativeModulesApple + - React-RCTFBReactNativeSpec + - ReactCommon + - SocketRocket + - React-rendererconsistency (0.80.2) + - React-renderercss (0.80.2): + - React-debug + - React-utils + - React-rendererdebug (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - RCT-Folly + - RCT-Folly/Fabric + - React-debug + - SocketRocket + - React-rncore (0.80.2) + - React-RuntimeApple (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-callinvoker + - React-Core/Default + - React-CoreModules + - React-cxxreact + - React-featureflags + - React-jserrorhandler + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsitooling + - React-Mapbuffer + - React-NativeModulesApple + - React-RCTFabric + - React-RCTFBReactNativeSpec + - React-RuntimeCore + - React-runtimeexecutor + - React-RuntimeHermes + - React-runtimescheduler + - React-utils + - SocketRocket + - React-RuntimeCore (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-cxxreact + - React-Fabric + - React-featureflags + - React-hermes + - React-jserrorhandler + - React-jsi + - React-jsiexecutor + - React-jsinspector + - React-jsitooling + - React-performancetimeline + - React-runtimeexecutor + - React-runtimescheduler + - React-utils + - SocketRocket + - React-runtimeexecutor (0.80.2): + - React-jsi (= 0.80.2) + - React-RuntimeHermes (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-featureflags + - React-hermes + - React-jsi + - React-jsinspector + - React-jsinspectorcdp + - React-jsinspectortracing + - React-jsitooling + - React-jsitracing + - React-RuntimeCore + - React-utils + - SocketRocket + - React-runtimescheduler (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-callinvoker + - React-cxxreact + - React-debug + - React-featureflags + - React-hermes + - React-jsi + - React-jsinspectortracing + - React-performancetimeline + - React-rendererconsistency + - React-rendererdebug + - React-runtimeexecutor + - React-timing + - React-utils + - SocketRocket + - React-timing (0.80.2) + - React-utils (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-debug + - React-hermes + - React-jsi (= 0.80.2) + - SocketRocket + - ReactAppDependencyProvider (0.80.2): + - ReactCodegen + - ReactCodegen (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-FabricImage + - React-featureflags + - React-graphics + - React-hermes + - React-jsi + - React-jsiexecutor + - React-NativeModulesApple + - React-RCTAppDelegate + - React-rendererdebug + - React-utils + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - SocketRocket + - ReactCommon (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - RCT-Folly + - RCT-Folly/Fabric + - ReactCommon/turbomodule (= 0.80.2) + - SocketRocket + - ReactCommon/turbomodule (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-callinvoker (= 0.80.2) + - React-cxxreact (= 0.80.2) + - React-jsi (= 0.80.2) + - React-logger (= 0.80.2) + - React-perflogger (= 0.80.2) + - ReactCommon/turbomodule/bridging (= 0.80.2) + - ReactCommon/turbomodule/core (= 0.80.2) + - SocketRocket + - ReactCommon/turbomodule/bridging (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-callinvoker (= 0.80.2) + - React-cxxreact (= 0.80.2) + - React-jsi (= 0.80.2) + - React-logger (= 0.80.2) + - React-perflogger (= 0.80.2) + - SocketRocket + - ReactCommon/turbomodule/core (0.80.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - React-callinvoker (= 0.80.2) + - React-cxxreact (= 0.80.2) + - React-debug (= 0.80.2) + - React-featureflags (= 0.80.2) + - React-jsi (= 0.80.2) + - React-logger (= 0.80.2) + - React-perflogger (= 0.80.2) + - React-utils (= 0.80.2) + - SocketRocket + - RNCMaskedView (0.3.2): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - RNGestureHandler (2.26.0): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - RNReanimated (3.18.0): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - RNReanimated/reanimated (= 3.18.0) + - RNReanimated/worklets (= 3.18.0) + - SocketRocket + - Yoga + - RNReanimated/reanimated (3.18.0): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - RNReanimated/reanimated/apple (= 3.18.0) + - SocketRocket + - Yoga + - RNReanimated/reanimated/apple (3.18.0): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - RNReanimated/worklets (3.18.0): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - RNReanimated/worklets/apple (= 3.18.0) + - SocketRocket + - Yoga + - RNReanimated/worklets/apple (3.18.0): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - RNScreens (4.16.0): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-RCTImage + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - RNScreens/common (= 4.16.0) + - SocketRocket + - Yoga + - RNScreens/common (4.16.0): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-RCTImage + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - RNShopifyCheckoutSheetKit (3.8.0): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - ShopifyCheckoutSheetKit (~> 3.8.0) + - ShopifyCheckoutSheetKit/AcceleratedCheckouts (~> 3.8.0) + - SocketRocket + - Yoga + - RNVectorIcons (10.3.0): + - boost + - DoubleConversion + - fast_float + - fmt + - glog + - hermes-engine + - RCT-Folly + - RCT-Folly/Fabric + - RCTRequired + - RCTTypeSafety + - React-Core + - React-debug + - React-Fabric + - React-featureflags + - React-graphics + - React-hermes + - React-ImageManager + - React-jsi + - React-NativeModulesApple + - React-RCTFabric + - React-renderercss + - React-rendererdebug + - React-utils + - ReactCodegen + - ReactCommon/turbomodule/bridging + - ReactCommon/turbomodule/core + - SocketRocket + - Yoga + - ShopifyCheckoutSheetKit (3.8.0): + - ShopifyCheckoutSheetKit/Core (= 3.8.0) + - ShopifyCheckoutSheetKit/AcceleratedCheckouts (3.8.0): + - ShopifyCheckoutSheetKit/Core + - ShopifyCheckoutSheetKit/Core (3.8.0) + - SocketRocket (0.7.1) + - Yoga (0.0.0) + +DEPENDENCIES: + - boost (from `../../node_modules/react-native/third-party-podspecs/boost.podspec`) + - DoubleConversion (from `../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) + - fast_float (from `../../node_modules/react-native/third-party-podspecs/fast_float.podspec`) + - FBLazyVector (from `../../node_modules/react-native/Libraries/FBLazyVector`) + - fmt (from `../../node_modules/react-native/third-party-podspecs/fmt.podspec`) + - glog (from `../../node_modules/react-native/third-party-podspecs/glog.podspec`) + - hermes-engine (from `../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec`) + - NitroModules (from `../../node_modules/react-native-nitro-modules`) + - QuickCrypto (from `../../node_modules/react-native-quick-crypto`) + - RCT-Folly (from `../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) + - RCTDeprecation (from `../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation`) + - RCTRequired (from `../../node_modules/react-native/Libraries/Required`) + - RCTTypeSafety (from `../../node_modules/react-native/Libraries/TypeSafety`) + - React (from `../../node_modules/react-native/`) + - React-callinvoker (from `../../node_modules/react-native/ReactCommon/callinvoker`) + - React-Core (from `../../node_modules/react-native/`) + - React-Core/RCTWebSocket (from `../../node_modules/react-native/`) + - React-CoreModules (from `../../node_modules/react-native/React/CoreModules`) + - React-cxxreact (from `../../node_modules/react-native/ReactCommon/cxxreact`) + - React-debug (from `../../node_modules/react-native/ReactCommon/react/debug`) + - React-defaultsnativemodule (from `../../node_modules/react-native/ReactCommon/react/nativemodule/defaults`) + - React-domnativemodule (from `../../node_modules/react-native/ReactCommon/react/nativemodule/dom`) + - React-Fabric (from `../../node_modules/react-native/ReactCommon`) + - React-FabricComponents (from `../../node_modules/react-native/ReactCommon`) + - React-FabricImage (from `../../node_modules/react-native/ReactCommon`) + - React-featureflags (from `../../node_modules/react-native/ReactCommon/react/featureflags`) + - React-featureflagsnativemodule (from `../../node_modules/react-native/ReactCommon/react/nativemodule/featureflags`) + - React-graphics (from `../../node_modules/react-native/ReactCommon/react/renderer/graphics`) + - React-hermes (from `../../node_modules/react-native/ReactCommon/hermes`) + - React-idlecallbacksnativemodule (from `../../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks`) + - React-ImageManager (from `../../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios`) + - React-jserrorhandler (from `../../node_modules/react-native/ReactCommon/jserrorhandler`) + - React-jsi (from `../../node_modules/react-native/ReactCommon/jsi`) + - React-jsiexecutor (from `../../node_modules/react-native/ReactCommon/jsiexecutor`) + - React-jsinspector (from `../../node_modules/react-native/ReactCommon/jsinspector-modern`) + - React-jsinspectorcdp (from `../../node_modules/react-native/ReactCommon/jsinspector-modern/cdp`) + - React-jsinspectornetwork (from `../../node_modules/react-native/ReactCommon/jsinspector-modern/network`) + - React-jsinspectortracing (from `../../node_modules/react-native/ReactCommon/jsinspector-modern/tracing`) + - React-jsitooling (from `../../node_modules/react-native/ReactCommon/jsitooling`) + - React-jsitracing (from `../../node_modules/react-native/ReactCommon/hermes/executor/`) + - React-logger (from `../../node_modules/react-native/ReactCommon/logger`) + - React-Mapbuffer (from `../../node_modules/react-native/ReactCommon`) + - React-microtasksnativemodule (from `../../node_modules/react-native/ReactCommon/react/nativemodule/microtasks`) + - react-native-config (from `../../node_modules/react-native-config`) + - react-native-encrypted-storage (from `../../node_modules/react-native-encrypted-storage`) + - react-native-quick-base64 (from `../../node_modules/react-native-quick-base64`) + - react-native-safe-area-context (from `../../node_modules/react-native-safe-area-context`) + - react-native-webview (from `../../node_modules/react-native-webview`) + - React-NativeModulesApple (from `../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios`) + - React-oscompat (from `../../node_modules/react-native/ReactCommon/oscompat`) + - React-perflogger (from `../../node_modules/react-native/ReactCommon/reactperflogger`) + - React-performancetimeline (from `../../node_modules/react-native/ReactCommon/react/performance/timeline`) + - React-RCTActionSheet (from `../../node_modules/react-native/Libraries/ActionSheetIOS`) + - React-RCTAnimation (from `../../node_modules/react-native/Libraries/NativeAnimation`) + - React-RCTAppDelegate (from `../../node_modules/react-native/Libraries/AppDelegate`) + - React-RCTBlob (from `../../node_modules/react-native/Libraries/Blob`) + - React-RCTFabric (from `../../node_modules/react-native/React`) + - React-RCTFBReactNativeSpec (from `../../node_modules/react-native/React`) + - React-RCTImage (from `../../node_modules/react-native/Libraries/Image`) + - React-RCTLinking (from `../../node_modules/react-native/Libraries/LinkingIOS`) + - React-RCTNetwork (from `../../node_modules/react-native/Libraries/Network`) + - React-RCTRuntime (from `../../node_modules/react-native/React/Runtime`) + - React-RCTSettings (from `../../node_modules/react-native/Libraries/Settings`) + - React-RCTText (from `../../node_modules/react-native/Libraries/Text`) + - React-RCTVibration (from `../../node_modules/react-native/Libraries/Vibration`) + - React-rendererconsistency (from `../../node_modules/react-native/ReactCommon/react/renderer/consistency`) + - React-renderercss (from `../../node_modules/react-native/ReactCommon/react/renderer/css`) + - React-rendererdebug (from `../../node_modules/react-native/ReactCommon/react/renderer/debug`) + - React-rncore (from `../../node_modules/react-native/ReactCommon`) + - React-RuntimeApple (from `../../node_modules/react-native/ReactCommon/react/runtime/platform/ios`) + - React-RuntimeCore (from `../../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimeexecutor (from `../../node_modules/react-native/ReactCommon/runtimeexecutor`) + - React-RuntimeHermes (from `../../node_modules/react-native/ReactCommon/react/runtime`) + - React-runtimescheduler (from `../../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) + - React-timing (from `../../node_modules/react-native/ReactCommon/react/timing`) + - React-utils (from `../../node_modules/react-native/ReactCommon/react/utils`) + - ReactAppDependencyProvider (from `build/generated/ios`) + - ReactCodegen (from `build/generated/ios`) + - ReactCommon/turbomodule/core (from `../../node_modules/react-native/ReactCommon`) + - "RNCMaskedView (from `../../node_modules/@react-native-masked-view/masked-view`)" + - RNGestureHandler (from `../../node_modules/react-native-gesture-handler`) + - RNReanimated (from `../../node_modules/react-native-reanimated`) + - RNScreens (from `../../node_modules/react-native-screens`) + - "RNShopifyCheckoutSheetKit (from `../../modules/@shopify/checkout-sheet-kit`)" + - RNVectorIcons (from `../../node_modules/react-native-vector-icons`) + - SocketRocket (~> 0.7.1) + - Yoga (from `../../node_modules/react-native/ReactCommon/yoga`) + +SPEC REPOS: + trunk: + - ShopifyCheckoutSheetKit + - SocketRocket + +EXTERNAL SOURCES: + boost: + :podspec: "../../node_modules/react-native/third-party-podspecs/boost.podspec" + DoubleConversion: + :podspec: "../../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" + fast_float: + :podspec: "../../node_modules/react-native/third-party-podspecs/fast_float.podspec" + FBLazyVector: + :path: "../../node_modules/react-native/Libraries/FBLazyVector" + fmt: + :podspec: "../../node_modules/react-native/third-party-podspecs/fmt.podspec" + glog: + :podspec: "../../node_modules/react-native/third-party-podspecs/glog.podspec" + hermes-engine: + :podspec: "../../node_modules/react-native/sdks/hermes-engine/hermes-engine.podspec" + :tag: hermes-2025-07-24-RNv0.80.2-5c7dbc0a78cb2d2a8bc81c41c617c3abecf209ff + NitroModules: + :path: "../../node_modules/react-native-nitro-modules" + QuickCrypto: + :path: "../../node_modules/react-native-quick-crypto" + RCT-Folly: + :podspec: "../../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" + RCTDeprecation: + :path: "../../node_modules/react-native/ReactApple/Libraries/RCTFoundation/RCTDeprecation" + RCTRequired: + :path: "../../node_modules/react-native/Libraries/Required" + RCTTypeSafety: + :path: "../../node_modules/react-native/Libraries/TypeSafety" + React: + :path: "../../node_modules/react-native/" + React-callinvoker: + :path: "../../node_modules/react-native/ReactCommon/callinvoker" + React-Core: + :path: "../../node_modules/react-native/" + React-CoreModules: + :path: "../../node_modules/react-native/React/CoreModules" + React-cxxreact: + :path: "../../node_modules/react-native/ReactCommon/cxxreact" + React-debug: + :path: "../../node_modules/react-native/ReactCommon/react/debug" + React-defaultsnativemodule: + :path: "../../node_modules/react-native/ReactCommon/react/nativemodule/defaults" + React-domnativemodule: + :path: "../../node_modules/react-native/ReactCommon/react/nativemodule/dom" + React-Fabric: + :path: "../../node_modules/react-native/ReactCommon" + React-FabricComponents: + :path: "../../node_modules/react-native/ReactCommon" + React-FabricImage: + :path: "../../node_modules/react-native/ReactCommon" + React-featureflags: + :path: "../../node_modules/react-native/ReactCommon/react/featureflags" + React-featureflagsnativemodule: + :path: "../../node_modules/react-native/ReactCommon/react/nativemodule/featureflags" + React-graphics: + :path: "../../node_modules/react-native/ReactCommon/react/renderer/graphics" + React-hermes: + :path: "../../node_modules/react-native/ReactCommon/hermes" + React-idlecallbacksnativemodule: + :path: "../../node_modules/react-native/ReactCommon/react/nativemodule/idlecallbacks" + React-ImageManager: + :path: "../../node_modules/react-native/ReactCommon/react/renderer/imagemanager/platform/ios" + React-jserrorhandler: + :path: "../../node_modules/react-native/ReactCommon/jserrorhandler" + React-jsi: + :path: "../../node_modules/react-native/ReactCommon/jsi" + React-jsiexecutor: + :path: "../../node_modules/react-native/ReactCommon/jsiexecutor" + React-jsinspector: + :path: "../../node_modules/react-native/ReactCommon/jsinspector-modern" + React-jsinspectorcdp: + :path: "../../node_modules/react-native/ReactCommon/jsinspector-modern/cdp" + React-jsinspectornetwork: + :path: "../../node_modules/react-native/ReactCommon/jsinspector-modern/network" + React-jsinspectortracing: + :path: "../../node_modules/react-native/ReactCommon/jsinspector-modern/tracing" + React-jsitooling: + :path: "../../node_modules/react-native/ReactCommon/jsitooling" + React-jsitracing: + :path: "../../node_modules/react-native/ReactCommon/hermes/executor/" + React-logger: + :path: "../../node_modules/react-native/ReactCommon/logger" + React-Mapbuffer: + :path: "../../node_modules/react-native/ReactCommon" + React-microtasksnativemodule: + :path: "../../node_modules/react-native/ReactCommon/react/nativemodule/microtasks" + react-native-config: + :path: "../../node_modules/react-native-config" + react-native-encrypted-storage: + :path: "../../node_modules/react-native-encrypted-storage" + react-native-quick-base64: + :path: "../../node_modules/react-native-quick-base64" + react-native-safe-area-context: + :path: "../../node_modules/react-native-safe-area-context" + react-native-webview: + :path: "../../node_modules/react-native-webview" + React-NativeModulesApple: + :path: "../../node_modules/react-native/ReactCommon/react/nativemodule/core/platform/ios" + React-oscompat: + :path: "../../node_modules/react-native/ReactCommon/oscompat" + React-perflogger: + :path: "../../node_modules/react-native/ReactCommon/reactperflogger" + React-performancetimeline: + :path: "../../node_modules/react-native/ReactCommon/react/performance/timeline" + React-RCTActionSheet: + :path: "../../node_modules/react-native/Libraries/ActionSheetIOS" + React-RCTAnimation: + :path: "../../node_modules/react-native/Libraries/NativeAnimation" + React-RCTAppDelegate: + :path: "../../node_modules/react-native/Libraries/AppDelegate" + React-RCTBlob: + :path: "../../node_modules/react-native/Libraries/Blob" + React-RCTFabric: + :path: "../../node_modules/react-native/React" + React-RCTFBReactNativeSpec: + :path: "../../node_modules/react-native/React" + React-RCTImage: + :path: "../../node_modules/react-native/Libraries/Image" + React-RCTLinking: + :path: "../../node_modules/react-native/Libraries/LinkingIOS" + React-RCTNetwork: + :path: "../../node_modules/react-native/Libraries/Network" + React-RCTRuntime: + :path: "../../node_modules/react-native/React/Runtime" + React-RCTSettings: + :path: "../../node_modules/react-native/Libraries/Settings" + React-RCTText: + :path: "../../node_modules/react-native/Libraries/Text" + React-RCTVibration: + :path: "../../node_modules/react-native/Libraries/Vibration" + React-rendererconsistency: + :path: "../../node_modules/react-native/ReactCommon/react/renderer/consistency" + React-renderercss: + :path: "../../node_modules/react-native/ReactCommon/react/renderer/css" + React-rendererdebug: + :path: "../../node_modules/react-native/ReactCommon/react/renderer/debug" + React-rncore: + :path: "../../node_modules/react-native/ReactCommon" + React-RuntimeApple: + :path: "../../node_modules/react-native/ReactCommon/react/runtime/platform/ios" + React-RuntimeCore: + :path: "../../node_modules/react-native/ReactCommon/react/runtime" + React-runtimeexecutor: + :path: "../../node_modules/react-native/ReactCommon/runtimeexecutor" + React-RuntimeHermes: + :path: "../../node_modules/react-native/ReactCommon/react/runtime" + React-runtimescheduler: + :path: "../../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler" + React-timing: + :path: "../../node_modules/react-native/ReactCommon/react/timing" + React-utils: + :path: "../../node_modules/react-native/ReactCommon/react/utils" + ReactAppDependencyProvider: + :path: build/generated/ios + ReactCodegen: + :path: build/generated/ios + ReactCommon: + :path: "../../node_modules/react-native/ReactCommon" + RNCMaskedView: + :path: "../../node_modules/@react-native-masked-view/masked-view" + RNGestureHandler: + :path: "../../node_modules/react-native-gesture-handler" + RNReanimated: + :path: "../../node_modules/react-native-reanimated" + RNScreens: + :path: "../../node_modules/react-native-screens" + RNShopifyCheckoutSheetKit: + :path: "../../modules/@shopify/checkout-sheet-kit" + RNVectorIcons: + :path: "../../node_modules/react-native-vector-icons" + Yoga: + :path: "../../node_modules/react-native/ReactCommon/yoga" + +SPEC CHECKSUMS: + boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 + DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb + fast_float: b32c788ed9c6a8c584d114d0047beda9664e7cc6 + FBLazyVector: 86588b5a1547e7a417942a08f49559b184e002c8 + fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd + glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 + hermes-engine: bbc1152da7d2d40f9e59c28acc6576fcf5d28e2a + NitroModules: 30c376f96874896e2ebf2ef16c5d84019c06d93b + QuickCrypto: be031b34e1004b427bdaca1be5283a3288bf6ce4 + RCT-Folly: 846fda9475e61ec7bcbf8a3fe81edfcaeb090669 + RCTDeprecation: 300c5eb91114d4339b0bb39505d0f4824d7299b7 + RCTRequired: e0446b01093475b7082fbeee5d1ef4ad1fe20ac4 + RCTTypeSafety: cb974efcdc6695deedf7bf1eb942f2a0603a063f + React: e7a4655b09d0e17e54be188cc34c2f3e2087318a + React-callinvoker: 62192daaa2f30c3321fc531e4f776f7b09cf892b + React-Core: b23cdaaa9d76389d958c06af3c57aa6ad611c542 + React-CoreModules: 8e0f562e5695991e455abbebe1e968af71d52553 + React-cxxreact: 6ccbe0cc2c652b29409b14b23cfb3cd74e084691 + React-debug: 1834225a63b420b16e9b8b01ba5870aee96d0610 + React-defaultsnativemodule: dd88d445d542d58ab61a8a29a7c1d2272dfed577 + React-domnativemodule: fc3c24f4d3bb92770727ea48b4133dab77ded7f7 + React-Fabric: 00fe76339e568da0d0497cc72daeeb01e463871a + React-FabricComponents: 7bb179ee55db68f88c007800b0ac62c930115a85 + React-FabricImage: 21e01118011dd1e4ff3cdab20dbf57839cff52ee + React-featureflags: 6e67f2e252bc8ebb1d538c2ae8c14df432fe5fc0 + React-featureflagsnativemodule: eff5216a5cde5df5d09243d15db1bc401474deef + React-graphics: 8539372da8754118a565251ed08a88fc70f69340 + React-hermes: cc8c77acee1406c258622cd8abbee9049f6b5761 + React-idlecallbacksnativemodule: 7349675d1ccbec876c29b0e206ac08c762baaa36 + React-ImageManager: 4089d8ad52c86a8ae1d7591282fff1665ff5518b + React-jserrorhandler: 89a7a5fa8d04791e729119d1db03bf0ee85a9e29 + React-jsi: ea5c640ea63c127080f158dac7f4f393d13d415c + React-jsiexecutor: cf7920f82e46fe9a484c15c9f31e67d7179aa826 + React-jsinspector: 69e974b6313dbbb635ba503f2f4f2c389b30edbf + React-jsinspectorcdp: 231ddd5b7164c37589dcde3b8b6960136c891d6d + React-jsinspectornetwork: ff74911f79cf0a407a7f0ad0eeb0be64687ed815 + React-jsinspectortracing: df2aa2d944bb3fa280d9c920b9a06664bca8a7e8 + React-jsitooling: 77849c27e374a028ed8106e434a35267f6c6600b + React-jsitracing: 0dc6978e5b38c6e5e01e6aed484e4aec3f5f581b + React-logger: 7cfc7b1ae1f8e5fe5097f9c746137cc3a8fad4ce + React-Mapbuffer: 7018c5b7da5b13ed22fe55dae51d50187a00b2d7 + React-microtasksnativemodule: 8ff9cb220a8efa625b5885996bd69e69db9edf02 + react-native-config: f1dde39f8468ad922fc7e8bd4308c8e6223d5ee8 + react-native-encrypted-storage: 569d114e329b1c2c2d9f8c84bcdbe4478dda2258 + react-native-quick-base64: 6568199bb2ac8e72ecdfdc73a230fbc5c1d3aac4 + react-native-safe-area-context: 1792c367881ef3be64cc01df775c19c6d217a015 + react-native-webview: a5f4076e4e0c1c84d8fe1ffe4ff720df685bbbcd + React-NativeModulesApple: 37c08c3c54db55854de816b0df0f3683832be35a + React-oscompat: 56d6de59f9ae95cd006a1c40be2cde83bc06a4e1 + React-perflogger: 4008bd05a8b6c157b06608c0ea0b8bd5d9c5e6c9 + React-performancetimeline: 9321ba7605abcfb3a2b497fd7cbaf5cfd8c7cf67 + React-RCTActionSheet: 49138012280ec3bbb35193d8d09adb8bc61c982e + React-RCTAnimation: ebfe7c62016d4c17b56b2cab3a221908ae46288d + React-RCTAppDelegate: 0108657ba9a19f6a1cd62dcd19c2c0485b3fc251 + React-RCTBlob: 6cc309d1623f3c2679125a04a7425685b7219e6b + React-RCTFabric: 0a9ff5c9d1e1d7fc026bda6671180cbf56861c15 + React-RCTFBReactNativeSpec: ff3e37e2456afc04211334e86d07bf20488df0ae + React-RCTImage: bb98a59aeed953a48be3f917b9b745b213b340ab + React-RCTLinking: d6e9795d4d75d154c1dd821fd0746cc3e05d6670 + React-RCTNetwork: 5c8a7a2dd26728323189362f149e788548ac72bc + React-RCTRuntime: 96808e8fdce300a26c82d8c24174e33ba5210a7c + React-RCTSettings: b6a02d545ce10dd936b39914b32674db6e865307 + React-RCTText: c7d9232da0e9b5082a99a617483d9164a9cd46e9 + React-RCTVibration: fe636c985c1bf25e4a5b5b4d9315a3b882468a72 + React-rendererconsistency: d20fcb77173861cc7d8356239823e3b36966fc31 + React-renderercss: 56461d1e18db6a325048fdd04a51d68bd7ddb5a8 + React-rendererdebug: fcd44d3eb8a02d74beee778bb142e724016c7375 + React-rncore: bafb76fc01b78757a9592e92dbc227f9260bf0ac + React-RuntimeApple: 01e3ad08793efaa54cf85276457fa4a1f103d5b4 + React-RuntimeCore: 5c4bec5bf402a99b134e55972f2f4e676c70b9ab + React-runtimeexecutor: b35de9cb7f5d19c66ea9b067235f95b947697ba5 + React-RuntimeHermes: ba549a5834a6592d243b9a605530ecd7b6f5e79c + React-runtimescheduler: 9a9914d58caec7976aaae381cd2d997408f2260f + React-timing: 4f97958cc918f0af9444f93e4a7083415e6f5daf + React-utils: f491e2726eb8ced8af13893e1f77317f0fa9a954 + ReactAppDependencyProvider: 8df342c127fd0c1e30e8b9f71ff814c22414a7c0 + ReactCodegen: 37cf3321221b0c4f89b0750dbaf466bc99de7a57 + ReactCommon: 592ef441605638b95e533653259254b4bd35ff4f + RNCMaskedView: 7e0ce15656772a939ff0d269100bca3a182163c8 + RNGestureHandler: eeb622199ef1fb3a076243131095df1c797072f0 + RNReanimated: 237d420b7bb4378ef1dacc7d7a5c674fddb4b5d2 + RNScreens: 3fc29af06302e1f1c18a7829fe57cbc2c0259912 + RNShopifyCheckoutSheetKit: 5587e0fc360607d832f7f10f8436883d1db4b5ef + RNVectorIcons: be4d047a76ad307ffe54732208fb0498fcb8477f + ShopifyCheckoutSheetKit: 5253ca4da4c4f31069286509693930d02b4150d8 + SocketRocket: d4aabe649be1e368d1318fdf28a022d714d65748 + Yoga: a742cc68e8366fcfc681808162492bc0aa7a9498 + +PODFILE CHECKSUM: 81acd7bd19af2e0a9570ba0704d5dee4fb230eca + +COCOAPODS: 1.15.2 diff --git a/react-native/sample/ios/ReactNative-Bridging-Header.h b/react-native/sample/ios/ReactNative-Bridging-Header.h new file mode 100644 index 00000000..24db3c47 --- /dev/null +++ b/react-native/sample/ios/ReactNative-Bridging-Header.h @@ -0,0 +1,24 @@ +/* + MIT License + + Copyright 2023 - Present, Shopify Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import diff --git a/react-native/sample/ios/ReactNative.xcodeproj/project.pbxproj b/react-native/sample/ios/ReactNative.xcodeproj/project.pbxproj new file mode 100644 index 00000000..e7e5f976 --- /dev/null +++ b/react-native/sample/ios/ReactNative.xcodeproj/project.pbxproj @@ -0,0 +1,1009 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 54; + objects = { + +/* Begin PBXBuildFile section */ + 0A018AC7205EBA49E1E67311 /* libPods-ReactNative.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9E0AC43398437CA0E3D05DDF /* libPods-ReactNative.a */; }; + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; }; + 37C6EE1EB87AA54212E67EDE /* libPods-ReactNativeTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 591887D86C91BE07EFFC9303 /* libPods-ReactNativeTests.a */; }; + 6A1F48E72B16900600BA591C /* ShopifyCheckoutSheetKitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A1F48E62B16900600BA591C /* ShopifyCheckoutSheetKitTests.swift */; }; + 6A58AAE82E607E0200A6AA85 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A58AAE72E607DFB00A6AA85 /* AppDelegate.swift */; }; + 6A86196D2BF36EB900E5EE1A /* CheckoutDidFailTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A86196C2BF36EB900E5EE1A /* CheckoutDidFailTests.swift */; }; + 6AEEAAB22C00010100E5EE1B /* AcceleratedCheckouts_SupportedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AEEAAB02C00010100E5EE1B /* AcceleratedCheckouts_SupportedTests.swift */; }; + 6AEEAAB32C00010100E5EE1B /* AcceleratedCheckouts_UnsupportedTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AEEAAB12C00010100E5EE1B /* AcceleratedCheckouts_UnsupportedTests.swift */; }; + 6AF1E0032C00010100E5EE1B /* EventSerializationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AF1E0022C00010100E5EE1B /* EventSerializationTests.swift */; }; + 6AFAD2D22BA9DEF8001F9644 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 6AFAD2D12BA9DEF8001F9644 /* Localizable.xcstrings */; }; + 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; }; + 9403907249B4B6D988902B48 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 1939D329B2C035D1A15E316B /* PrivacyInfo.xcprivacy */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 00E356F41AD99517003FC87E /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 83CBB9F71A601CBA00E9B192 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 13B07F861A680F5B00A75B9A; + remoteInfo = ReactNative; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 00E356EE1AD99517003FC87E /* ReactNativeTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ReactNativeTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07F961A680F5B00A75B9A /* ReactNative.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ReactNative.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = ReactNative/Images.xcassets; sourceTree = ""; }; + 13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ReactNative/Info.plist; sourceTree = ""; }; + 1939D329B2C035D1A15E316B /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = ReactNative/PrivacyInfo.xcprivacy; sourceTree = ""; }; + 39099806D4750FF73C4975E3 /* Pods-ReactNativeTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeTests.release.xcconfig"; path = "Target Support Files/Pods-ReactNativeTests/Pods-ReactNativeTests.release.xcconfig"; sourceTree = ""; }; + 4EF1B8B6CA16C991C5BEE2F2 /* Pods-ReactNative.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNative.release.xcconfig"; path = "Target Support Files/Pods-ReactNative/Pods-ReactNative.release.xcconfig"; sourceTree = ""; }; + 591887D86C91BE07EFFC9303 /* libPods-ReactNativeTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNativeTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 6A1F48E62B16900600BA591C /* ShopifyCheckoutSheetKitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShopifyCheckoutSheetKitTests.swift; sourceTree = ""; }; + 6A58AAE72E607DFB00A6AA85 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 6A86196C2BF36EB900E5EE1A /* CheckoutDidFailTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckoutDidFailTests.swift; sourceTree = ""; }; + 6A8D64CF2AF25C1200FE4E4A /* ReactNative.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = ReactNative.entitlements; path = ReactNative/ReactNative.entitlements; sourceTree = ""; }; + 6AABC1942B17417E008240EB /* OpenSSL.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = OpenSSL.xcframework; path = "Pods/OpenSSL-Universal/Frameworks/OpenSSL.xcframework"; sourceTree = ""; }; + 6AAEC3572B067AD500C7099B /* Entypo.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; name = Entypo.ttf; path = "../node_modules/react-native-vector-icons/Fonts/Entypo.ttf"; sourceTree = ""; }; + 6AEEAAB02C00010100E5EE1B /* AcceleratedCheckouts_SupportedTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcceleratedCheckouts_SupportedTests.swift; sourceTree = ""; }; + 6AEEAAB12C00010100E5EE1B /* AcceleratedCheckouts_UnsupportedTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcceleratedCheckouts_UnsupportedTests.swift; sourceTree = ""; }; + 6AF1E0022C00010100E5EE1B /* EventSerializationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventSerializationTests.swift; sourceTree = ""; }; + 6AF2B3762B0BCA6600C6CE4F /* ReactNative-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "ReactNative-Bridging-Header.h"; sourceTree = ""; }; + 6AFAD2D12BA9DEF8001F9644 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; name = Localizable.xcstrings; path = ReactNative/Localizable.xcstrings; sourceTree = ""; }; + 6AFC2CB62B0D5814003B5A63 /* libShopifyCheckoutSheetKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; path = libShopifyCheckoutSheetKit.a; sourceTree = BUILT_PRODUCTS_DIR; }; + 7959053DABE1E06BE5CF1255 /* Pods-ReactNative-ReactNativeTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNative-ReactNativeTests.release.xcconfig"; path = "Target Support Files/Pods-ReactNative-ReactNativeTests/Pods-ReactNative-ReactNativeTests.release.xcconfig"; sourceTree = ""; }; + 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = ReactNative/LaunchScreen.storyboard; sourceTree = ""; }; + 9E0AC43398437CA0E3D05DDF /* libPods-ReactNative.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReactNative.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + DF9E097D026528607DDCD7B7 /* Pods-ReactNative-ReactNativeTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNative-ReactNativeTests.debug.xcconfig"; path = "Target Support Files/Pods-ReactNative-ReactNativeTests/Pods-ReactNative-ReactNativeTests.debug.xcconfig"; sourceTree = ""; }; + E22D02A7C54F90741A3D90E9 /* Pods-ReactNativeTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNativeTests.debug.xcconfig"; path = "Target Support Files/Pods-ReactNativeTests/Pods-ReactNativeTests.debug.xcconfig"; sourceTree = ""; }; + ED728EF8BB9B5E9BA9D50EBE /* Pods-ReactNative.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReactNative.debug.xcconfig"; path = "Target Support Files/Pods-ReactNative/Pods-ReactNative.debug.xcconfig"; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 00E356EB1AD99517003FC87E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 37C6EE1EB87AA54212E67EDE /* libPods-ReactNativeTests.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F8C1A680F5B00A75B9A /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 0A018AC7205EBA49E1E67311 /* libPods-ReactNative.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 00E356EF1AD99517003FC87E /* ReactNativeTests */ = { + isa = PBXGroup; + children = ( + 00E356F01AD99517003FC87E /* Supporting Files */, + 6A1F48E62B16900600BA591C /* ShopifyCheckoutSheetKitTests.swift */, + 6A86196C2BF36EB900E5EE1A /* CheckoutDidFailTests.swift */, + 6AEEAAB02C00010100E5EE1B /* AcceleratedCheckouts_SupportedTests.swift */, + 6AEEAAB12C00010100E5EE1B /* AcceleratedCheckouts_UnsupportedTests.swift */, + 6AF1E0022C00010100E5EE1B /* EventSerializationTests.swift */, + ); + path = ReactNativeTests; + sourceTree = ""; + }; + 00E356F01AD99517003FC87E /* Supporting Files */ = { + isa = PBXGroup; + children = ( + ); + name = "Supporting Files"; + sourceTree = ""; + }; + 13B07FAE1A68108700A75B9A /* ReactNative */ = { + isa = PBXGroup; + children = ( + 6A58AAE72E607DFB00A6AA85 /* AppDelegate.swift */, + 6AF2B3762B0BCA6600C6CE4F /* ReactNative-Bridging-Header.h */, + 6AAEC3572B067AD500C7099B /* Entypo.ttf */, + 6A8D64CF2AF25C1200FE4E4A /* ReactNative.entitlements */, + 13B07FB51A68108700A75B9A /* Images.xcassets */, + 13B07FB61A68108700A75B9A /* Info.plist */, + 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */, + 6AFAD2D12BA9DEF8001F9644 /* Localizable.xcstrings */, + 1939D329B2C035D1A15E316B /* PrivacyInfo.xcprivacy */, + ); + name = ReactNative; + sourceTree = ""; + }; + 6A18CE262AF1B119007B1B08 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 6AABC1942B17417E008240EB /* OpenSSL.xcframework */, + 6AFC2CB62B0D5814003B5A63 /* libShopifyCheckoutSheetKit.a */, + 9E0AC43398437CA0E3D05DDF /* libPods-ReactNative.a */, + 591887D86C91BE07EFFC9303 /* libPods-ReactNativeTests.a */, + ); + name = Frameworks; + sourceTree = ""; + }; + 6AB7C68F2AF197BE000DB11B /* Packages */ = { + isa = PBXGroup; + children = ( + ); + name = Packages; + sourceTree = ""; + }; + 832341AE1AAA6A7D00B99B32 /* Libraries */ = { + isa = PBXGroup; + children = ( + ); + name = Libraries; + sourceTree = ""; + }; + 83CBB9F61A601CBA00E9B192 = { + isa = PBXGroup; + children = ( + 6AB7C68F2AF197BE000DB11B /* Packages */, + 13B07FAE1A68108700A75B9A /* ReactNative */, + 832341AE1AAA6A7D00B99B32 /* Libraries */, + 00E356EF1AD99517003FC87E /* ReactNativeTests */, + 83CBBA001A601CBA00E9B192 /* Products */, + BBD78D7AC51CEA395F1C20DB /* Pods */, + 6A18CE262AF1B119007B1B08 /* Frameworks */, + ); + indentWidth = 2; + sourceTree = ""; + tabWidth = 2; + usesTabs = 0; + }; + 83CBBA001A601CBA00E9B192 /* Products */ = { + isa = PBXGroup; + children = ( + 13B07F961A680F5B00A75B9A /* ReactNative.app */, + 00E356EE1AD99517003FC87E /* ReactNativeTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + BBD78D7AC51CEA395F1C20DB /* Pods */ = { + isa = PBXGroup; + children = ( + ED728EF8BB9B5E9BA9D50EBE /* Pods-ReactNative.debug.xcconfig */, + 4EF1B8B6CA16C991C5BEE2F2 /* Pods-ReactNative.release.xcconfig */, + DF9E097D026528607DDCD7B7 /* Pods-ReactNative-ReactNativeTests.debug.xcconfig */, + 7959053DABE1E06BE5CF1255 /* Pods-ReactNative-ReactNativeTests.release.xcconfig */, + E22D02A7C54F90741A3D90E9 /* Pods-ReactNativeTests.debug.xcconfig */, + 39099806D4750FF73C4975E3 /* Pods-ReactNativeTests.release.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 00E356ED1AD99517003FC87E /* ReactNativeTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "ReactNativeTests" */; + buildPhases = ( + 13CD11EE2D19D04D273718C4 /* [CP] Check Pods Manifest.lock */, + 00E356EA1AD99517003FC87E /* Sources */, + 00E356EB1AD99517003FC87E /* Frameworks */, + 00E356EC1AD99517003FC87E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 00E356F51AD99517003FC87E /* PBXTargetDependency */, + ); + name = ReactNativeTests; + productName = ReactNativeTests; + productReference = 00E356EE1AD99517003FC87E /* ReactNativeTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + 13B07F861A680F5B00A75B9A /* ReactNative */ = { + isa = PBXNativeTarget; + buildConfigurationList = 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "ReactNative" */; + buildPhases = ( + EC9AFE111A4C07A98A76F332 /* [CP] Check Pods Manifest.lock */, + 13B07F871A680F5B00A75B9A /* Sources */, + 13B07F8C1A680F5B00A75B9A /* Frameworks */, + 13B07F8E1A680F5B00A75B9A /* Resources */, + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */, + 1F1EB8B45BFDA1305B3ACC48 /* [CP] Copy Pods Resources */, + 1EF77979E16522DC120150A8 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = ReactNative; + productName = ReactNative; + productReference = 13B07F961A680F5B00A75B9A /* ReactNative.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 83CBB9F71A601CBA00E9B192 /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + TargetAttributes = { + 00E356ED1AD99517003FC87E = { + CreatedOnToolsVersion = 6.2; + LastSwiftMigration = 1430; + TestTargetID = 13B07F861A680F5B00A75B9A; + }; + 13B07F861A680F5B00A75B9A = { + LastSwiftMigration = 1430; + }; + }; + }; + buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "ReactNative" */; + compatibilityVersion = "Xcode 12.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 83CBB9F61A601CBA00E9B192; + productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 13B07F861A680F5B00A75B9A /* ReactNative */, + 00E356ED1AD99517003FC87E /* ReactNativeTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 00E356EC1AD99517003FC87E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F8E1A680F5B00A75B9A /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */, + 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, + 6AFAD2D22BA9DEF8001F9644 /* Localizable.xcstrings in Resources */, + 9403907249B4B6D988902B48 /* PrivacyInfo.xcprivacy in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 12; + files = ( + ); + inputPaths = ( + "$(SRCROOT)/.xcode.env.local", + "$(SRCROOT)/.xcode.env", + ); + name = "Bundle React Native code and images"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "set -e\n\nWITH_ENVIRONMENT=\"../../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"\n"; + }; + 13CD11EE2D19D04D273718C4 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ReactNativeTests-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + 1EF77979E16522DC120150A8 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ReactNative/Pods-ReactNative-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ReactNative/Pods-ReactNative-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNative/Pods-ReactNative-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 1F1EB8B45BFDA1305B3ACC48 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ReactNative/Pods-ReactNative-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-ReactNative/Pods-ReactNative-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-ReactNative/Pods-ReactNative-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + EC9AFE111A4C07A98A76F332 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-ReactNative-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 00E356EA1AD99517003FC87E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6A1F48E72B16900600BA591C /* ShopifyCheckoutSheetKitTests.swift in Sources */, + 6A86196D2BF36EB900E5EE1A /* CheckoutDidFailTests.swift in Sources */, + 6AEEAAB22C00010100E5EE1B /* AcceleratedCheckouts_SupportedTests.swift in Sources */, + 6AEEAAB32C00010100E5EE1B /* AcceleratedCheckouts_UnsupportedTests.swift in Sources */, + 6AF1E0032C00010100E5EE1B /* EventSerializationTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 13B07F871A680F5B00A75B9A /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 6A58AAE82E607E0200A6AA85 /* AppDelegate.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 00E356F51AD99517003FC87E /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 13B07F861A680F5B00A75B9A /* ReactNative */; + targetProxy = 00E356F41AD99517003FC87E /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 00E356F61AD99517003FC87E /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E22D02A7C54F90741A3D90E9 /* Pods-ReactNativeTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ENABLE_MODULES = YES; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_ROOT}/Headers/Public\"", + "\"${PODS_ROOT}/Headers/Public/CocoaAsyncSocket\"", + "\"${PODS_ROOT}/Headers/Public/DoubleConversion\"", + "\"${PODS_ROOT}/Headers/Public/FBLazyVector\"", + "\"${PODS_ROOT}/Headers/Public/RCT-Folly\"", + "\"${PODS_ROOT}/Headers/Public/RCTRequired\"", + "\"${PODS_ROOT}/Headers/Public/RCTTypeSafety\"", + "\"${PODS_ROOT}/Headers/Public/RNCMaskedView\"", + "\"${PODS_ROOT}/Headers/Public/RNGestureHandler\"", + "\"${PODS_ROOT}/Headers/Public/RNReanimated\"", + "\"${PODS_ROOT}/Headers/Public/RNScreens\"", + "\"${PODS_ROOT}/Headers/Public/RNVectorIcons\"", + "\"${PODS_ROOT}/Headers/Public/React-Codegen\"", + "\"${PODS_ROOT}/Headers/Public/React-Core\"", + "\"${PODS_ROOT}/Headers/Public/React-NativeModulesApple\"", + "\"${PODS_ROOT}/Headers/Public/React-RCTAnimation\"", + "\"${PODS_ROOT}/Headers/Public/React-RCTAppDelegate\"", + "\"${PODS_ROOT}/Headers/Public/React-RCTBlob\"", + "\"${PODS_ROOT}/Headers/Public/React-RCTText\"", + "\"${PODS_ROOT}/Headers/Public/React-callinvoker\"", + "\"${PODS_ROOT}/Headers/Public/React-cxxreact\"", + "\"${PODS_ROOT}/Headers/Public/React-debug\"", + "\"${PODS_ROOT}/Headers/Public/React-hermes\"", + "\"${PODS_ROOT}/Headers/Public/React-jsi\"", + "\"${PODS_ROOT}/Headers/Public/React-jsiexecutor\"", + "\"${PODS_ROOT}/Headers/Public/React-jsinspector\"", + "\"${PODS_ROOT}/Headers/Public/React-logger\"", + "\"${PODS_ROOT}/Headers/Public/React-perflogger\"", + "\"${PODS_ROOT}/Headers/Public/React-runtimeexecutor\"", + "\"${PODS_ROOT}/Headers/Public/React-runtimescheduler\"", + "\"${PODS_ROOT}/Headers/Public/React-utils\"", + "\"${PODS_ROOT}/Headers/Public/ReactCommon\"", + "\"${PODS_ROOT}/Headers/Public/SocketRocket\"", + "\"${PODS_ROOT}/Headers/Public/Yoga\"", + "\"${PODS_ROOT}/Headers/Public/YogaKit\"", + "\"${PODS_ROOT}/Headers/Public/fmt\"", + "\"${PODS_ROOT}/Headers/Public/glog\"", + "\"${PODS_ROOT}/Headers/Public/hermes-engine\"", + "\"${PODS_ROOT}/Headers/Public/libevent\"", + "\"${PODS_ROOT}/Headers/Public/react-native-safe-area-context\"", + "\"${PODS_ROOT}/Headers/Public/RNShopifyCheckoutSheetKit\"", + "\"$(PODS_ROOT)/DoubleConversion\"", + "\"$(PODS_ROOT)/boost\"", + "\"$(PODS_ROOT)/Headers/Private/React-Core\"", + "\"$(PODS_TARGET_SRCROOT)/include/\"", + "\"$(PODS_ROOT)/boost\"", + "\"$(PODS_ROOT)/boost-for-react-native\"", + "\"$(PODS_ROOT)/glog\"", + "\"$(PODS_ROOT)/RCT-Folly\"", + "\"$(PODS_ROOT)/Headers/Public/React-hermes\"", + "\"$(PODS_ROOT)/Headers/Public/hermes-engine\"", + "\"$(PODS_ROOT)/../../node_modules/react-native/ReactCommon\"", + "\"${PODS_ROOT}/Headers/Public/RNShopifyCheckoutSheetKit\"/**", + ); + INFOPLIST_FILE = ReactNativeTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.6; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(SDKROOT)/usr/lib/swift", + "$(inherited)", + "\"${PODS_CONFIGURATION_BUILD_DIR}/CocoaAsyncSocket\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/DoubleConversion\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RCT-Folly\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RCTTypeSafety\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RNCMaskedView\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RNGestureHandler\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RNReanimated\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RNScreens\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RNVectorIcons\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-Codegen\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-Core-60309c9c\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-CoreModules\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTAnimation\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTAppDelegate\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTBlob\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTImage\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTLinking\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTNetwork\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTSettings\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTText\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-RCTVibration\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-cxxreact\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-debug\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-hermes\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-jsi\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-jsiexecutor\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-jsinspector\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-logger\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-perflogger\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-runtimescheduler\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/React-utils\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/ShopifyCheckoutSheetKit\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/SocketRocket\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/Yoga\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/YogaKit\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/fmt\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/glog\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/libevent\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/react-native-safe-area-context\"", + "\"${PODS_CONFIGURATION_BUILD_DIR}/RNShopifyCheckoutSheetKit\"", + "\"${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}\"", + /usr/lib/swift, + ); + OTHER_LDFLAGS = ( + "-ObjC", + "-lc++", + "$(inherited)", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.shopify.example.CheckoutKitReactNative; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_INCLUDE_PATHS = "$(inherited) \"${PODS_CONFIGURATION_BUILD_DIR}/ShopifyCheckoutSheetKit\" \"${PODS_CONFIGURATION_BUILD_DIR}/YogaKit\" \"${PODS_CONFIGURATION_BUILD_DIR}/RNShopifyCheckoutSheetKit\"/**"; + SWIFT_OBJC_BRIDGING_HEADER = "ReactNativeTests-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ReactNative.app/ReactNative"; + }; + name = Debug; + }; + 00E356F71AD99517003FC87E /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 39099806D4750FF73C4975E3 /* Pods-ReactNativeTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CLANG_ENABLE_MODULES = YES; + COPY_PHASE_STRIP = NO; + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_ROOT}/Headers/Public\"", + "\"${PODS_ROOT}/Headers/Public/CocoaAsyncSocket\"", + "\"${PODS_ROOT}/Headers/Public/DoubleConversion\"", + "\"${PODS_ROOT}/Headers/Public/FBLazyVector\"", + "\"${PODS_ROOT}/Headers/Public/RCT-Folly\"", + "\"${PODS_ROOT}/Headers/Public/RCTRequired\"", + "\"${PODS_ROOT}/Headers/Public/RCTTypeSafety\"", + "\"${PODS_ROOT}/Headers/Public/RNCMaskedView\"", + "\"${PODS_ROOT}/Headers/Public/RNGestureHandler\"", + "\"${PODS_ROOT}/Headers/Public/RNReanimated\"", + "\"${PODS_ROOT}/Headers/Public/RNScreens\"", + "\"${PODS_ROOT}/Headers/Public/RNVectorIcons\"", + "\"${PODS_ROOT}/Headers/Public/React-Codegen\"", + "\"${PODS_ROOT}/Headers/Public/React-Core\"", + "\"${PODS_ROOT}/Headers/Public/React-NativeModulesApple\"", + "\"${PODS_ROOT}/Headers/Public/React-RCTAnimation\"", + "\"${PODS_ROOT}/Headers/Public/React-RCTAppDelegate\"", + "\"${PODS_ROOT}/Headers/Public/React-RCTBlob\"", + "\"${PODS_ROOT}/Headers/Public/React-RCTText\"", + "\"${PODS_ROOT}/Headers/Public/React-callinvoker\"", + "\"${PODS_ROOT}/Headers/Public/React-cxxreact\"", + "\"${PODS_ROOT}/Headers/Public/React-debug\"", + "\"${PODS_ROOT}/Headers/Public/React-hermes\"", + "\"${PODS_ROOT}/Headers/Public/React-jsi\"", + "\"${PODS_ROOT}/Headers/Public/React-jsiexecutor\"", + "\"${PODS_ROOT}/Headers/Public/React-jsinspector\"", + "\"${PODS_ROOT}/Headers/Public/React-logger\"", + "\"${PODS_ROOT}/Headers/Public/React-perflogger\"", + "\"${PODS_ROOT}/Headers/Public/React-runtimeexecutor\"", + "\"${PODS_ROOT}/Headers/Public/React-runtimescheduler\"", + "\"${PODS_ROOT}/Headers/Public/React-utils\"", + "\"${PODS_ROOT}/Headers/Public/ReactCommon\"", + "\"${PODS_ROOT}/Headers/Public/SocketRocket\"", + "\"${PODS_ROOT}/Headers/Public/Yoga\"", + "\"${PODS_ROOT}/Headers/Public/YogaKit\"", + "\"${PODS_ROOT}/Headers/Public/fmt\"", + "\"${PODS_ROOT}/Headers/Public/glog\"", + "\"${PODS_ROOT}/Headers/Public/hermes-engine\"", + "\"${PODS_ROOT}/Headers/Public/libevent\"", + "\"${PODS_ROOT}/Headers/Public/react-native-safe-area-context\"", + "\"${PODS_ROOT}/Headers/Public/RNShopifyCheckoutSheetKit\"", + "\"$(PODS_ROOT)/DoubleConversion\"", + "\"$(PODS_ROOT)/boost\"", + "\"$(PODS_ROOT)/Headers/Private/React-Core\"", + "\"$(PODS_TARGET_SRCROOT)/include/\"", + "\"$(PODS_ROOT)/boost\"", + "\"$(PODS_ROOT)/boost-for-react-native\"", + "\"$(PODS_ROOT)/glog\"", + "\"$(PODS_ROOT)/RCT-Folly\"", + "\"$(PODS_ROOT)/Headers/Public/React-hermes\"", + "\"$(PODS_ROOT)/Headers/Public/hermes-engine\"", + "\"$(PODS_ROOT)/../../node_modules/react-native/ReactCommon\"", + "\"${PODS_ROOT}/Headers/Public/RNShopifyCheckoutSheetKit\"/**", + ); + INFOPLIST_FILE = ReactNativeTests/Info.plist; + IPHONEOS_DEPLOYMENT_TARGET = 16.6; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@loader_path/Frameworks", + ); + OTHER_LDFLAGS = ( + "-ObjC", + "-lc++", + "$(inherited)", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.shopify.example.CheckoutKitReactNative; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_INCLUDE_PATHS = "$(inherited) \"${PODS_CONFIGURATION_BUILD_DIR}/ShopifyCheckoutSheetKit\" \"${PODS_CONFIGURATION_BUILD_DIR}/YogaKit\" \"${PODS_CONFIGURATION_BUILD_DIR}/RNShopifyCheckoutSheetKit\"/**"; + SWIFT_OBJC_BRIDGING_HEADER = "ReactNativeTests-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ReactNative.app/ReactNative"; + }; + name = Release; + }; + 13B07F941A680F5B00A75B9A /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = ED728EF8BB9B5E9BA9D50EBE /* Pods-ReactNative.debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = ReactNative/ReactNative.entitlements; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = A7XGC83MZE; + ENABLE_BITCODE = NO; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_ROOT}/Headers/Public\"", + "\"${PODS_ROOT}/Headers/Public/CocoaAsyncSocket\"", + "\"${PODS_ROOT}/Headers/Public/DoubleConversion\"", + "\"${PODS_ROOT}/Headers/Public/FBLazyVector\"", + "\"${PODS_ROOT}/Headers/Public/RCT-Folly\"", + "\"${PODS_ROOT}/Headers/Public/RCTRequired\"", + "\"${PODS_ROOT}/Headers/Public/RCTTypeSafety\"", + "\"${PODS_ROOT}/Headers/Public/RNCMaskedView\"", + "\"${PODS_ROOT}/Headers/Public/RNGestureHandler\"", + "\"${PODS_ROOT}/Headers/Public/RNReanimated\"", + "\"${PODS_ROOT}/Headers/Public/RNScreens\"", + "\"${PODS_ROOT}/Headers/Public/RNVectorIcons\"", + "\"${PODS_ROOT}/Headers/Public/React-Codegen\"", + "\"${PODS_ROOT}/Headers/Public/React-Core\"", + "\"${PODS_ROOT}/Headers/Public/React-NativeModulesApple\"", + "\"${PODS_ROOT}/Headers/Public/React-RCTAnimation\"", + "\"$(SRCROOT)/Pods/ShopifyCheckoutSheetKit\"/**", + "\"${PODS_ROOT}/Headers/Public/React-RCTAppDelegate\"", + "\"${PODS_ROOT}/Headers/Public/React-RCTBlob\"", + "\"${PODS_ROOT}/Headers/Public/React-RCTText\"", + "\"${PODS_ROOT}/Headers/Public/React-callinvoker\"", + "\"${PODS_ROOT}/Headers/Public/React-cxxreact\"", + "\"${PODS_ROOT}/Headers/Public/React-debug\"", + "\"${PODS_ROOT}/Headers/Public/React-hermes\"", + "\"${PODS_ROOT}/Headers/Public/React-jsi\"", + "\"${PODS_ROOT}/Headers/Public/React-jsiexecutor\"", + "\"${PODS_ROOT}/Headers/Public/React-jsinspector\"", + "\"${PODS_ROOT}/Headers/Public/React-logger\"", + "\"${PODS_ROOT}/Headers/Public/React-perflogger\"", + "\"${PODS_ROOT}/Headers/Public/React-runtimeexecutor\"", + "\"${PODS_ROOT}/Headers/Public/React-runtimescheduler\"", + "\"${PODS_ROOT}/Headers/Public/React-utils\"", + "\"${PODS_ROOT}/Headers/Public/ReactCommon\"", + "\"${PODS_ROOT}/Headers/Public/SocketRocket\"", + "\"${PODS_ROOT}/Headers/Public/Yoga\"", + "\"${PODS_ROOT}/Headers/Public/YogaKit\"", + "\"${PODS_ROOT}/Headers/Public/fmt\"", + "\"${PODS_ROOT}/Headers/Public/glog\"", + "\"${PODS_ROOT}/Headers/Public/hermes-engine\"", + "\"${PODS_ROOT}/Headers/Public/libevent\"", + "\"${PODS_ROOT}/Headers/Public/react-native-safe-area-context\"", + "\"${PODS_ROOT}/Headers/Public/RNShopifyCheckoutSheetKit\"/**", + "\"$(PODS_ROOT)/DoubleConversion\"", + "\"$(PODS_ROOT)/boost\"", + "\"$(PODS_ROOT)/Headers/Private/React-Core\"", + "\"$(PODS_TARGET_SRCROOT)/include/\"", + "\"$(PODS_ROOT)/boost\"", + "\"$(PODS_ROOT)/boost-for-react-native\"", + "\"$(PODS_ROOT)/glog\"", + "\"$(PODS_ROOT)/RCT-Folly\"", + "\"$(PODS_ROOT)/Headers/Public/React-hermes\"", + "\"$(PODS_ROOT)/Headers/Public/hermes-engine\"", + "\"$(PODS_ROOT)/../../node_modules/react-native/ReactCommon\"", + ); + INFOPLIST_FILE = ReactNative/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Checkout Kit React Native Demo"; + IPHONEOS_DEPLOYMENT_TARGET = 16.6; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.shopify.example.CheckoutKitReactNative; + PRODUCT_NAME = ReactNative; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_OBJC_BRIDGING_HEADER = "ReactNative-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 13B07F951A680F5B00A75B9A /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 4EF1B8B6CA16C991C5BEE2F2 /* Pods-ReactNative.release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = ReactNative/ReactNative.entitlements; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = A7XGC83MZE; + HEADER_SEARCH_PATHS = ( + "$(inherited)", + "\"${PODS_ROOT}/Headers/Public\"", + "\"${PODS_ROOT}/Headers/Public/CocoaAsyncSocket\"", + "\"${PODS_ROOT}/Headers/Public/DoubleConversion\"", + "\"${PODS_ROOT}/Headers/Public/FBLazyVector\"", + "\"${PODS_ROOT}/Headers/Public/RCT-Folly\"", + "\"${PODS_ROOT}/Headers/Public/RCTRequired\"", + "\"${PODS_ROOT}/Headers/Public/RCTTypeSafety\"", + "\"${PODS_ROOT}/Headers/Public/RNCMaskedView\"", + "\"${PODS_ROOT}/Headers/Public/RNGestureHandler\"", + "\"${PODS_ROOT}/Headers/Public/RNReanimated\"", + "\"${PODS_ROOT}/Headers/Public/RNScreens\"", + "\"${PODS_ROOT}/Headers/Public/RNVectorIcons\"", + "\"${PODS_ROOT}/Headers/Public/React-Codegen\"", + "\"${PODS_ROOT}/Headers/Public/React-Core\"", + "\"${PODS_ROOT}/Headers/Public/React-NativeModulesApple\"", + "\"${PODS_ROOT}/Headers/Public/React-RCTAnimation\"", + "\"$(SRCROOT)/Pods/ShopifyCheckoutSheetKit\"/**", + "\"${PODS_ROOT}/Headers/Public/React-RCTAppDelegate\"", + "\"${PODS_ROOT}/Headers/Public/React-RCTBlob\"", + "\"${PODS_ROOT}/Headers/Public/React-RCTText\"", + "\"${PODS_ROOT}/Headers/Public/React-callinvoker\"", + "\"${PODS_ROOT}/Headers/Public/React-cxxreact\"", + "\"${PODS_ROOT}/Headers/Public/React-debug\"", + "\"${PODS_ROOT}/Headers/Public/React-hermes\"", + "\"${PODS_ROOT}/Headers/Public/React-jsi\"", + "\"${PODS_ROOT}/Headers/Public/React-jsiexecutor\"", + "\"${PODS_ROOT}/Headers/Public/React-jsinspector\"", + "\"${PODS_ROOT}/Headers/Public/React-logger\"", + "\"${PODS_ROOT}/Headers/Public/React-perflogger\"", + "\"${PODS_ROOT}/Headers/Public/React-runtimeexecutor\"", + "\"${PODS_ROOT}/Headers/Public/React-runtimescheduler\"", + "\"${PODS_ROOT}/Headers/Public/React-utils\"", + "\"${PODS_ROOT}/Headers/Public/ReactCommon\"", + "\"${PODS_ROOT}/Headers/Public/SocketRocket\"", + "\"${PODS_ROOT}/Headers/Public/Yoga\"", + "\"${PODS_ROOT}/Headers/Public/YogaKit\"", + "\"${PODS_ROOT}/Headers/Public/fmt\"", + "\"${PODS_ROOT}/Headers/Public/glog\"", + "\"${PODS_ROOT}/Headers/Public/hermes-engine\"", + "\"${PODS_ROOT}/Headers/Public/libevent\"", + "\"${PODS_ROOT}/Headers/Public/react-native-safe-area-context\"", + "\"${PODS_ROOT}/Headers/Public/RNShopifyCheckoutSheetKit\"/**", + "\"$(PODS_ROOT)/DoubleConversion\"", + "\"$(PODS_ROOT)/boost\"", + "\"$(PODS_ROOT)/Headers/Private/React-Core\"", + "\"$(PODS_TARGET_SRCROOT)/include/\"", + "\"$(PODS_ROOT)/boost\"", + "\"$(PODS_ROOT)/boost-for-react-native\"", + "\"$(PODS_ROOT)/glog\"", + "\"$(PODS_ROOT)/RCT-Folly\"", + "\"$(PODS_ROOT)/Headers/Public/React-hermes\"", + "\"$(PODS_ROOT)/Headers/Public/hermes-engine\"", + "\"$(PODS_ROOT)/../../node_modules/react-native/ReactCommon\"", + ); + INFOPLIST_FILE = ReactNative/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = "Checkout Kit React Native Demo"; + IPHONEOS_DEPLOYMENT_TARGET = 16.6; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + MARKETING_VERSION = 1.0; + OTHER_LDFLAGS = ( + "$(inherited)", + "-ObjC", + "-lc++", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.shopify.example.CheckoutKitReactNative; + PRODUCT_NAME = ReactNative; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SWIFT_OBJC_BRIDGING_HEADER = "ReactNative-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 1; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; + 83CBBA201A601CBA00E9B192 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CC = ""; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++20"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CXX = ""; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION, + ); + GCC_SYMBOLS_PRIVATE_EXTERN = NO; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.6; + LD = ""; + LDPLUSPLUS = ""; + LD_RUNPATH_SEARCH_PATHS = ( + /usr/lib/swift, + "$(inherited)", + ); + LIBRARY_SEARCH_PATHS = ( + "\"$(SDKROOT)/usr/lib/swift\"", + "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", + "\"$(inherited)\"", + ); + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = "$(inherited)"; + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-DFOLLY_NO_CONFIG", + "-DFOLLY_MOBILE=1", + "-DFOLLY_USE_LIBCPP=1", + "-DFOLLY_CFG_NO_COROUTINES=1", + ); + OTHER_LDFLAGS = "$(inherited)"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG"; + SWIFT_OBJC_BRIDGING_HEADER = "ReactNative-Bridging-Header.h"; + USE_HERMES = true; + }; + name = Debug; + }; + 83CBBA211A601CBA00E9B192 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CC = ""; + CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; + CLANG_CXX_LANGUAGE_STANDARD = "c++20"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = YES; + CXX = ""; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = i386; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + _LIBCPP_ENABLE_CXX17_REMOVED_UNARY_BINARY_FUNCTION, + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.6; + LD = ""; + LDPLUSPLUS = ""; + LD_RUNPATH_SEARCH_PATHS = ( + /usr/lib/swift, + "$(inherited)", + ); + LIBRARY_SEARCH_PATHS = ( + "\"$(SDKROOT)/usr/lib/swift\"", + "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"", + "\"$(inherited)\"", + ); + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CFLAGS = "$(inherited)"; + OTHER_CPLUSPLUSFLAGS = ( + "$(OTHER_CFLAGS)", + "-DFOLLY_NO_CONFIG", + "-DFOLLY_MOBILE=1", + "-DFOLLY_USE_LIBCPP=1", + "-DFOLLY_CFG_NO_COROUTINES=1", + ); + OTHER_LDFLAGS = "$(inherited)"; + REACT_NATIVE_PATH = "${PODS_ROOT}/../../../node_modules/react-native"; + SDKROOT = iphoneos; + SWIFT_OBJC_BRIDGING_HEADER = "ReactNative-Bridging-Header.h"; + USE_HERMES = true; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 00E357021AD99517003FC87E /* Build configuration list for PBXNativeTarget "ReactNativeTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 00E356F61AD99517003FC87E /* Debug */, + 00E356F71AD99517003FC87E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 13B07F931A680F5B00A75B9A /* Build configuration list for PBXNativeTarget "ReactNative" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 13B07F941A680F5B00A75B9A /* Debug */, + 13B07F951A680F5B00A75B9A /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "ReactNative" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 83CBBA201A601CBA00E9B192 /* Debug */, + 83CBBA211A601CBA00E9B192 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */; +} diff --git a/react-native/sample/ios/ReactNative.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/react-native/sample/ios/ReactNative.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..919434a6 --- /dev/null +++ b/react-native/sample/ios/ReactNative.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/react-native/sample/ios/ReactNative.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/react-native/sample/ios/ReactNative.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/react-native/sample/ios/ReactNative.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/react-native/sample/ios/ReactNative.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/react-native/sample/ios/ReactNative.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 00000000..58fc765d --- /dev/null +++ b/react-native/sample/ios/ReactNative.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,14 @@ +{ + "pins" : [ + { + "identity" : "checkout-kit-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/shopify/checkout-kit-swift", + "state" : { + "revision" : "3125cee51f53ec838d1d443f65cabdc14ff5e891", + "version" : "0.6.0" + } + } + ], + "version" : 2 +} diff --git a/react-native/sample/ios/ReactNative.xcodeproj/xcshareddata/xcschemes/ReactNative.xcscheme b/react-native/sample/ios/ReactNative.xcodeproj/xcshareddata/xcschemes/ReactNative.xcscheme new file mode 100644 index 00000000..e3e30f2e --- /dev/null +++ b/react-native/sample/ios/ReactNative.xcodeproj/xcshareddata/xcschemes/ReactNative.xcscheme @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/react-native/sample/ios/ReactNative.xcworkspace/contents.xcworkspacedata b/react-native/sample/ios/ReactNative.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..9b3933a9 --- /dev/null +++ b/react-native/sample/ios/ReactNative.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,10 @@ + + + + + + + diff --git a/react-native/sample/ios/ReactNative.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/react-native/sample/ios/ReactNative.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/react-native/sample/ios/ReactNative.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/react-native/sample/ios/ReactNative/Images.xcassets/AppIcon.appiconset/1024.jpg b/react-native/sample/ios/ReactNative/Images.xcassets/AppIcon.appiconset/1024.jpg new file mode 100644 index 00000000..014b4f24 Binary files /dev/null and b/react-native/sample/ios/ReactNative/Images.xcassets/AppIcon.appiconset/1024.jpg differ diff --git a/react-native/sample/ios/ReactNative/Images.xcassets/AppIcon.appiconset/Contents.json b/react-native/sample/ios/ReactNative/Images.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..0fae87d5 --- /dev/null +++ b/react-native/sample/ios/ReactNative/Images.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "1024.jpg", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/react-native/sample/ios/ReactNative/Images.xcassets/Contents.json b/react-native/sample/ios/ReactNative/Images.xcassets/Contents.json new file mode 100644 index 00000000..2d92bd53 --- /dev/null +++ b/react-native/sample/ios/ReactNative/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/react-native/sample/ios/ReactNative/Info.plist b/react-native/sample/ios/ReactNative/Info.plist new file mode 100644 index 00000000..eb830f68 --- /dev/null +++ b/react-native/sample/ios/ReactNative/Info.plist @@ -0,0 +1,74 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleDisplayName + ReactNative + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleSignature + ???? + CFBundleURLTypes + + + CFBundleURLName + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleURLSchemes + + rn + + + + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + LSApplicationQueriesSchemes + + rn + + LSRequiresIPhoneOS + + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + NSAllowsArbitraryLoadsForMedia + + NSAllowsLocalNetworking + + + NSLocationWhenInUseUsageDescription + Your location is required to locate pickup points near you. + RCTNewArchEnabled + + UIAppFonts + + Entypo.ttf + + UILaunchStoryboardName + LaunchScreen + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/react-native/sample/ios/ReactNative/LaunchScreen.storyboard b/react-native/sample/ios/ReactNative/LaunchScreen.storyboard new file mode 100644 index 00000000..b954db79 --- /dev/null +++ b/react-native/sample/ios/ReactNative/LaunchScreen.storyboard @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/react-native/sample/ios/ReactNative/Localizable.xcstrings b/react-native/sample/ios/ReactNative/Localizable.xcstrings new file mode 100644 index 00000000..a06d4388 --- /dev/null +++ b/react-native/sample/ios/ReactNative/Localizable.xcstrings @@ -0,0 +1,18 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "shopify_checkout_sheet_title" : { + "comment" : "Checkout title", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Checkout" + } + } + } + } + }, + "version" : "1.0" +} diff --git a/react-native/sample/ios/ReactNative/PrivacyInfo.xcprivacy b/react-native/sample/ios/ReactNative/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..41b8317f --- /dev/null +++ b/react-native/sample/ios/ReactNative/PrivacyInfo.xcprivacy @@ -0,0 +1,37 @@ + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryUserDefaults + NSPrivacyAccessedAPITypeReasons + + CA92.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + + + + NSPrivacyCollectedDataTypes + + NSPrivacyTracking + + + diff --git a/react-native/sample/ios/ReactNativeTests-Bridging-Header.h b/react-native/sample/ios/ReactNativeTests-Bridging-Header.h new file mode 100644 index 00000000..1b2cb5d6 --- /dev/null +++ b/react-native/sample/ios/ReactNativeTests-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/react-native/sample/ios/ReactNativeTests/AcceleratedCheckouts_SupportedTests.swift b/react-native/sample/ios/ReactNativeTests/AcceleratedCheckouts_SupportedTests.swift new file mode 100644 index 00000000..1bca9fac --- /dev/null +++ b/react-native/sample/ios/ReactNativeTests/AcceleratedCheckouts_SupportedTests.swift @@ -0,0 +1,428 @@ +/* + MIT License + + Copyright 2023 - Present, Shopify Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import Foundation +import PassKit +import SwiftUI +@testable import RNShopifyCheckoutSheetKit +@testable import ShopifyCheckoutSheetKit +import XCTest + +struct WalletButtons { + static let zero = Double(0) + static let one = Double(48) + static let two = Double(104) +} + +@available(iOS 16.0, *) +class AcceleratedCheckouts_SupportedTests: XCTestCase { + private var shopifyCheckoutSheetKit: RCTShopifyCheckoutSheetKit! + + override func setUp() { + super.setUp() + shopifyCheckoutSheetKit = RCTShopifyCheckoutSheetKit() + resetSharedConfigurations() + resetCheckoutKitDefaults() + } + + override func tearDown() { + resetSharedConfigurations() + shopifyCheckoutSheetKit = nil + super.tearDown() + } + + private func resetSharedConfigurations() { + AcceleratedCheckoutConfiguration.shared.configuration = nil + AcceleratedCheckoutConfiguration.shared.applePayConfiguration = nil + } + + private func resetCheckoutKitDefaults() { + ShopifyCheckoutSheetKit.configuration.preloading = Configuration.Preloading(enabled: true) + ShopifyCheckoutSheetKit.configuration.colorScheme = .automatic + ShopifyCheckoutSheetKit.configuration.closeButtonTintColor = nil + } + + private func configureAcceleratedCheckouts(includeApplePay: Bool, customerAccessToken: String? = nil) { + let expectation = self.expectation(description: "configureAcceleratedCheckouts") + + let storefrontDomain = "example.myshopify.com" + let accessToken = "shpat_test_token" + let email = "buyer@example.com" + let phone = "+12223334444" + let merchantIdentifier: String? = includeApplePay ? "merchant.com.shopify.reactnative.tests" : nil + let contactFields: [String]? = includeApplePay ? ["email", "phone"] : nil + let supportedShippingCountries: [String]? = includeApplePay ? ["IE", "CA"] : nil + + shopifyCheckoutSheetKit.configureAcceleratedCheckouts( + storefrontDomain, + storefrontAccessToken: accessToken, + customerEmail: email, + customerPhoneNumber: phone, + customerAccessToken: customerAccessToken, + applePayMerchantIdentifier: merchantIdentifier, + applyPayContactFields: contactFields, + supportedShippingCountries: supportedShippingCountries, + resolve: { _ in expectation.fulfill() }, + reject: { _, _, _ in } + ) + + wait(for: [expectation], timeout: 2) + } + + func testConfigureAcceleratedCheckoutsSetsSharedConfigsOnIOS16() throws { + let notificationExpectation = expectation(forNotification: Notification.Name("AcceleratedCheckoutConfigurationUpdated"), object: nil, handler: nil) + configureAcceleratedCheckouts(includeApplePay: true) + wait(for: [notificationExpectation], timeout: 2) + XCTAssertNotNil(AcceleratedCheckoutConfiguration.shared.configuration) + XCTAssertNotNil(AcceleratedCheckoutConfiguration.shared.applePayConfiguration) + } + + func testIsAcceleratedCheckoutAvailableBeforeAndAfterConfig() throws { + let beforeExpectation = expectation(description: "isAcceleratedCheckoutAvailable before") + var beforeValue: Bool = true + shopifyCheckoutSheetKit.isAcceleratedCheckoutAvailable({ value in + beforeValue = (value as? Bool) ?? true + beforeExpectation.fulfill() + }, reject: { _, _, _ in }) + wait(for: [beforeExpectation], timeout: 2) + XCTAssertEqual(beforeValue, false) + + configureAcceleratedCheckouts(includeApplePay: false) + + let afterExpectation = expectation(description: "isAcceleratedCheckoutAvailable after") + var afterValue: Bool = false + shopifyCheckoutSheetKit.isAcceleratedCheckoutAvailable({ value in + afterValue = (value as? Bool) ?? false + afterExpectation.fulfill() + }, reject: { _, _, _ in }) + wait(for: [afterExpectation], timeout: 2) + XCTAssertEqual(afterValue, true) + } + + func testIsApplePayAvailableRequiresApplePayConfig() throws { + let beforeExpectation = expectation(description: "isApplePayAvailable before") + var beforeValue: Bool = true + shopifyCheckoutSheetKit.isApplePayAvailable({ value in + beforeValue = (value as? Bool) ?? true + beforeExpectation.fulfill() + }, reject: { _, _, _ in }) + wait(for: [beforeExpectation], timeout: 2) + XCTAssertEqual(beforeValue, false) + + configureAcceleratedCheckouts(includeApplePay: false) + + let withoutApplePayExpectation = expectation(description: "isApplePayAvailable without Apple Pay") + var withoutApplePayValue: Bool = true + shopifyCheckoutSheetKit.isApplePayAvailable({ value in + withoutApplePayValue = (value as? Bool) ?? true + withoutApplePayExpectation.fulfill() + }, reject: { _, _, _ in }) + wait(for: [withoutApplePayExpectation], timeout: 2) + XCTAssertEqual(withoutApplePayValue, false) + + configureAcceleratedCheckouts(includeApplePay: true) + + let afterExpectation = expectation(description: "isApplePayAvailable after") + var afterValue: Bool = false + shopifyCheckoutSheetKit.isApplePayAvailable({ value in + afterValue = (value as? Bool) ?? false + afterExpectation.fulfill() + }, reject: { _, _, _ in }) + wait(for: [afterExpectation], timeout: 2) + XCTAssertEqual(afterValue, true) + } + + func testConfigureAcceleratedCheckoutsStoresCustomerAccessToken() throws { + let token = "customer-access-token-123" + configureAcceleratedCheckouts(includeApplePay: false, customerAccessToken: token) + guard let config = AcceleratedCheckoutConfiguration.shared.configuration else { + return XCTFail("configuration missing") + } + XCTAssertEqual(config.customer?.copy().customerAccessToken, token) + } + + func testConfigureAcceleratedCheckoutsWithNilCustomerAccessToken() throws { + configureAcceleratedCheckouts(includeApplePay: false, customerAccessToken: nil) + guard let config = AcceleratedCheckoutConfiguration.shared.configuration else { + return XCTFail("configuration missing") + } + XCTAssertNil(config.customer?.copy().customerAccessToken) + } + + func testButtonsViewHeightZeroWhenWalletsExplicitEmpty() throws { + configureAcceleratedCheckouts(includeApplePay: false) + + let viewExpectation = expectation(description: "onSizeChange height 0 for empty wallets") + + let view = RCTAcceleratedCheckoutButtonsView() + view.checkoutIdentifier = ["cartId": "gid://shopify/Cart/1"] + view.onSizeChange = { payload in + guard let payload = payload else { return } + let height = (payload["height"] as? NSNumber)?.doubleValue ?? 0 + if height == WalletButtons.zero { + viewExpectation.fulfill() + } + } + view.wallets = [] + + wait(for: [viewExpectation], timeout: 2) + } + + func testButtonsViewHeightReflectsWalletCountWhenWalletsProvided() throws { + configureAcceleratedCheckouts(includeApplePay: false) + + let viewExpectation = expectation(description: "onSizeChange height for two wallets") + var fulfilled = false + + let view = RCTAcceleratedCheckoutButtonsView() + view.checkoutIdentifier = ["cartId": "gid://shopify/Cart/1"] + view.onSizeChange = { payload in + if fulfilled { return } + guard let payload = payload else { return } + + let height = (payload["height"] as? NSNumber)?.doubleValue ?? -1 + + if height == WalletButtons.two { + fulfilled = true + viewExpectation.fulfill() + } + } + view.wallets = ["applePay", "shopPay"] + + wait(for: [viewExpectation], timeout: 2) + } + + func testButtonsViewEmptyWhenContainingUnknownWallets() throws { + configureAcceleratedCheckouts(includeApplePay: false) + + let viewExpectation = expectation(description: "onSizeChange height 0 when contains unknown wallet") + var fulfilled = false + + let view = RCTAcceleratedCheckoutButtonsView() + view.checkoutIdentifier = ["cartId": "gid://shopify/Cart/1"] + view.onSizeChange = { payload in + if fulfilled { return } + guard let payload = payload else { return } + + let height = (payload["height"] as? NSNumber)?.doubleValue ?? -1 + + if height == WalletButtons.zero { + fulfilled = true + viewExpectation.fulfill() + } + } + view.wallets = ["applePay", "bogus", "shopPay"] + + wait(for: [viewExpectation], timeout: 2) + XCTAssertNil(view.instance) + } + + func testButtonsViewEmptyWhenCheckoutIdentifierMissingOrInvalid() throws { + configureAcceleratedCheckouts(includeApplePay: false) + + let missingExpectation = expectation(description: "height 0 when identifier missing") + let missing = RCTAcceleratedCheckoutButtonsView() + missing.onSizeChange = { payload in + guard let payload = payload else { return } + let height = (payload["height"] as? NSNumber)?.doubleValue ?? -1 + if height == 0 { missingExpectation.fulfill() } + } + _ = missing + NotificationCenter.default.post(name: Notification.Name("AcceleratedCheckoutConfigurationUpdated"), object: nil) + + wait(for: [missingExpectation], timeout: 2) + + let invalidExpectation = expectation(description: "height 0 when identifier invalid") + let invalid = RCTAcceleratedCheckoutButtonsView() + invalid.onSizeChange = { payload in + guard let payload = payload else { return } + let height = (payload["height"] as? NSNumber)?.doubleValue ?? -1 + if height == 0 { + invalidExpectation.fulfill() + } + } + invalid.checkoutIdentifier = ["variantId": "gid://shopify/ProductVariant/1", "quantity": 0] + + wait(for: [invalidExpectation], timeout: 2) + } + + func testButtonsViewAcceptsCartIdWithWhitespace() throws { + configureAcceleratedCheckouts(includeApplePay: false) + + let viewExpectation = expectation(description: "trimmed cartId renders non-zero height") + var fulfilledCart = false + + let view = RCTAcceleratedCheckoutButtonsView() + view.wallets = ["applePay", "shopPay"] + view.onSizeChange = { payload in + if fulfilledCart { return } + guard let payload = payload else { return } + let height = (payload["height"] as? NSNumber)?.doubleValue ?? -1 + if height == WalletButtons.two { + fulfilledCart = true + viewExpectation.fulfill() + } + } + view.checkoutIdentifier = ["cartId": " gid://shopify/Cart/1 "] + + wait(for: [viewExpectation], timeout: 2) + XCTAssertNotNil(view.instance) + } + + func testButtonsViewAcceptsVariantAndQuantity_withDefaultWallets() throws { + configureAcceleratedCheckouts(includeApplePay: false) + + let viewExpectation = expectation(description: "variant + quantity renders non-zero height") + var fulfilledVariant = false + + let view = RCTAcceleratedCheckoutButtonsView() + view.onSizeChange = { payload in + if fulfilledVariant { return } + guard let payload = payload else { return } + + let height = (payload["height"] as? NSNumber)?.doubleValue ?? -1 + + /// "Wallets" prop is nil, so default rendered (2 buttons) + if height == WalletButtons.two { + fulfilledVariant = true + viewExpectation.fulfill() + } + } + view.checkoutIdentifier = [ + "variantId": "gid://shopify/ProductVariant/123", + "quantity": NSNumber(value: 2) + ] + + wait(for: [viewExpectation], timeout: 2) + XCTAssertNotNil(view.instance) + } + + func testButtonsViewAcceptsVariantAndQuantity_withExplicitWallets() throws { + configureAcceleratedCheckouts(includeApplePay: false) + + let viewExpectation = expectation(description: "variant + quantity renders non-zero height") + var fulfilledVariant = false + + let view = RCTAcceleratedCheckoutButtonsView() + view.wallets = ["shopPay"] + view.onSizeChange = { payload in + if fulfilledVariant { return } + guard let payload = payload else { return } + + let height = (payload["height"] as? NSNumber)?.doubleValue ?? -1 + + /// Wallets prop is explicitly set, so must be respected + if height == WalletButtons.one { + fulfilledVariant = true + viewExpectation.fulfill() + } + } + view.checkoutIdentifier = [ + "variantId": "gid://shopify/ProductVariant/123", + "quantity": NSNumber(value: 2) + ] + + wait(for: [viewExpectation], timeout: 2) + XCTAssertNotNil(view.instance) + } + + func testButtonsViewRendersEmptyWhenWalletsArrayIsEmpty() throws { + configureAcceleratedCheckouts(includeApplePay: false) + + let viewExpectation = expectation(description: "variant + quantity renders non-zero height") + var fulfilledVariant = false + + let view = RCTAcceleratedCheckoutButtonsView() + view.wallets = [] + view.onSizeChange = { payload in + if fulfilledVariant { return } + guard let payload = payload else { return } + + let height = (payload["height"] as? NSNumber)?.doubleValue ?? -1 + + /// Wallets prop is explicitly set, so must be respected + if height == WalletButtons.zero { + fulfilledVariant = true + viewExpectation.fulfill() + } + } + view.checkoutIdentifier = [ + "variantId": "gid://shopify/ProductVariant/123", + "quantity": NSNumber(value: 2) + ] + + wait(for: [viewExpectation], timeout: 2) + XCTAssertNil(view.instance) + } + + func testButtonsViewHeightZeroWhenWalletsMapToEmptyUnknowns() throws { + configureAcceleratedCheckouts(includeApplePay: false) + + let view = RCTAcceleratedCheckoutButtonsView() + view.wallets = ["bogus", "unknown", "invalid"] + + let height = view.intrinsicContentSize.height + XCTAssertEqual(height, WalletButtons.zero) + XCTAssertNil(view.instance) + } + + func testApplePayLabelMapping_knownAndUnknownKeys() throws { + XCTAssertTrue(PayWithApplePayButtonLabel.from("buy") == .buy) + XCTAssertTrue(PayWithApplePayButtonLabel.from("checkout") == .checkout) + XCTAssertTrue(PayWithApplePayButtonLabel.from("continue") == .continue) + XCTAssertTrue(PayWithApplePayButtonLabel.from("plain") == .plain) + XCTAssertTrue(PayWithApplePayButtonLabel.from("unknown") == .plain) + XCTAssertTrue(PayWithApplePayButtonLabel.from("unknown", fallback: .buy) == .buy) + } + + func testConfigureAcceleratedCheckoutsResolvesFalseForInvalidApplePayContactField() throws { + let expectation = self.expectation(description: "configureAcceleratedCheckouts invalid contact field resolves false") + var resolved: Bool = true + + let storefrontDomain = "example.myshopify.com" + let accessToken = "shpat_test_token" + + shopifyCheckoutSheetKit.configureAcceleratedCheckouts( + storefrontDomain, + storefrontAccessToken: accessToken, + customerEmail: nil, + customerPhoneNumber: nil, + customerAccessToken: nil, + applePayMerchantIdentifier: "merchant.com.shopify.reactnative.tests", + applyPayContactFields: ["email", "not_a_field"], + supportedShippingCountries: [], + resolve: { value in + resolved = (value as? Bool) ?? true + expectation.fulfill() + }, + reject: { _, _, _ in } + ) + + wait(for: [expectation], timeout: 2) + XCTAssertEqual(resolved, false) + } +} + +private extension BinaryInteger { + var doubleValue: Double { Double(self) } +} diff --git a/react-native/sample/ios/ReactNativeTests/AcceleratedCheckouts_UnsupportedTests.swift b/react-native/sample/ios/ReactNativeTests/AcceleratedCheckouts_UnsupportedTests.swift new file mode 100644 index 00000000..2c5d7d67 --- /dev/null +++ b/react-native/sample/ios/ReactNativeTests/AcceleratedCheckouts_UnsupportedTests.swift @@ -0,0 +1,65 @@ +/* + MIT License + + Copyright 2023 - Present, Shopify Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import Foundation +@testable import RNShopifyCheckoutSheetKit +import XCTest + +class AcceleratedCheckouts_UnsupportedTests: XCTestCase { + private var module: RCTShopifyCheckoutSheetKit! + private var manager: RCTAcceleratedCheckoutButtonsManager! + + override func setUp() { + super.setUp() + module = RCTShopifyCheckoutSheetKit() + manager = RCTAcceleratedCheckoutButtonsManager() + manager.supported = false + } + + override func tearDown() { + module = nil + manager = nil + super.tearDown() + } + + func testManagerReturnsFallbackViewOnPreIOS16() throws { + let view = manager.view() + XCTAssertEqual(String(describing: type(of: view!)), "UIView") + } + + func testAvailabilityAPIsReturnFalseOnPreIOS16() throws { + let accelExpectation = expectation(description: "isAcceleratedCheckoutAvailable false on <16") + module.isAcceleratedCheckoutAvailable({ value in + XCTAssertEqual(value as? Bool, false) + accelExpectation.fulfill() + }, reject: { _, _, _ in }) + + let applePayExpectation = expectation(description: "isApplePayAvailable false on <16") + module.isApplePayAvailable({ value in + XCTAssertEqual(value as? Bool, false) + applePayExpectation.fulfill() + }, reject: { _, _, _ in }) + + wait(for: [accelExpectation, applePayExpectation], timeout: 2) + } +} diff --git a/react-native/sample/ios/ReactNativeTests/CheckoutDidFailTests.swift b/react-native/sample/ios/ReactNativeTests/CheckoutDidFailTests.swift new file mode 100644 index 00000000..a8243e2d --- /dev/null +++ b/react-native/sample/ios/ReactNativeTests/CheckoutDidFailTests.swift @@ -0,0 +1,201 @@ +/* + MIT License + + Copyright 2023 - Present, Shopify Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import Foundation +@testable import RNShopifyCheckoutSheetKit +@testable import ShopifyCheckoutSheetKit +import XCTest + +class CheckoutDidFailTests: XCTestCase { + private var shopifyCheckoutSheetKit: RCTShopifyCheckoutSheetKit! + + override func setUp() { + super.setUp() + shopifyCheckoutSheetKit = getShopifyCheckoutSheetKit() + resetShopifyCheckoutSheetKitDefaults() + } + + private func resetShopifyCheckoutSheetKitDefaults() { + ShopifyCheckoutSheetKit.configuration.preloading = Configuration.Preloading(enabled: true) + ShopifyCheckoutSheetKit.configuration.colorScheme = .automatic + } + + override func tearDown() { + shopifyCheckoutSheetKit = nil + super.tearDown() + } + + private func getShopifyCheckoutSheetKit() -> RCTShopifyCheckoutSheetKit { + return RCTShopifyCheckoutSheetKit() + } + + func testCheckoutDidFailEmitsCheckoutExpiredError() { + let mock = mockSendEvent(eventName: "error") + mock.startObserving() + + let error = CheckoutError.checkoutExpired( + message: "expired", + code: CheckoutErrorCode.cartExpired, + recoverable: false + ) + + mock.checkoutDidFail(error: error) + + XCTAssertTrue(mock.didSendEvent, "Event should have been sent when checkout fails") + + guard let eventBody = mock.eventBody as? [String: Any] else { + return XCTFail("Event body was not available or not in the correct format") + } + + if case .checkoutExpired = error { + XCTAssertEqual(eventBody["__typename"] as? String, "CheckoutExpiredError") + XCTAssertEqual(eventBody["message"] as? String, "expired") + XCTAssertEqual(eventBody["code"] as? String, CheckoutErrorCode.cartExpired.rawValue) + XCTAssertEqual(eventBody["recoverable"] as? Bool, false) + } else { + XCTFail("Expected checkoutExpiredError but found different error") + } + } + + func testCheckoutDidFailEmitsCheckoutClientError() { + let mock = mockSendEvent(eventName: "error") + mock.startObserving() + + let error = CheckoutError.checkoutUnavailable( + message: "expired", + code: .clientError(code: CheckoutErrorCode.cartExpired), + recoverable: false + ) + + mock.checkoutDidFail(error: error) + + XCTAssertTrue(mock.didSendEvent, "Event should have been sent when checkout fails") + + guard let eventBody = mock.eventBody as? [String: Any] else { + return XCTFail("Event body was not available or not in the correct format") + } + + if case .checkoutUnavailable = error { + XCTAssertEqual(eventBody["__typename"] as? String, "CheckoutClientError") + XCTAssertEqual(eventBody["message"] as? String, "expired") + XCTAssertEqual(eventBody["code"] as? String, CheckoutErrorCode.cartExpired.rawValue) + XCTAssertEqual(eventBody["recoverable"] as? Bool, false) + } else { + XCTFail("Expected checkoutClientError but found different error") + } + } + + func testCheckoutDidFailEmitsCheckoutHTTPError() { + let mock = mockSendEvent(eventName: "error") + mock.startObserving() + + let error = CheckoutError.checkoutUnavailable( + message: "internal server error", + code: .httpError(statusCode: 500), + recoverable: true + ) + + mock.checkoutDidFail(error: error) + + XCTAssertTrue(mock.didSendEvent, "Event should have been sent when checkout fails") + + guard let eventBody = mock.eventBody as? [String: Any] else { + return XCTFail("Event body was not available or not in the correct format") + } + + if case .checkoutUnavailable = error { + XCTAssertEqual(eventBody["__typename"] as? String, "CheckoutHTTPError") + XCTAssertEqual(eventBody["message"] as? String, "internal server error") + XCTAssertEqual(eventBody["statusCode"] as? Int, 500) + XCTAssertEqual(eventBody["recoverable"] as? Bool, true) + } else { + XCTFail("Expected checkoutClientError but found different error") + } + } + + func testCheckoutDidFailEmitsConfigurationError() { + let mock = mockSendEvent(eventName: "error") + mock.startObserving() + + let error = CheckoutError.configurationError( + message: "storefront password required", + code: CheckoutErrorCode.storefrontPasswordRequired, + recoverable: false + ) + + mock.checkoutDidFail(error: error) + + XCTAssertTrue(mock.didSendEvent, "Event should have been sent when checkout fails") + + guard let eventBody = mock.eventBody as? [String: Any] else { + return XCTFail("Event body was not available or not in the correct format") + } + + if case .configurationError = error { + XCTAssertEqual(eventBody["__typename"] as? String, "ConfigurationError") + XCTAssertEqual(eventBody["message"] as? String, "storefront password required") + XCTAssertEqual(eventBody["code"] as? String, CheckoutErrorCode.storefrontPasswordRequired.rawValue) + XCTAssertEqual(eventBody["recoverable"] as? Bool, false) + } else { + XCTFail("Expected CheckoutConfigurationError but found different error") + } + } + + func testCheckoutDidFailEmitsInternalError() { + let mock = mockSendEvent(eventName: "error") + mock.startObserving() + + let error = CheckoutError.sdkError( + underlying: NSError(domain: "com.shopify", code: 1001, userInfo: [NSLocalizedDescriptionKey: "failed"]), + recoverable: true + ) + + mock.checkoutDidFail(error: error) + + XCTAssertTrue(mock.didSendEvent, "Event should have been sent when checkout fails") + + guard let eventBody = mock.eventBody as? [String: Any] else { + return XCTFail("Event body was not available or not in the correct format") + } + + if case .sdkError = error { + XCTAssertEqual(eventBody["__typename"] as? String, "InternalError") + XCTAssertEqual(eventBody["message"] as? String, "failed") + XCTAssertEqual(eventBody["recoverable"] as? Bool, true) + } else { + XCTFail("Expected InternalError but found different error") + } + } + + private func mockSendEvent(eventName: String) -> RCTShopifyCheckoutSheetKitMock { + let mock = RCTShopifyCheckoutSheetKitMock() + mock.eventName = eventName + return mock + } + + private func mockAsyncSendEvent(eventName: String) -> AsyncRCTShopifyCheckoutSheetKitMock { + let mock = AsyncRCTShopifyCheckoutSheetKitMock() + mock.eventName = eventName + return mock + } +} diff --git a/react-native/sample/ios/ReactNativeTests/EventSerializationTests.swift b/react-native/sample/ios/ReactNativeTests/EventSerializationTests.swift new file mode 100644 index 00000000..bcecbfa9 --- /dev/null +++ b/react-native/sample/ios/ReactNativeTests/EventSerializationTests.swift @@ -0,0 +1,63 @@ +/* + MIT License + + Copyright 2023 - Present, Shopify Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import Foundation +@testable import RNShopifyCheckoutSheetKit +import ShopifyCheckoutSheetKit +import XCTest + +@available(iOS 16.0, *) +class EventSerializationTests: XCTestCase { + + // MARK: - RenderState + + func testRenderStateSerialization_includesErrorReason() throws { + let serialized = ShopifyEventSerialization.serialize(renderState: .error(reason: "invariant_violation")) + XCTAssertEqual(serialized["state"], "error") + XCTAssertEqual(serialized["reason"], "invariant_violation") + } + + func testRenderStateSerialization_includesEmptyErrorReason() throws { + let serialized = ShopifyEventSerialization.serialize(renderState: .error(reason: "")) + XCTAssertEqual(serialized["state"], "error") + XCTAssertEqual(serialized["reason"], "") + } + + func testRenderStateSerialization_loadingAndRendered() throws { + let loading = ShopifyEventSerialization.serialize(renderState: .loading) + XCTAssertEqual(loading["state"], "loading") + XCTAssertNil(loading["reason"]) + + let rendered = ShopifyEventSerialization.serialize(renderState: .rendered) + XCTAssertEqual(rendered["state"], "rendered") + XCTAssertNil(rendered["reason"]) + } + + // MARK: - Click event + + func testClickEventSerialization() throws { + let url = URL(string: "https://shopify.dev/test")! + let serialized = ShopifyEventSerialization.serialize(clickEvent: url) + XCTAssertEqual(serialized["url"], url) + } +} diff --git a/react-native/sample/ios/ReactNativeTests/Info.plist b/react-native/sample/ios/ReactNativeTests/Info.plist new file mode 100644 index 00000000..ba72822e --- /dev/null +++ b/react-native/sample/ios/ReactNativeTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + diff --git a/react-native/sample/ios/ReactNativeTests/ShopifyCheckoutSheetKitTests.swift b/react-native/sample/ios/ReactNativeTests/ShopifyCheckoutSheetKitTests.swift new file mode 100644 index 00000000..1a246d0e --- /dev/null +++ b/react-native/sample/ios/ReactNativeTests/ShopifyCheckoutSheetKitTests.swift @@ -0,0 +1,524 @@ +/* + MIT License + + Copyright 2023 - Present, Shopify Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import Foundation +@testable import RNShopifyCheckoutSheetKit +@testable import ShopifyCheckoutSheetKit +import XCTest + +class ShopifyCheckoutSheetKitTests: XCTestCase { + private var shopifyCheckoutSheetKit: RCTShopifyCheckoutSheetKit! + + override func setUp() { + super.setUp() + shopifyCheckoutSheetKit = getShopifyCheckoutSheetKit() + resetShopifyCheckoutSheetKitDefaults() + } + + override func tearDown() { + shopifyCheckoutSheetKit = nil + super.tearDown() + } + + private func resetShopifyCheckoutSheetKitDefaults() { + ShopifyCheckoutSheetKit.configuration.preloading = Configuration.Preloading(enabled: true) + ShopifyCheckoutSheetKit.configuration.colorScheme = .automatic + ShopifyCheckoutSheetKit.configuration.closeButtonTintColor = nil + ShopifyCheckoutSheetKit.configuration.logLevel = LogLevel.error + } + + private func getShopifyCheckoutSheetKit() -> RCTShopifyCheckoutSheetKit { + return RCTShopifyCheckoutSheetKit() + } + + /// getConfig + func testReturnsDefaultConfig() { + // Call getConfig and capture the result + var result: [String: Any]? + shopifyCheckoutSheetKit.getConfig({ config in result = config as? [String: Any] }, reject: { _, _, _ in }) + + // Verify that getConfig returned the expected result + XCTAssertEqual(result?["preloading"] as? Bool, true) + XCTAssertEqual(result?["colorScheme"] as? String, "automatic") + } + + /// configure + func testConfigure() { + let configuration: [AnyHashable: Any] = [ + "preloading": true, + "colorScheme": "dark", + "colors": [ + "ios": [ + "tintColor": "#FF0000", + "backgroundColor": "#0000FF" + ] + ] + ] + + shopifyCheckoutSheetKit.setConfig(configuration) + + XCTAssertTrue(ShopifyCheckoutSheetKit.configuration.preloading.enabled) + XCTAssertEqual(ShopifyCheckoutSheetKit.configuration.colorScheme, .dark) + XCTAssertEqual(ShopifyCheckoutSheetKit.configuration.tintColor, UIColor(hex: "#FF0000")) + XCTAssertEqual(ShopifyCheckoutSheetKit.configuration.backgroundColor, UIColor(hex: "#0000FF")) + } + + func testConfigureWithPartialConfig() { + let configuration: [AnyHashable: Any] = [ + "preloading": false + ] + + shopifyCheckoutSheetKit.setConfig(configuration) + + XCTAssertFalse(ShopifyCheckoutSheetKit.configuration.preloading.enabled) + } + + func testConfigureWithInvalidColors() { + let configuration: [AnyHashable: Any] = [ + "colors": [ + "ios": [ + "tintColor": "invalid" + ] + ] + ] + + let defaultColorFallback = UIColor(red: 0, green: 0, blue: 0, alpha: 1) + shopifyCheckoutSheetKit.setConfig(configuration) + + XCTAssertEqual(ShopifyCheckoutSheetKit.configuration.tintColor, defaultColorFallback) + } + + func testConfigureWithCloseButtonColor() { + let configuration: [AnyHashable: Any] = [ + "colors": [ + "ios": [ + "closeButtonColor": "#FF0000" + ] + ] + ] + + shopifyCheckoutSheetKit.setConfig(configuration) + + XCTAssertEqual(ShopifyCheckoutSheetKit.configuration.closeButtonTintColor, UIColor(hex: "#FF0000")) + } + + func testConfigureWithInvalidCloseButtonColor() { + let configuration: [AnyHashable: Any] = [ + "colors": [ + "ios": [ + "closeButtonColor": "invalid" + ] + ] + ] + + let defaultColorFallback = UIColor(red: 0, green: 0, blue: 0, alpha: 1) + shopifyCheckoutSheetKit.setConfig(configuration) + + XCTAssertEqual(ShopifyCheckoutSheetKit.configuration.closeButtonTintColor, defaultColorFallback) + } + + func testConfigureWithoutCloseButtonColor() { + let configuration: [AnyHashable: Any] = [ + "colors": [ + "ios": [ + "tintColor": "#FF0000" + ] + ] + ] + + shopifyCheckoutSheetKit.setConfig(configuration) + + // closeButtonTintColor should remain nil when not specified (uses system default) + XCTAssertNil(ShopifyCheckoutSheetKit.configuration.closeButtonTintColor) + } + + func testGetConfigIncludesCloseButtonColor() { + // Set a close button color + let configuration: [AnyHashable: Any] = [ + "colors": [ + "ios": [ + "closeButtonColor": "#00FF00" + ] + ] + ] + shopifyCheckoutSheetKit.setConfig(configuration) + + // Call getConfig and capture the result + var result: [String: Any]? + shopifyCheckoutSheetKit.getConfig({ config in result = config as? [String: Any] }, reject: { _, _, _ in }) + + // Verify that getConfig returned the close button color + XCTAssertNotNil(result?["closeButtonColor"]) + let returnedColor = result?["closeButtonColor"] as? UIColor + XCTAssertEqual(returnedColor, UIColor(hex: "#00FF00")) + } + + func testConfigureWithLogLevelDebug() { + let configuration: [AnyHashable: Any] = [ + "logLevel": "debug" + ] + + shopifyCheckoutSheetKit.setConfig(configuration) + + XCTAssertEqual(ShopifyCheckoutSheetKit.configuration.logLevel, LogLevel.debug) + } + + func testConfigureWithLogLevelError() { + let configuration: [AnyHashable: Any] = [ + "logLevel": "error" + ] + + shopifyCheckoutSheetKit.setConfig(configuration) + + XCTAssertEqual(ShopifyCheckoutSheetKit.configuration.logLevel, LogLevel.error) + } + + func testConfigureWithLogLevelNone() { + let configuration: [AnyHashable: Any] = [ + "logLevel": "none" + ] + + shopifyCheckoutSheetKit.setConfig(configuration) + + XCTAssertEqual(ShopifyCheckoutSheetKit.configuration.logLevel, LogLevel.none) + } + + func testConfigureWithInvalidLogLevelDefaultsToError() { + let configuration: [AnyHashable: Any] = [ + "logLevel": "invalid" + ] + + shopifyCheckoutSheetKit.setConfig(configuration) + + XCTAssertEqual(ShopifyCheckoutSheetKit.configuration.logLevel, LogLevel.error) + } + + func testLogLevelHandlesUppercaseDebug() { + let configuration: [AnyHashable: Any] = [ + "logLevel": "DEBUG" + ] + + shopifyCheckoutSheetKit.setConfig(configuration) + + XCTAssertEqual(ShopifyCheckoutSheetKit.configuration.logLevel, LogLevel.debug) + } + + func testLogLevelHandlesMixedCaseDebug() { + let configuration: [AnyHashable: Any] = [ + "logLevel": "Debug" + ] + + shopifyCheckoutSheetKit.setConfig(configuration) + + XCTAssertEqual(ShopifyCheckoutSheetKit.configuration.logLevel, LogLevel.debug) + } + + func testLogLevelHandlesUppercaseError() { + let configuration: [AnyHashable: Any] = [ + "logLevel": "ERROR" + ] + + shopifyCheckoutSheetKit.setConfig(configuration) + + XCTAssertEqual(ShopifyCheckoutSheetKit.configuration.logLevel, LogLevel.error) + } + + func testSetConfigWithoutLogLevelDefaultsToError() { + let configuration: [AnyHashable: Any] = [ + "preloading": true + ] + + shopifyCheckoutSheetKit.setConfig(configuration) + + XCTAssertEqual(ShopifyCheckoutSheetKit.configuration.logLevel, LogLevel.error) + } + + func testGetConfigIncludesLogLevel() { + let configuration: [AnyHashable: Any] = [ + "logLevel": "debug" + ] + shopifyCheckoutSheetKit.setConfig(configuration) + + var result: [String: Any]? + shopifyCheckoutSheetKit.getConfig({ config in result = config as? [String: Any] }, reject: { _, _, _ in }) + + XCTAssertEqual(result?["logLevel"] as? String, "debug") + } + + func testGetConfigReturnsDefaultLogLevel() { + var result: [String: Any]? + shopifyCheckoutSheetKit.getConfig({ config in result = config as? [String: Any] }, reject: { _, _, _ in }) + + XCTAssertEqual(result?["logLevel"] as? String, "error") + } + + func testGetConfigReturnsDebugForDebugLogLevel() { + let configuration: [AnyHashable: Any] = [ + "logLevel": "debug" + ] + shopifyCheckoutSheetKit.setConfig(configuration) + + var result: [String: Any]? + shopifyCheckoutSheetKit.getConfig({ config in result = config as? [String: Any] }, reject: { _, _, _ in }) + + XCTAssertEqual(result?["logLevel"] as? String, "debug") + } + + func testGetConfigReturnsErrorForErrorLogLevel() { + let configuration: [AnyHashable: Any] = [ + "logLevel": "error" + ] + shopifyCheckoutSheetKit.setConfig(configuration) + + var result: [String: Any]? + shopifyCheckoutSheetKit.getConfig({ config in result = config as? [String: Any] }, reject: { _, _, _ in }) + + XCTAssertEqual(result?["logLevel"] as? String, "error") + } + + func testGetConfigReturnsErrorForNoneLogLevel() { + let configuration: [AnyHashable: Any] = [ + "logLevel": "none" + ] + shopifyCheckoutSheetKit.setConfig(configuration) + + var result: [String: Any]? + shopifyCheckoutSheetKit.getConfig({ config in result = config as? [String: Any] }, reject: { _, _, _ in }) + + XCTAssertEqual(result?["logLevel"] as? String, "error") + } + + func testGetConfigReturnsErrorForInvalidLogLevel() { + let configuration: [AnyHashable: Any] = [ + "logLevel": "invalid" + ] + shopifyCheckoutSheetKit.setConfig(configuration) + + var result: [String: Any]? + shopifyCheckoutSheetKit.getConfig({ config in result = config as? [String: Any] }, reject: { _, _, _ in }) + + XCTAssertEqual(result?["logLevel"] as? String, "error") + } + + /// checkoutDidComplete + func testCheckoutDidCompleteSendsEvent() { + let event = CheckoutCompletedEvent( + orderDetails: CheckoutCompletedEvent.OrderDetails( + billingAddress: CheckoutCompletedEvent.Address( + address1: "650 King Street", + address2: nil, + city: "Toronto", + countryCode: "CA", + firstName: "Evelyn", + lastName: "Hartley", + name: "Shopify", + phone: nil, + postalCode: nil, + referenceId: nil, + zoneCode: "ON" + ), + cart: CheckoutCompletedEvent.CartInfo( + lines: [], + price: CheckoutCompletedEvent.Price( + discounts: nil, + shipping: CheckoutCompletedEvent.Money(amount: nil, currencyCode: nil), + subtotal: CheckoutCompletedEvent.Money(amount: nil, currencyCode: nil), + taxes: CheckoutCompletedEvent.Money(amount: nil, currencyCode: nil), + total: CheckoutCompletedEvent.Money(amount: nil, currencyCode: nil) + ), + token: "token" + ), + deliveries: nil, + email: "test@shopify.com", + id: "test-order-id", + paymentMethods: nil, + phone: nil + ) + ) + let mock = mockSendEvent(eventName: "completed") + + mock.startObserving() + mock.checkoutDidComplete(event: event) + + XCTAssertTrue(mock.didSendEvent) + if let eventBody = mock.eventBody as? CheckoutCompletedEvent { + XCTAssertEqual(eventBody.orderDetails.id, "test-order-id") + XCTAssertEqual(eventBody.orderDetails.billingAddress?.address1, "650 King Street") + XCTAssertEqual(eventBody.orderDetails.billingAddress?.name, "Shopify") + XCTAssertEqual(eventBody.orderDetails.email, "test@shopify.com") + XCTAssertEqual(eventBody.orderDetails.cart.token, "token") + } + } + + /// checkoutDidCancel + func testCheckoutDidCancelSendsEvent() { + let mock = mockAsyncSendEvent(eventName: "close") + + let expectation = self.expectation(description: "CheckoutDidCancel") + + mock.sendEventImplementation = { name, _ in + if name == "close" { + mock.didSendEvent = true + expectation.fulfill() + } + } + + mock.checkoutSheet = MockCheckoutSheet() + mock.startObserving() + mock.checkoutDidCancel() + + // Wait for the expectation to be fulfilled + waitForExpectations(timeout: 1, handler: nil) + + XCTAssertTrue(mock.didSendEvent) + + // swiftlint:disable:next force_cast + XCTAssertTrue((mock.checkoutSheet as! MockCheckoutSheet).dismissWasCalled) + } + + /// checkoutDidEmitWebPixelEvent + func testCheckoutDidEmitStandardWebPixelEvent() { + let mock = mockSendEvent(eventName: "pixel") + + let context = Context( + document: WebPixelsDocument( + characterSet: "utf8", + location: nil, + referrer: "test", + title: nil + ), + navigator: nil, + window: nil + ) + let event = StandardEvent(context: context, id: "test", name: "test", timestamp: "test", data: nil) + let pixelEvent = PixelEvent.standardEvent(event) + + mock.startObserving() + mock.checkoutDidEmitWebPixelEvent(event: pixelEvent) + + XCTAssertTrue(mock.didSendEvent) + if let eventBody = mock.eventBody as? [String: Any] { + XCTAssertEqual(eventBody["type"] as? String, "STANDARD") + XCTAssertEqual(eventBody["id"] as? String, "test") + XCTAssertEqual(eventBody["name"] as? String, "test") + XCTAssertEqual(eventBody["timestamp"] as? String, "test") + // swiftlint:disable:next force_cast + XCTAssertEqual(eventBody["context"] as! [String: [String: String?]], [ + "document": [ + "characterSet": "utf8", + "referrer": "test" + ] + ]) + } else { + XCTFail("Failed to parse standard event") + } + } + + func testCheckoutDidEmitCustomWebPixelEvent() { + let mock = mockSendEvent(eventName: "pixel") + + let context = Context( + document: WebPixelsDocument( + characterSet: "utf8", + location: nil, + referrer: "test", + title: nil + ), + navigator: nil, + window: nil + ) + let customData = "{\"nestedData\": {\"someAttribute\": \"456\"}}" + let event = CustomEvent(context: context, customData: customData, id: "test", name: "test", timestamp: "test") + let pixelEvent = PixelEvent.customEvent(event) + + mock.startObserving() + mock.checkoutDidEmitWebPixelEvent(event: pixelEvent) + + XCTAssertTrue(mock.didSendEvent) + if let eventBody = mock.eventBody as? [String: Any] { + XCTAssertEqual(eventBody["type"] as? String, "CUSTOM") + XCTAssertEqual(eventBody["id"] as? String, "test") + XCTAssertEqual(eventBody["name"] as? String, "test") + XCTAssertEqual(eventBody["timestamp"] as? String, "test") + // swiftlint:disable:next force_cast + XCTAssertEqual(eventBody["context"] as! [String: [String: String?]], [ + "document": [ + "characterSet": "utf8", + "referrer": "test" + ] + ]) + // swiftlint:disable:next force_cast + XCTAssertEqual(eventBody["customData"] as! [String: [String: String]], [ + "nestedData": [ + "someAttribute": "456" + ] + ]) + } else { + XCTFail("Failed to parse custom event") + } + } + + private func mockSendEvent(eventName: String) -> RCTShopifyCheckoutSheetKitMock { + let mock = RCTShopifyCheckoutSheetKitMock() + mock.eventName = eventName + return mock + } + + private func mockAsyncSendEvent(eventName: String) -> AsyncRCTShopifyCheckoutSheetKitMock { + let mock = AsyncRCTShopifyCheckoutSheetKitMock() + mock.eventName = eventName + return mock + } +} + +class RCTShopifyCheckoutSheetKitMock: RCTShopifyCheckoutSheetKit { + var didSendEvent = false + var eventName: String? + var eventBody: Any! + + override func sendEvent(withName name: String!, body: Any!) { + if name == eventName { + didSendEvent = true + eventBody = body + } + } +} + +class AsyncRCTShopifyCheckoutSheetKitMock: RCTShopifyCheckoutSheetKit { + var didSendEvent = false + var eventName: String? + var sendEventImplementation: ((String?, Any?) -> Void)? + + override func sendEvent(withName name: String!, body: Any!) { + sendEventImplementation?(name, body) + } +} + +class MockCheckoutSheet: UIViewController { + var dismissWasCalled = false + + override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { + dismissWasCalled = true + super.dismiss(animated: flag, completion: completion) + } +} diff --git a/react-native/sample/package.json b/react-native/sample/package.json new file mode 100644 index 00000000..0a793a38 --- /dev/null +++ b/react-native/sample/package.json @@ -0,0 +1,84 @@ +{ + "name": "sample", + "version": "0.6.0", + "private": true, + "scripts": { + "android": "sh ./scripts/android", + "clean": "rm -rf node_modules ios/build ios/pods vendor", + "clean:android": "(cd android && ./gradlew clean)", + "build:android": "sh ./scripts/build_android", + "release:android": "sh ./scripts/release_android", + "build:ios": "sh ./scripts/build_ios", + "lint": "pnpm run typecheck && eslint .", + "ios": "react-native run-ios", + "start": "react-native start -- --reset-cache", + "typecheck": "tsc --noEmit", + "test:ios": "sh ./scripts/test_ios", + "test:android": "sh ./scripts/test_android" + }, + "dependencies": { + "@apollo/client": "^3.13.9", + "@react-navigation/bottom-tabs": "^7.4.6", + "@react-navigation/native": "^7.1.17", + "@react-navigation/native-stack": "^7.3.25", + "@react-navigation/stack": "^7.4.8", + "@shopify/checkout-sheet-kit": "workspace:*", + "graphql": "^16.8.2", + "jotai": "^2.13.1", + "react-native-config": "1.5.6", + "react-native-dotenv": "^3.4.11", + "react-native-encrypted-storage": "^4.0.3", + "react-native-nitro-modules": "^0.33.7", + "react-native-quick-base64": "^2.2.2", + "react-native-quick-crypto": "1.0.9", + "react-native-reanimated": "3.18.0", + "react-native-safe-area-context": "^5.6.1", + "react-native-screens": "4.16.0", + "react-native-vector-icons": "^10.3.0", + "react-native-webview": "^13.16.0" + }, + "peerDependencies": { + "@react-native/babel-preset": "*", + "@react-native/metro-config": "*", + "@types/react-native": "*", + "react": "*", + "react-native": "*" + }, + "devDependencies": { + "@babel/core": "^7.20.0", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.18.6", + "@babel/plugin-proposal-optional-chaining": "^7.21.0", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.23.3", + "@babel/plugin-transform-template-literals": "^7.23.3", + "@babel/preset-env": "^7.20.0", + "@babel/runtime": "^7.27.6", + "@react-native-masked-view/masked-view": "^0.3.2", + "@types/react-native-vector-icons": "^6.4.18", + "@types/setimmediate": "^1", + "babel-plugin-module-resolver": "^5.0.0", + "setimmediate": "^1.0.5" + }, + "engines": { + "node": ">= 18" + }, + "eslintConfig": { + "root": true, + "extends": "@react-native", + "rules": { + "no-console": "off", + "@typescript-eslint/no-shadow": "off" + }, + "overrides": [ + { + "files": [ + "*.ts", + "*.tsx" + ], + "rules": { + "@typescript-eslint/consistent-type-imports": "error" + } + } + ] + } +} diff --git a/react-native/sample/react-native.config.js b/react-native/sample/react-native.config.js new file mode 100644 index 00000000..93429c7b --- /dev/null +++ b/react-native/sample/react-native.config.js @@ -0,0 +1,60 @@ +const path = require('path'); + +const root = path.resolve(__dirname, '..'); + +const resolvePackageRoot = packageName => + path.dirname( + require.resolve(`${packageName}/package.json`, { + paths: [root, __dirname], + }), + ); + +module.exports = { + dependencies: { + '@react-native-masked-view/masked-view': { + root: resolvePackageRoot('@react-native-masked-view/masked-view'), + }, + react: { + root: resolvePackageRoot('react'), + }, + 'react-native': { + root: resolvePackageRoot('react-native'), + }, + 'react-native-config': { + root: resolvePackageRoot('react-native-config'), + }, + 'react-native-encrypted-storage': { + root: resolvePackageRoot('react-native-encrypted-storage'), + }, + 'react-native-gesture-handler': { + root: resolvePackageRoot('react-native-gesture-handler'), + }, + 'react-native-nitro-modules': { + root: resolvePackageRoot('react-native-nitro-modules'), + }, + 'react-native-quick-base64': { + root: resolvePackageRoot('react-native-quick-base64'), + }, + 'react-native-quick-crypto': { + root: resolvePackageRoot('react-native-quick-crypto'), + }, + 'react-native-reanimated': { + root: resolvePackageRoot('react-native-reanimated'), + }, + 'react-native-safe-area-context': { + root: resolvePackageRoot('react-native-safe-area-context'), + }, + 'react-native-screens': { + root: resolvePackageRoot('react-native-screens'), + }, + 'react-native-vector-icons': { + root: resolvePackageRoot('react-native-vector-icons'), + }, + 'react-native-webview': { + root: resolvePackageRoot('react-native-webview'), + }, + '@shopify/checkout-sheet-kit': { + root: path.resolve(root, 'modules', '@shopify/checkout-sheet-kit'), + }, + }, +}; diff --git a/react-native/sample/scripts/android b/react-native/sample/scripts/android new file mode 100755 index 00000000..44b610d6 --- /dev/null +++ b/react-native/sample/scripts/android @@ -0,0 +1,8 @@ +#!/usr/bin/env sh + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +. "$SCRIPT_DIR/android_sccache" + +exec react-native run-android "$@" diff --git a/react-native/sample/scripts/android_sccache b/react-native/sample/scripts/android_sccache new file mode 100755 index 00000000..ba02b4a9 --- /dev/null +++ b/react-native/sample/scripts/android_sccache @@ -0,0 +1,22 @@ +#!/usr/bin/env sh + +# React Native's Android CMake files discover and invoke a command named +# `ccache`. Put an sccache-backed compatibility command first on PATH. +if [ "${SCCACHE:-}" = "false" ]; then + if command -v sccache >/dev/null 2>&1; then + sccache --stop-server 2>/dev/null || true + fi + return 0 2>/dev/null || exit 0 +fi + +if command -v sccache >/dev/null 2>&1; then + ANDROID_SCCACHE_SHIM_DIR="${TMPDIR:-/tmp}/checkout-sheet-kit-react-native-sccache" + mkdir -p "$ANDROID_SCCACHE_SHIM_DIR" + cat > "$ANDROID_SCCACHE_SHIM_DIR/ccache" <<'EOF' +#!/usr/bin/env sh +exec sccache "$@" +EOF + chmod +x "$ANDROID_SCCACHE_SHIM_DIR/ccache" + export PATH="$ANDROID_SCCACHE_SHIM_DIR:$PATH" + export RUSTC_WRAPPER="${RUSTC_WRAPPER:-sccache}" +fi diff --git a/react-native/sample/scripts/build_android b/react-native/sample/scripts/build_android new file mode 100755 index 00000000..27c536cb --- /dev/null +++ b/react-native/sample/scripts/build_android @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -ex + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +. "$SCRIPT_DIR/android_sccache" + +cd android + +./gradlew assembleDebug --no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a diff --git a/react-native/sample/scripts/build_ios b/react-native/sample/scripts/build_ios new file mode 100755 index 00000000..e5cd5a16 --- /dev/null +++ b/react-native/sample/scripts/build_ios @@ -0,0 +1,36 @@ + #!/usr/bin/env bash + +set -ex +set -eo pipefail + +# Add sccache for faster compilation outside of CI environments +# Can be disabled with SCCACHE=false for debugging or matching CI behavior locally +if [ "$CI" = "true" ] || [ "$SCCACHE" = "false" ]; then + # CI detected or sccache explicitly disabled - skipping sccache for clean builds + if command -v sccache >/dev/null 2>&1; then + sccache --stop-server 2>/dev/null || true + fi +else + if command -v sccache >/dev/null 2>&1; then + export RUSTC_WRAPPER=sccache + export CC="sccache clang" + export CXX="sccache clang++" + fi +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/simulator" +dest="$(get_sim_destination)" + +cd ios + +xcodebuild clean build \ + -workspace ReactNative.xcworkspace \ + -scheme ReactNative \ + -sdk iphonesimulator \ + -destination "$dest" \ + -skipPackagePluginValidation \ + GCC_PRECOMPILE_PREFIX_HEADER=YES \ + ASSETCATALOG_COMPILER_OPTIMIZATION=time \ + COMPILER_INDEX_STORE_ENABLE=NO \ +| xcbeautify diff --git a/react-native/sample/scripts/release_android b/react-native/sample/scripts/release_android new file mode 100755 index 00000000..e3f21863 --- /dev/null +++ b/react-native/sample/scripts/release_android @@ -0,0 +1,46 @@ +#!/usr/bin/env bash + +# Working directory is "sample" when running "pnpm sample release:android" from root. + +# Exit immediately if a command exits with a non-zero status +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +. "$SCRIPT_DIR/android_sccache" + +# Print each command as it is executed +set -x + +mkdir -p android/app/src/main/assets +npx react-native bundle --platform android \ + --dev false \ + --entry-file index.js \ + --bundle-output android/app/src/main/assets/index.android.bundle \ + +# Generate release build (.aab file) +npx react-native build-android --mode=release + +# Generate APK (for testing) +cd android +./gradlew clean assembleRelease + +# Stop printing commands +set +x + +cat << EOF + + +Run "cd sample && npx react-native run-android --mode release" to test the release build. + +Output files: + - sample/android/app/build/outputs/bundle/release/app-release.aab (Upload to Play Store) + - sample/android/app/build/outputs/apk/release/app-release.apk (For testing) + +To install the release build: + - Ensure an emulator or device is connected + - Uninstall existing version: adb uninstall com.shopify.checkoutkitreactnative + - Install new version: adb install android/app/build/outputs/apk/release/app-release.apk + +To observe the production logs for your emulator/device: + - Run "adb logcat" +EOF diff --git a/react-native/sample/scripts/simulator b/react-native/sample/scripts/simulator new file mode 100644 index 00000000..a84a288b --- /dev/null +++ b/react-native/sample/scripts/simulator @@ -0,0 +1,32 @@ +#!/usr/bin/env bash + +# Determine an iOS Simulator destination string for xcodebuild. +get_sim_destination() { + if [[ -n "$CURRENT_SIMULATOR_UUID" ]]; then + echo "id=$CURRENT_SIMULATOR_UUID" + return + fi + + # Prefer a currently booted iOS simulator (iPhone or iPad) + local udid + udid="$(xcrun simctl list devices booted \ + | grep -E 'iPhone|iPad' \ + | sed -n -E 's/.*\(([0-9A-Fa-f-]{36})\).*/\1/p' \ + | head -n1)" + + # If none booted, pick the first available iOS simulator (iPhone or iPad) + if [[ -z "$udid" ]]; then + udid="$(xcrun simctl list devices available \ + | grep -E 'iPhone|iPad' \ + | sed -n -E 's/.*\(([0-9A-Fa-f-]{36})\).*/\1/p' \ + | head -n1)" + fi + + if [[ -n "$udid" ]]; then + echo "id=$udid" + else + echo "platform=iOS Simulator,name=iPhone 15 Pro" + fi +} + + diff --git a/react-native/sample/scripts/test_android b/react-native/sample/scripts/test_android new file mode 100755 index 00000000..a75e5496 --- /dev/null +++ b/react-native/sample/scripts/test_android @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -e + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +. "$SCRIPT_DIR/android_sccache" + +cd android + +./gradlew clean generateAndroidManifestFromTemplate test --no-daemon --console=plain -Dorg.gradle.workers.max=1 -PreactNativeArchitectures=arm64-v8a diff --git a/react-native/sample/scripts/test_ios b/react-native/sample/scripts/test_ios new file mode 100755 index 00000000..fbca71b9 --- /dev/null +++ b/react-native/sample/scripts/test_ios @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -e -o pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/simulator" +dest="$(get_sim_destination)" + +cd ios + +xcodebuild test \ + -workspace ReactNative.xcworkspace \ + -scheme ReactNative \ + -destination "$dest" \ + -skipPackagePluginValidation \ + -sdk iphonesimulator \ + ASSETCATALOG_COMPILER_OPTIMIZATION=time \ + COMPILER_INDEX_STORE_ENABLE=NO \ +| xcbeautify --renderer github-actions diff --git a/react-native/sample/src/App.tsx b/react-native/sample/src/App.tsx new file mode 100644 index 00000000..b67bd33d --- /dev/null +++ b/react-native/sample/src/App.tsx @@ -0,0 +1,585 @@ +/* +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import type {PropsWithChildren, ReactNode} from 'react'; +import React, {useCallback, useEffect, useMemo, useState} from 'react'; +import {Appearance, Linking, Pressable, StatusBar} from 'react-native'; +import { + NavigationContainer, + useNavigation, + type NavigationProp, +} from '@react-navigation/native'; +import {createBottomTabNavigator} from '@react-navigation/bottom-tabs'; +import {createNativeStackNavigator} from '@react-navigation/native-stack'; +import {ApolloClient, InMemoryCache, ApolloProvider} from '@apollo/client'; +import Icon from 'react-native-vector-icons/Entypo'; + +import CatalogScreen from './screens/CatalogScreen'; +import SettingsScreen from './screens/SettingsScreen'; +import AccountScreen from './screens/AccountScreen'; +import LoginScreen from './screens/LoginScreen'; + +import type {Configuration, Features} from '@shopify/checkout-sheet-kit'; +import { + ApplePayContactField, + ColorScheme, + LogLevel, + ShopifyCheckoutSheetProvider, + useShopifyCheckoutSheet, +} from '@shopify/checkout-sheet-kit'; +import type { + CheckoutCompletedEvent, + CheckoutException, + PixelEvent, +} from '@shopify/checkout-sheet-kit'; +import {ConfigProvider, useConfig} from './context/Config'; +import {BuyerIdentityMode} from './auth/types'; +import { + ThemeProvider, + darkColors, + getColors, + getNavigationTheme, + lightColors, + useTheme, +} from './context/Theme'; +import {CartProvider, useCart} from './context/Cart'; +import {useAuth} from './context/Auth'; +import CartScreen from './screens/CartScreen'; +import ProductDetailsScreen from './screens/ProductDetailsScreen'; +import type {ProductVariant, ShopifyProduct} from '../@types'; +import ErrorBoundary from './ErrorBoundary'; +import env from 'react-native-config'; +import {createDebugLogger} from './utils'; +import {useShopifyEventHandlers} from './hooks/useCheckoutEventHandlers'; + +const log = createDebugLogger('ENV'); + +function quote(str: string | undefined) { + return `"${str}"`; +} + +console.groupCollapsed('ENV'); +log('STOREFRONT_DOMAIN:', quote(env.STOREFRONT_DOMAIN)); +log( + 'STOREFRONT_ACCESS_TOKEN:', + '*'.repeat(8) + env.STOREFRONT_ACCESS_TOKEN?.slice(-4), +); +log('STOREFRONT_VERSION:', quote(env.STOREFRONT_VERSION)); +log( + 'STOREFRONT_MERCHANT_IDENTIFIER:', + quote(env.STOREFRONT_MERCHANT_IDENTIFIER), +); +log('EMAIL:', quote(env.EMAIL)); +log('PHONE:', quote(env.PHONE)); +console.groupEnd(); + +export type RootStackParamList = { + Catalog: undefined; + CatalogScreen: undefined; + ProductDetails: {product: ShopifyProduct; variant?: ProductVariant}; + Cart: undefined; + CartModal: undefined; + Account: undefined; + Settings: undefined; +}; + +export type AccountStackParamList = { + AccountHome: undefined; + Login: undefined; +}; + +const Tab = createBottomTabNavigator(); +const Stack = createNativeStackNavigator(); +const AccountStack = createNativeStackNavigator(); + +export const cache = new InMemoryCache(); + +const client = new ApolloClient({ + uri: `https://${env.STOREFRONT_DOMAIN}/api/${env.STOREFRONT_VERSION}/graphql.json`, + cache, + headers: { + 'Content-Type': 'application/json', + 'X-Shopify-Storefront-Access-Token': env.STOREFRONT_ACCESS_TOKEN ?? '', + }, + connectToDevTools: __DEV__, +}); + +function AppWithTheme({children}: PropsWithChildren) { + const {colorScheme} = useTheme(); + + return ( + + {children} + + ); +} + +const createNavigationIcon = + (name: string) => + ({ + color, + size, + }: { + color: string; + size: number; + focused?: boolean; + }): ReactNode => { + return ; + }; + +// See https://reactnative.dev/docs/linking#get-the-deep-link for more information +const useInitialURL = (): {url: string | null} => { + const [url, setUrl] = useState(null); + + useEffect(() => { + const getUrlAsync = async () => { + // Get the deep link used to open the app + const initialUrl = await Linking.getInitialURL(); + + if (initialUrl !== url) { + setUrl(initialUrl); + } + }; + + getUrlAsync(); + }, [url]); + + return { + url, + }; +}; + +// This code is meant as example only. +class StorefrontURL { + readonly url: string; + + constructor(url: string) { + this.url = url; + } + + isThankYouPage(): boolean { + return /thank[-_]you/i.test(this.url); + } + + isCheckout(): boolean { + return this.url.includes('/checkout'); + } + + isCart() { + return this.url.includes('/cart'); + } +} + +const checkoutKitConfigDefaults: Configuration = { + logLevel: LogLevel.debug, + colorScheme: ColorScheme.dark, + preloading: true, + colors: { + ios: { + backgroundColor: '#f0f0e8', + tintColor: '#2d2a38', + }, + android: { + backgroundColor: '#f0f0e8', + progressIndicator: '#2d2a38', + headerBackgroundColor: '#f0f0e8', + headerTextColor: '#2d2a38', + }, + }, +}; + +function AppWithContext({children}: PropsWithChildren) { + const shopify = useShopifyCheckoutSheet(); + const eventHandlers = useShopifyEventHandlers(); + + useEffect(() => { + const close = shopify.addEventListener('close', () => { + eventHandlers.onCancel?.(); + }); + + const pixel = shopify.addEventListener('pixel', (event: PixelEvent) => { + eventHandlers.onWebPixelEvent?.(event); + }); + + const completed = shopify.addEventListener( + 'completed', + (event: CheckoutCompletedEvent) => { + eventHandlers.onComplete?.(event); + }, + ); + + const error = shopify.addEventListener( + 'error', + (error: CheckoutException) => { + eventHandlers.onFail?.(error); + }, + ); + + return () => { + pixel?.remove(); + completed?.remove(); + close?.remove(); + error?.remove(); + }; + }, [shopify, eventHandlers]); + + return ( + + + + {children} + + + ); +} + +function CatalogStack() { + return ( + ({ + headerBackTitle: 'Back', + // eslint-disable-next-line react/no-unstable-nested-components + headerRight: () => ( + + navigation.getParent()?.navigate('Catalog', {screen: 'CartModal'}) + } + /> + ), + })}> + + ({ + headerTitle: route.params.product.title, + headerShown: true, + headerBackVisible: true, + headerBackTitle: 'Back', + })} + /> + + + ); +} + +function CartIcon({onPress}: {onPress: () => void}) { + const theme = useTheme(); + + return ( + + + + ); +} + +function AccountStackScreen() { + return ( + + + + + ); +} + +function AppWithCheckoutKit({children}: PropsWithChildren) { + const {appConfig} = useConfig(); + const {isAuthenticated, customerEmail, getValidAccessToken} = useAuth(); + const [accessToken, setAccessToken] = useState(null); + + const fetchAccessToken = useCallback(async () => { + if ( + appConfig.buyerIdentityMode === BuyerIdentityMode.CustomerAccount && + isAuthenticated + ) { + const token = await getValidAccessToken(); + setAccessToken(token); + } else { + setAccessToken(null); + } + }, [appConfig.buyerIdentityMode, isAuthenticated, getValidAccessToken]); + + useEffect(() => { + fetchAccessToken(); + }, [fetchAccessToken]); + + const updatedColors = getColors( + appConfig.colorScheme, + Appearance.getColorScheme(), + ); + + const checkoutKitThemeConfig: Configuration = useMemo(() => { + if (appConfig.colorScheme === ColorScheme.automatic) { + return { + colorScheme: ColorScheme.automatic, + colors: { + ios: { + backgroundColor: updatedColors.webviewBackgroundColor, + tintColor: updatedColors.webViewProgressIndicator, + }, + android: { + light: { + backgroundColor: lightColors.webviewBackgroundColor, + progressIndicator: lightColors.webViewProgressIndicator, + headerBackgroundColor: lightColors.webviewBackgroundColor, + headerTextColor: lightColors.webviewHeaderTextColor, + closeButtonColor: lightColors.webviewCloseButtonColor, + }, + dark: { + backgroundColor: darkColors.webviewBackgroundColor, + progressIndicator: darkColors.webViewProgressIndicator, + headerBackgroundColor: darkColors.webviewBackgroundColor, + headerTextColor: darkColors.webviewHeaderTextColor, + closeButtonColor: darkColors.webviewCloseButtonColor, + }, + }, + }, + }; + } + + return { + colorScheme: appConfig.colorScheme, + colors: { + ios: { + backgroundColor: updatedColors.webviewBackgroundColor, + tintColor: updatedColors.webViewProgressIndicator, + closeButtonColor: updatedColors.webviewCloseButtonColor, + }, + android: { + backgroundColor: updatedColors.webviewBackgroundColor, + progressIndicator: updatedColors.webViewProgressIndicator, + headerBackgroundColor: updatedColors.webviewBackgroundColor, + headerTextColor: updatedColors.webviewHeaderTextColor, + closeButtonColor: updatedColors.webviewCloseButtonColor, + }, + }, + }; + }, [appConfig.colorScheme, updatedColors]); + + const checkoutKitConfig: Configuration = useMemo(() => { + return { + ...checkoutKitConfigDefaults, + ...checkoutKitThemeConfig, + colors: { + ...checkoutKitThemeConfig.colors, + ios: { + ...checkoutKitThemeConfig.colors?.ios, + ...checkoutKitConfigDefaults.colors?.ios, + }, + android: + appConfig.colorScheme === ColorScheme.automatic + ? checkoutKitThemeConfig.colors?.android + : { + ...checkoutKitThemeConfig.colors?.android, + ...checkoutKitConfigDefaults.colors?.android, + }, + }, + acceleratedCheckouts: { + storefrontDomain: env.STOREFRONT_DOMAIN!, + storefrontAccessToken: env.STOREFRONT_ACCESS_TOKEN!, + /** + * We're reading the customer email and phone number from the environment variables here, + * but in a real app you would derive these values from your backend. + */ + customer: + appConfig.buyerIdentityMode === BuyerIdentityMode.Hardcoded + ? { + email: env.EMAIL!, + phoneNumber: env.PHONE!, + } + : appConfig.buyerIdentityMode === + BuyerIdentityMode.CustomerAccount && isAuthenticated + ? { + email: customerEmail ?? undefined, + accessToken: accessToken ?? undefined, + } + : undefined, + wallets: { + applePay: { + contactFields: [ + ApplePayContactField.email, + ApplePayContactField.phone, + ], + merchantIdentifier: env.STOREFRONT_MERCHANT_IDENTIFIER!, + }, + }, + }, + } as Configuration; + }, [appConfig, checkoutKitThemeConfig, isAuthenticated, customerEmail, accessToken]); + + return ( + + {children} + + ); +} + +function AppWithNavigation(props: {children: React.ReactNode}) { + const {colorScheme, preference} = useTheme(); + return ( + + {props.children} + + ); +} + +function Routes() { + const {totalQuantity} = useCart(); + const navigation = useNavigation>(); + const {url: initialUrl} = useInitialURL(); + const shopify = useShopifyCheckoutSheet(); + + useEffect(() => { + async function handleUniversalLink(url: string) { + const storefrontUrl = new StorefrontURL(url); + + switch (true) { + // Checkout URLs + case storefrontUrl.isCheckout() && !storefrontUrl.isThankYouPage(): + shopify.present(url); + return; + // Cart URLs + case storefrontUrl.isCart(): + navigation.navigate('Cart'); + return; + } + + // Open everything else in a mobile browser + const canOpenUrl = await Linking.canOpenURL(url); + + if (canOpenUrl) { + await Linking.openURL(url); + } + } + + if (initialUrl) { + handleUniversalLink(initialUrl); + } + + // Subscribe to universal links + const subscription = Linking.addEventListener('url', ({url}) => { + handleUniversalLink(url); + }); + + return () => { + subscription.remove(); + }; + }, [initialUrl, shopify, navigation]); + + return ( + + + 0 ? totalQuantity : undefined, + }} + /> + + + + ); +} + +const checkoutKitFeatures: Partial = { + handleGeolocationRequests: true, +}; + +function App() { + return ( + + + + + + + + + + + + + + ); +} + +export default App; diff --git a/react-native/sample/src/ErrorBoundary.tsx b/react-native/sample/src/ErrorBoundary.tsx new file mode 100644 index 00000000..1e6a5e9f --- /dev/null +++ b/react-native/sample/src/ErrorBoundary.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import {View, Text} from 'react-native'; + +interface State { + hasError: boolean; + error: Error | null; +} + +class ErrorBoundary extends React.Component { + state: State = {hasError: false, error: null}; + + static getDerivedStateFromError(error: Error) { + return {hasError: true, error}; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + console.log('[ErrorBoundary]', error, errorInfo); + } + + render() { + if (this.state.hasError) { + return ( + + Something went wrong. + + ); + } + + return this.props.children; + } +} + +export default ErrorBoundary; diff --git a/react-native/sample/src/auth/__tests__/CustomerAccountManager.test.ts b/react-native/sample/src/auth/__tests__/CustomerAccountManager.test.ts new file mode 100644 index 00000000..b80744e2 --- /dev/null +++ b/react-native/sample/src/auth/__tests__/CustomerAccountManager.test.ts @@ -0,0 +1,196 @@ +import {getDefaultStore} from 'jotai'; +import { + CustomerAccountManager, + customerAccountManager, +} from '../customerAccountManager'; + +const SHOP_ID = 'test-shop-123'; +const CLIENT_ID = 'test-client-456'; + +function createTokenResponseBody(overrides: Record = {}) { + return JSON.stringify({ + access_token: 'access-token-123', + refresh_token: 'refresh-token-456', + expires_in: 3600, + id_token: buildIdToken({email: 'test@example.com'}), + token_type: 'bearer', + ...overrides, + }); +} + +function buildIdToken(payload: Record): string { + const header = btoa(JSON.stringify({alg: 'RS256'})); + const body = btoa(JSON.stringify(payload)); + return `${header}.${body}.signature`; +} + +beforeEach(() => { + jest.restoreAllMocks(); + global.fetch = jest.fn(); + const EncryptedStorage = + require('react-native-encrypted-storage').default; + EncryptedStorage.clear(); +}); + +describe('CustomerAccountManager', () => { + describe('static getters', () => { + it('returns the correct redirectUri', () => { + expect(CustomerAccountManager.redirectUri).toBe( + `shop.${SHOP_ID}.app://callback`, + ); + }); + + it('returns the correct callbackScheme', () => { + expect(CustomerAccountManager.callbackScheme).toBe( + `shop.${SHOP_ID}.app`, + ); + }); + }); + + describe('buildAuthorizationURL', () => { + it('returns a well-formed authorization URL', () => { + const manager = new CustomerAccountManager(); + const url = manager.buildAuthorizationURL(); + + expect(url).toContain( + `https://shopify.com/authentication/${SHOP_ID}/oauth/authorize`, + ); + + const parsed = new URL(url); + expect(parsed.searchParams.get('client_id')).toBe(CLIENT_ID); + expect(parsed.searchParams.get('redirect_uri')).toBe( + `shop.${SHOP_ID}.app://callback`, + ); + expect(parsed.searchParams.get('response_type')).toBe('code'); + expect(parsed.searchParams.get('scope')).toBe( + 'openid email customer-account-api:full', + ); + expect(parsed.searchParams.get('code_challenge_method')).toBe('S256'); + expect(parsed.searchParams.get('code_challenge')).toBeTruthy(); + expect(parsed.searchParams.get('state')).toBeTruthy(); + }); + }); + + describe('handleAuthCallback', () => { + it('exchanges code for tokens and updates session atom', async () => { + const manager = new CustomerAccountManager(); + const store = getDefaultStore(); + + const url = manager.buildAuthorizationURL(); + const parsed = new URL(url); + const state = parsed.searchParams.get('state')!; + + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => JSON.parse(createTokenResponseBody()), + }); + + await manager.handleAuthCallback('test-code', state); + + const session = store.get(manager.sessionAtom); + expect(session.isAuthenticated).toBe(true); + expect(session.email).toBe('test@example.com'); + expect(session.tokenExpiresAt).toBeGreaterThan(Date.now()); + }); + + it('throws on state mismatch', async () => { + const manager = new CustomerAccountManager(); + manager.buildAuthorizationURL(); + + await expect( + manager.handleAuthCallback('test-code', 'wrong-state'), + ).rejects.toThrow('Invalid state parameter'); + }); + }); + + describe('logout', () => { + it('resets session atom to unauthenticated', async () => { + const manager = new CustomerAccountManager(); + const store = getDefaultStore(); + + const url = manager.buildAuthorizationURL(); + const parsed = new URL(url); + const state = parsed.searchParams.get('state')!; + + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => JSON.parse(createTokenResponseBody()), + }); + + await manager.handleAuthCallback('test-code', state); + + const sessionBefore = store.get(manager.sessionAtom); + expect(sessionBefore.isAuthenticated).toBe(true); + + (global.fetch as jest.Mock).mockResolvedValueOnce({ok: true}); + + await manager.logout(); + + const sessionAfter = store.get(manager.sessionAtom); + expect(sessionAfter.isAuthenticated).toBe(false); + expect(sessionAfter.email).toBeNull(); + expect(sessionAfter.tokenExpiresAt).toBeNull(); + }); + }); + + describe('getValidAccessToken', () => { + it('returns the access token when session is valid', async () => { + const manager = new CustomerAccountManager(); + + const url = manager.buildAuthorizationURL(); + const parsed = new URL(url); + const state = parsed.searchParams.get('state')!; + + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => JSON.parse(createTokenResponseBody()), + }); + + await manager.handleAuthCallback('test-code', state); + + const token = await manager.getValidAccessToken(); + expect(token).toBe('access-token-123'); + }); + + it('returns null when no tokens are stored', async () => { + const manager = new CustomerAccountManager(); + const token = await manager.getValidAccessToken(); + expect(token).toBeNull(); + }); + + it('refreshes the token when expiring soon', async () => { + const manager = new CustomerAccountManager(); + + const url = manager.buildAuthorizationURL(); + const parsed = new URL(url); + const state = parsed.searchParams.get('state')!; + + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => + JSON.parse(createTokenResponseBody({expires_in: 60})), + }); + + await manager.handleAuthCallback('test-code', state); + + (global.fetch as jest.Mock).mockResolvedValueOnce({ + ok: true, + json: async () => + JSON.parse( + createTokenResponseBody({ + access_token: 'refreshed-token-789', + }), + ), + }); + + const token = await manager.getValidAccessToken(); + expect(token).toBe('refreshed-token-789'); + }); + }); + + describe('singleton export', () => { + it('exports a singleton instance', () => { + expect(customerAccountManager).toBeInstanceOf(CustomerAccountManager); + }); + }); +}); diff --git a/react-native/sample/src/auth/__tests__/PKCE.test.ts b/react-native/sample/src/auth/__tests__/PKCE.test.ts new file mode 100644 index 00000000..51fe8f4b --- /dev/null +++ b/react-native/sample/src/auth/__tests__/PKCE.test.ts @@ -0,0 +1,47 @@ +import {PKCE} from '../pkce'; + +const BASE64URL_REGEX = /^[A-Za-z0-9_-]+$/; + +describe('PKCE', () => { + describe('generateCodeVerifier', () => { + it('returns a base64url-encoded string', () => { + const verifier = PKCE.generateCodeVerifier(); + expect(verifier).toMatch(BASE64URL_REGEX); + }); + + it('returns a string of 43 characters (32 bytes base64url)', () => { + const verifier = PKCE.generateCodeVerifier(); + expect(verifier).toHaveLength(43); + }); + }); + + describe('generateCodeChallenge', () => { + it('returns a base64url-encoded string', () => { + const challenge = PKCE.generateCodeChallenge('test-verifier'); + expect(challenge).toMatch(BASE64URL_REGEX); + }); + + it('returns a string of 43 characters (SHA-256 hash base64url)', () => { + const challenge = PKCE.generateCodeChallenge('test-verifier'); + expect(challenge).toHaveLength(43); + }); + }); + + describe('generateState', () => { + it('returns a base64url-encoded string', () => { + const state = PKCE.generateState(); + expect(state).toMatch(BASE64URL_REGEX); + }); + + it('returns a string of 36 characters (27 bytes base64url)', () => { + const state = PKCE.generateState(); + expect(state).toHaveLength(36); + }); + }); + + describe('encapsulation', () => { + it('does not expose base64URLEncode as a public method', () => { + expect((PKCE as any).base64URLEncode).toBeUndefined(); + }); + }); +}); diff --git a/react-native/sample/src/auth/customerAccountManager.ts b/react-native/sample/src/auth/customerAccountManager.ts new file mode 100644 index 00000000..a8d00dff --- /dev/null +++ b/react-native/sample/src/auth/customerAccountManager.ts @@ -0,0 +1,326 @@ +import Config from 'react-native-config'; +import {atom, getDefaultStore} from 'jotai'; +import {PKCE} from './pkce'; +import * as tokenStorage from './tokenStorage'; +import type {OAuthTokenResult} from './types'; +import {createDebugLogger} from '../utils'; + +interface TokenResponse { + access_token: string; + refresh_token?: string; + expires_in: number; + id_token?: string; + token_type: string; +} + +interface Session { + isAuthenticated: boolean; + email: string | null; + tokenExpiresAt: number | null; +} + +const log = createDebugLogger('CustomerAccount'); + +const REFRESH_THRESHOLD_MS = 5 * 60 * 1000; + +const defaultSession: Session = { + isAuthenticated: false, + email: null, + tokenExpiresAt: null, +}; + +export class CustomerAccountManager { + readonly sessionAtom = atom(defaultSession); + readonly isLoadingAtom = atom(true); + + private store = getDefaultStore(); + private storedCodeVerifier: string | null = null; + private storedState: string | null = null; + + constructor() { + this.restoreSession(); + } + + static get redirectUri(): string { + return `shop.${CustomerAccountManager.shopId}.app://callback`; + } + + static get callbackScheme(): string { + return `shop.${CustomerAccountManager.shopId}.app`; + } + + buildAuthorizationURL(): string { + const verifier = PKCE.generateCodeVerifier(); + const challenge = PKCE.generateCodeChallenge(verifier); + const state = PKCE.generateState(); + + this.storedCodeVerifier = verifier; + this.storedState = state; + + const params = new URLSearchParams({ + client_id: CustomerAccountManager.clientId, + redirect_uri: CustomerAccountManager.redirectUri, + response_type: 'code', + scope: 'openid email customer-account-api:full', + code_challenge: challenge, + code_challenge_method: 'S256', + state, + }); + + return `${CustomerAccountManager.authorizationEndpoint}?${params.toString()}`; + } + + async handleAuthCallback(code: string, state: string): Promise { + this.store.set(this.isLoadingAtom, true); + try { + const tokens = await this.exchangeCodeForTokens(code, state); + const email = CustomerAccountManager.extractEmailFromIdToken( + tokens.idToken, + ); + this.store.set(this.sessionAtom, { + isAuthenticated: true, + email, + tokenExpiresAt: tokens.expiresAt, + }); + } finally { + this.store.set(this.isLoadingAtom, false); + } + } + + async getValidAccessToken(): Promise { + const tokens = await tokenStorage.getTokens(); + if (!tokens) { + return null; + } + + const isExpiringSoon = + Date.now() + REFRESH_THRESHOLD_MS >= tokens.expiresAt; + + if (isExpiringSoon && tokens.refreshToken) { + try { + const refreshed = await this.refreshAccessToken(); + return refreshed.accessToken; + } catch (error) { + log('Token refresh failed, returning existing token', error); + return tokens.accessToken; + } + } + + return tokens.accessToken; + } + + async logout(): Promise { + const tokens = await tokenStorage.getTokens(); + await tokenStorage.clearTokens(); + + if (tokens?.idToken) { + try { + const params = new URLSearchParams({ + id_token_hint: tokens.idToken, + }); + await fetch( + `${CustomerAccountManager.logoutEndpoint}?${params.toString()}`, + {method: 'GET'}, + ); + } catch { + log('Server-side logout request failed'); + } + } + + this.store.set(this.sessionAtom, defaultSession); + log('Logged out'); + } + + private static get shopId(): string { + const shopId = Config.CUSTOMER_ACCOUNT_API_SHOP_ID; + if (!shopId) { + throw new Error('CUSTOMER_ACCOUNT_API_SHOP_ID is not configured'); + } + return shopId; + } + + private static get clientId(): string { + const clientId = Config.CUSTOMER_ACCOUNT_API_CLIENT_ID; + if (!clientId) { + throw new Error('CUSTOMER_ACCOUNT_API_CLIENT_ID is not configured'); + } + return clientId; + } + + private static get authorizationEndpoint(): string { + return `https://shopify.com/authentication/${CustomerAccountManager.shopId}/oauth/authorize`; + } + + private static get tokenEndpoint(): string { + return `https://shopify.com/authentication/${CustomerAccountManager.shopId}/oauth/token`; + } + + private static get logoutEndpoint(): string { + return `https://shopify.com/authentication/${CustomerAccountManager.shopId}/logout`; + } + + private static extractEmailFromIdToken( + idToken: string | null, + ): string | null { + if (!idToken) { + return null; + } + + try { + const parts = idToken.split('.'); + if (parts.length !== 3) { + return null; + } + + const payload = JSON.parse(atob(parts[1]!)); + return payload.email ?? null; + } catch { + log('Failed to extract email from ID token'); + return null; + } + } + + private async exchangeCodeForTokens( + code: string, + state: string, + ): Promise { + if (state !== this.storedState) { + throw new Error('Invalid state parameter'); + } + + if (!this.storedCodeVerifier) { + throw new Error('Missing code verifier'); + } + + const body = new URLSearchParams({ + grant_type: 'authorization_code', + client_id: CustomerAccountManager.clientId, + redirect_uri: CustomerAccountManager.redirectUri, + code, + code_verifier: this.storedCodeVerifier, + }); + + const response = await fetch(CustomerAccountManager.tokenEndpoint, { + method: 'POST', + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + body: body.toString(), + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Token exchange failed: ${errorText}`); + } + + const data: TokenResponse = await response.json(); + + const tokens: OAuthTokenResult = { + accessToken: data.access_token, + refreshToken: data.refresh_token ?? null, + expiresIn: data.expires_in, + expiresAt: Date.now() + data.expires_in * 1000, + idToken: data.id_token ?? null, + tokenType: data.token_type, + }; + + await tokenStorage.saveTokens(tokens); + + const email = CustomerAccountManager.extractEmailFromIdToken( + tokens.idToken, + ); + if (email) { + await tokenStorage.saveEmail(email); + } + + this.storedCodeVerifier = null; + this.storedState = null; + + log('Token exchange successful'); + return tokens; + } + + private async refreshAccessToken(): Promise { + const existing = await tokenStorage.getTokens(); + if (!existing?.refreshToken) { + throw new Error('No refresh token available'); + } + + const body = new URLSearchParams({ + grant_type: 'refresh_token', + client_id: CustomerAccountManager.clientId, + refresh_token: existing.refreshToken, + }); + + const response = await fetch(CustomerAccountManager.tokenEndpoint, { + method: 'POST', + headers: {'Content-Type': 'application/x-www-form-urlencoded'}, + body: body.toString(), + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Token refresh failed: ${errorText}`); + } + + const data: TokenResponse = await response.json(); + + const tokens: OAuthTokenResult = { + accessToken: data.access_token, + refreshToken: data.refresh_token ?? existing.refreshToken, + expiresIn: data.expires_in, + expiresAt: Date.now() + data.expires_in * 1000, + idToken: data.id_token ?? existing.idToken, + tokenType: data.token_type, + }; + + await tokenStorage.saveTokens(tokens); + + log('Token refresh successful'); + return tokens; + } + + private async restoreSession(): Promise { + try { + const tokens = await tokenStorage.getTokens(); + const email = await tokenStorage.getEmail(); + + if (!tokens) { + this.store.set(this.sessionAtom, defaultSession); + return; + } + + const isExpired = Date.now() >= tokens.expiresAt; + + if (isExpired && tokens.refreshToken) { + try { + const refreshed = await this.refreshAccessToken(); + const refreshedEmail = + CustomerAccountManager.extractEmailFromIdToken( + refreshed.idToken, + ) ?? email; + this.store.set(this.sessionAtom, { + isAuthenticated: true, + email: refreshedEmail, + tokenExpiresAt: refreshed.expiresAt, + }); + return; + } catch { + await tokenStorage.clearTokens(); + this.store.set(this.sessionAtom, defaultSession); + return; + } + } + + this.store.set(this.sessionAtom, { + isAuthenticated: !isExpired, + email, + tokenExpiresAt: tokens.expiresAt, + }); + } catch { + this.store.set(this.sessionAtom, defaultSession); + } finally { + this.store.set(this.isLoadingAtom, false); + } + } +} + +export const customerAccountManager = new CustomerAccountManager(); diff --git a/react-native/sample/src/auth/pkce.ts b/react-native/sample/src/auth/pkce.ts new file mode 100644 index 00000000..3a24f114 --- /dev/null +++ b/react-native/sample/src/auth/pkce.ts @@ -0,0 +1,30 @@ +import crypto from 'react-native-quick-crypto'; + +function base64URLEncode(buffer: ArrayBufferLike): string { + const bytes = new Uint8Array(buffer); + let binary = ''; + for (let i = 0; i < bytes.length; i++) { + binary += String.fromCharCode(bytes[i]!); + } + return btoa(binary) + .replace(/\+/g, '-') + .replace(/\//g, '_') + .replace(/[=]/g, ''); +} + +export class PKCE { + static generateCodeVerifier(): string { + const bytes = crypto.randomBytes(32); + return base64URLEncode(bytes.buffer); + } + + static generateCodeChallenge(verifier: string): string { + const hash = crypto.createHash('sha256').update(verifier).digest(); + return base64URLEncode(hash.buffer); + } + + static generateState(): string { + const bytes = crypto.randomBytes(27); + return base64URLEncode(bytes.buffer); + } +} diff --git a/react-native/sample/src/auth/tokenStorage.ts b/react-native/sample/src/auth/tokenStorage.ts new file mode 100644 index 00000000..9271f3e9 --- /dev/null +++ b/react-native/sample/src/auth/tokenStorage.ts @@ -0,0 +1,30 @@ +import EncryptedStorage from 'react-native-encrypted-storage'; +import type {OAuthTokenResult} from './types'; + +const TOKENS_KEY = 'customer_account_tokens'; +const EMAIL_KEY = 'customer_account_email'; + +export async function saveTokens(tokens: OAuthTokenResult): Promise { + await EncryptedStorage.setItem(TOKENS_KEY, JSON.stringify(tokens)); +} + +export async function getTokens(): Promise { + const raw = await EncryptedStorage.getItem(TOKENS_KEY); + if (!raw) { + return null; + } + return JSON.parse(raw) as OAuthTokenResult; +} + +export async function clearTokens(): Promise { + await EncryptedStorage.removeItem(TOKENS_KEY); + await EncryptedStorage.removeItem(EMAIL_KEY); +} + +export async function saveEmail(email: string): Promise { + await EncryptedStorage.setItem(EMAIL_KEY, email); +} + +export async function getEmail(): Promise { + return EncryptedStorage.getItem(EMAIL_KEY); +} diff --git a/react-native/sample/src/auth/types.ts b/react-native/sample/src/auth/types.ts new file mode 100644 index 00000000..0dfbf003 --- /dev/null +++ b/react-native/sample/src/auth/types.ts @@ -0,0 +1,24 @@ +export const BuyerIdentityMode = { + Guest: 'guest', + Hardcoded: 'hardcoded', + CustomerAccount: 'customerAccount', +} as const; + +export type BuyerIdentityMode = + (typeof BuyerIdentityMode)[keyof typeof BuyerIdentityMode]; + +export const BuyerIdentityModeDisplayNames: Record = + { + [BuyerIdentityMode.Guest]: 'Guest', + [BuyerIdentityMode.Hardcoded]: 'Hardcoded', + [BuyerIdentityMode.CustomerAccount]: 'Customer Account', + }; + +export interface OAuthTokenResult { + accessToken: string; + refreshToken: string | null; + expiresIn: number; + expiresAt: number; + idToken: string | null; + tokenType: string; +} diff --git a/react-native/sample/src/context/Auth.tsx b/react-native/sample/src/context/Auth.tsx new file mode 100644 index 00000000..5eba4c51 --- /dev/null +++ b/react-native/sample/src/context/Auth.tsx @@ -0,0 +1,18 @@ +import {useAtomValue} from 'jotai'; +import {customerAccountManager} from '../auth/customerAccountManager'; + +export function useAuth() { + const session = useAtomValue(customerAccountManager.sessionAtom); + const isLoading = useAtomValue(customerAccountManager.isLoadingAtom); + + return { + isAuthenticated: session.isAuthenticated, + customerEmail: session.email, + isLoading, + tokenExpiresAt: session.tokenExpiresAt, + logout: () => customerAccountManager.logout(), + getValidAccessToken: () => customerAccountManager.getValidAccessToken(), + handleAuthCallback: (code: string, state: string) => + customerAccountManager.handleAuthCallback(code, state), + }; +} diff --git a/react-native/sample/src/context/Cart.tsx b/react-native/sample/src/context/Cart.tsx new file mode 100644 index 00000000..c4235be5 --- /dev/null +++ b/react-native/sample/src/context/Cart.tsx @@ -0,0 +1,278 @@ +import type {PropsWithChildren} from 'react'; +import React, { + createContext, + useCallback, + useEffect, + useMemo, + useReducer, +} from 'react'; +import {Alert} from 'react-native'; +import {atom, useAtom} from 'jotai'; +import {useShopifyCheckoutSheet} from '@shopify/checkout-sheet-kit'; +import useShopify from '../hooks/useShopify'; +import {useConfig} from './Config'; +import {useAuth} from './Auth'; +import {BuyerIdentityMode} from '../auth/types'; +import {createBuyerIdentityCartInput} from '../utils'; + +interface Context { + cartId: string | undefined; + checkoutURL: string | undefined; + totalQuantity: number; + addingToCart: Set; + clearCart: () => void; + addToCart: (variantId: string) => Promise; + removeFromCart: (variantId: string) => Promise; +} + +const defaultCartId = undefined; +const defaultCheckoutURL = undefined; +const defaultTotalQuantity = 0; + +const CartContext = createContext({ + cartId: defaultCartId, + checkoutURL: undefined, + totalQuantity: 0, + addingToCart: new Set(), + addToCart: async () => {}, + removeFromCart: async () => {}, + clearCart: () => {}, +}); + +type AddingToCartAction = + | {type: 'add'; variantId: string} + | {type: 'remove'; variantId: string}; + +const checkoutURLState = atom(defaultCheckoutURL); +const cartIdState = atom(defaultCartId); +const totalQuantityState = atom(defaultTotalQuantity); + +export const CartProvider: React.FC = ({children}) => { + const shopify = useShopifyCheckoutSheet(); + // Reuse the same cart ID for the lifetime of the app + const [checkoutURL, setCheckoutURL] = useAtom(checkoutURLState); + // Reuse the same cart ID for the lifetime of the app + const [cartId, setCartId] = useAtom(cartIdState); + // Keep track of the number of items in the cart + const [totalQuantity, setTotalQuantity] = useAtom(totalQuantityState); + // Maintain a loading state for items being added to the cart + const addingToCartReducer = ( + state: Set, + action: AddingToCartAction, + ): Set => { + switch (action.type) { + case 'add': + return new Set([...state, action.variantId]); + case 'remove': + return new Set([...state].filter(id => id !== action.variantId)); + default: + throw new Error(); + } + }; + // Maintain a loading state for items being added to the cart + const defaultSet: Set = new Set(); + const [addingToCart, dispatch] = useReducer(addingToCartReducer, defaultSet); + const {appConfig} = useConfig(); + const {getValidAccessToken, isAuthenticated} = useAuth(); + + const {mutations, queries} = useShopify(); + const [createCart] = mutations.cartCreate; + const [addLineItems] = mutations.cartLinesAdd; + const [removeLineItems] = mutations.cartLinesRemove; + const [fetchCart] = queries.cart; + + const clearCart = useCallback(() => { + setCartId(defaultCartId); + setCheckoutURL(undefined); + setTotalQuantity(0); + }, [setCartId, setCheckoutURL, setTotalQuantity]); + + useEffect(() => { + clearCart(); + }, [appConfig.buyerIdentityMode, clearCart]); + + useEffect(() => { + const subscription = shopify.addEventListener('completed', () => { + clearCart(); + }); + + return subscription?.remove; + }, [shopify, clearCart, setCartId, setCheckoutURL, setTotalQuantity]); + + useEffect(() => { + async function getCart() { + try { + const {data} = await fetchCart({ + variables: { + cartId, + }, + }); + if (data?.cart.totalQuantity) { + setTotalQuantity(data?.cart.totalQuantity); + } + } catch {} + } + + if (cartId) { + getCart(); + } + }, [cartId, fetchCart, setTotalQuantity]); + + const preloadCheckout = useCallback( + async (checkoutURL: string) => { + if (checkoutURL) { + const config = await shopify.getConfig(); + if (config?.preloading) { + shopify.preload(checkoutURL); + } + } + }, + [shopify], + ); + + const addToCart = useCallback( + async (variantId: string) => { + let id = cartId; + + dispatch({type: 'add', variantId}); + + if ( + !id && + appConfig.buyerIdentityMode === BuyerIdentityMode.CustomerAccount && + !isAuthenticated + ) { + dispatch({type: 'remove', variantId}); + Alert.alert( + 'Sign in required', + 'Sign in on the Account tab or change the Buyer Identity setting to add items to your cart.', + ); + return; + } + + if (!id) { + let customerAccessToken: string | undefined; + if ( + appConfig.buyerIdentityMode === BuyerIdentityMode.CustomerAccount + ) { + customerAccessToken = + (await getValidAccessToken()) ?? undefined; + } + const cartInput = createBuyerIdentityCartInput( + appConfig, + customerAccessToken, + ); + const cart = await createCart({variables: {input: cartInput}}); + id = cart.data.cartCreate.cart?.id; + + if (id) { + setCartId(id); + } + } + + const {data} = await addLineItems({ + variables: { + cartId: id, + lines: [{quantity: 1, merchandiseId: variantId}], + }, + }); + + dispatch({type: 'remove', variantId}); + + setCheckoutURL(data.cartLinesAdd.cart.checkoutUrl); + setTotalQuantity(data.cartLinesAdd.cart.totalQuantity); + + if (data.cartLinesAdd.cart.checkoutUrl) { + await preloadCheckout(data.cartLinesAdd.cart.checkoutUrl); + } + + if (id) { + fetchCart({ + variables: { + cartId: id, + }, + }); + } + }, + [ + cartId, + addLineItems, + setCheckoutURL, + setTotalQuantity, + appConfig, + createCart, + setCartId, + fetchCart, + preloadCheckout, + getValidAccessToken, + isAuthenticated, + ], + ); + + const removeFromCart = useCallback( + async (variantId: string) => { + if (!cartId) { + return; + } + + dispatch({type: 'add', variantId}); + + const {data} = await removeLineItems({ + variables: { + cartId, + lineIds: [variantId], + }, + }); + + setCheckoutURL(data.cartLinesRemove.cart.checkoutUrl); + setTotalQuantity(data.cartLinesRemove.cart.totalQuantity); + + if (checkoutURL) { + await preloadCheckout(checkoutURL); + } + + if (cartId) { + await fetchCart({ + variables: { + cartId, + }, + }); + } + + dispatch({type: 'remove', variantId}); + }, + [ + cartId, + removeLineItems, + setCheckoutURL, + setTotalQuantity, + checkoutURL, + preloadCheckout, + fetchCart, + ], + ); + + const value = useMemo( + () => ({ + cartId, + checkoutURL, + addToCart, + removeFromCart, + totalQuantity, + addingToCart, + clearCart, + }), + [ + cartId, + checkoutURL, + addToCart, + removeFromCart, + totalQuantity, + addingToCart, + clearCart, + ], + ); + + return {children}; +}; + +export const useCart = () => React.useContext(CartContext); diff --git a/react-native/sample/src/context/Config.tsx b/react-native/sample/src/context/Config.tsx new file mode 100644 index 00000000..407fde01 --- /dev/null +++ b/react-native/sample/src/context/Config.tsx @@ -0,0 +1,90 @@ +import type {PropsWithChildren} from 'react'; +import React, { + createContext, + useCallback, + useEffect, + useMemo, + useState, +} from 'react'; +import {ColorScheme, ApplePayStyle} from '@shopify/checkout-sheet-kit'; +import EncryptedStorage from 'react-native-encrypted-storage'; +import {useTheme} from './Theme'; +import {BuyerIdentityMode} from '../auth/types'; + +export interface AppConfig { + colorScheme: ColorScheme; + buyerIdentityMode: BuyerIdentityMode; + applePayStyle?: ApplePayStyle; +} + +interface Context { + appConfig: AppConfig; + setAppConfig: (config: AppConfig) => void; +} + +const CONFIG_STORAGE_KEY = 'app_config'; + +const defaultAppConfig: AppConfig = { + colorScheme: ColorScheme.automatic, + buyerIdentityMode: BuyerIdentityMode.Guest, + applePayStyle: ApplePayStyle.automatic, +}; + +const ConfigContext = createContext({ + appConfig: defaultAppConfig, + setAppConfig: () => undefined, +}); + +export const ConfigProvider: React.FC< + PropsWithChildren<{config?: AppConfig}> +> = ({children, config}) => { + const [appConfig, setInternalAppConfig] = + useState(defaultAppConfig); + const {setColorScheme} = useTheme(); + + useEffect(() => { + async function restoreConfig() { + try { + const raw = await EncryptedStorage.getItem(CONFIG_STORAGE_KEY); + if (raw) { + const saved = JSON.parse(raw) as Partial; + const restored: AppConfig = { + ...defaultAppConfig, + ...config, + ...saved, + }; + setInternalAppConfig(restored); + setColorScheme(restored.colorScheme); + return; + } + } catch {} + setColorScheme(config?.colorScheme ?? ColorScheme.automatic); + } + restoreConfig(); + }, [config, setColorScheme]); + + const setAppConfig = useCallback((newConfig: AppConfig) => { + console.groupCollapsed('APP CONFIG UPDATE'); + console.log(newConfig); + console.groupEnd(); + setInternalAppConfig(newConfig); + EncryptedStorage.setItem( + CONFIG_STORAGE_KEY, + JSON.stringify(newConfig), + ).catch(() => {}); + }, []); + + const value = useMemo( + () => ({ + appConfig, + setAppConfig, + }), + [appConfig, setAppConfig], + ); + + return ( + {children} + ); +}; + +export const useConfig = () => React.useContext(ConfigContext); diff --git a/react-native/sample/src/context/Theme.tsx b/react-native/sample/src/context/Theme.tsx new file mode 100644 index 00000000..b4f2d712 --- /dev/null +++ b/react-native/sample/src/context/Theme.tsx @@ -0,0 +1,207 @@ +import type {PropsWithChildren} from 'react'; +import React, {createContext, useCallback, useMemo, useState} from 'react'; +import type {ColorSchemeName} from 'react-native'; +import {Appearance, useColorScheme} from 'react-native'; +import type {Theme} from '@react-navigation/native'; +import {DarkTheme, DefaultTheme} from '@react-navigation/native'; +import {ColorScheme} from '@shopify/checkout-sheet-kit'; + +interface Context { + cornerRadius: number; + colors: Colors; + colorScheme: ColorScheme; + preference: ColorSchemeName; + setColorScheme: (colorScheme: ColorScheme) => void; +} + +export const darkColors: Colors = { + background: '#1D1D1F', + backgroundSubdued: '#222', + border: '#333336', + text: '#fff', + textSubdued: '#eee', + primary: '#0B96F1', + primaryText: '#fff', + secondary: '#0087ff', + secondaryText: '#fff', + + webviewBackgroundColor: '#1D1D1F', + webViewProgressIndicator: '#0B96F1', + webviewHeaderBackgroundColor: '#1D1D1F', + webviewHeaderTextColor: '#ffffff', + webviewCloseButtonColor: '#ffffff', +}; + +export const lightColors: Colors = { + background: '#eee', + backgroundSubdued: '#fff', + border: '#eee', + text: '#000', + textSubdued: '#a3a3a3', + primary: '#0087ff', + primaryText: '#fff', + secondary: '#000', + secondaryText: '#fff', + + webviewBackgroundColor: '#ffffff', + webViewProgressIndicator: '#0087ff', + webviewHeaderBackgroundColor: '#ffffff', + webviewHeaderTextColor: '#000000', + webviewCloseButtonColor: '#000000', +}; + +export const webColors: Colors = { + background: '#f0f0e8', + backgroundSubdued: '#e8e8e0', + border: '#d0d0cd', + text: '#2d2a38', + textSubdued: '#a3a3a3', + primary: '#2c2a38', + primaryText: '#0087ff', + secondary: '#2d2a38', + secondaryText: '#fff', + + webviewBackgroundColor: '#f0f0e8', + webViewProgressIndicator: '#2c2a38', + webviewHeaderBackgroundColor: '#f0f0e8', + webviewHeaderTextColor: '#2c2a38', + webviewCloseButtonColor: '#2c2a38', +}; + +const ThemeContext = createContext({ + cornerRadius: 35, + colorScheme: ColorScheme.automatic, + colors: lightColors, + preference: Appearance.getColorScheme(), + setColorScheme() {}, +}); + +export interface Colors { + background: string; + backgroundSubdued: string; + border: string; + text: string; + textSubdued: string; + primary: string; + primaryText: string; + secondary: string; + secondaryText: string; + webviewBackgroundColor: string; + webViewProgressIndicator: string; + webviewHeaderBackgroundColor: string; + webviewHeaderTextColor: string; + webviewCloseButtonColor: string; +} + +export function getNavigationTheme( + colorScheme: ColorScheme, + preference: ColorSchemeName, +): Theme { + const colors = getColors(colorScheme, preference); + const primary = '#0087ff'; + + const light = { + ...DefaultTheme, + dark: false, + colors: { + ...DefaultTheme.colors, + primary, + background: colors.background, + card: colors.backgroundSubdued, + text: colors.text, + border: colors.border, + notification: colors.primary, + }, + }; + + const dark = { + ...DarkTheme, + dark: true, + colors: { + ...DarkTheme.colors, + primary, + background: colors.background, + card: colors.backgroundSubdued, + text: colors.primaryText, + border: colors.border, + notification: colors.primary, + }, + }; + + const web = { + ...DefaultTheme, + dark: false, + colors: { + ...DefaultTheme.colors, + primary, + background: colors.background, + card: colors.backgroundSubdued, + text: colors.text, + border: colors.border, + notification: colors.primary, + }, + }; + + switch (colorScheme) { + case ColorScheme.automatic: + return preference === 'dark' ? dark : light; + case ColorScheme.dark: + return dark; + case ColorScheme.web: + return web; + default: + return light; + } +} + +export function getColors( + colorScheme: ColorScheme, + preference: ColorSchemeName, +): Colors { + switch (colorScheme) { + case ColorScheme.automatic: + return preference === 'dark' ? darkColors : lightColors; + case ColorScheme.dark: + return darkColors; + case ColorScheme.web: + return webColors; + default: + return lightColors; + } +} + +export const ThemeProvider: React.FC< + PropsWithChildren<{defaultValue: ColorScheme; cornerRadius: number}> +> = ({children, defaultValue = ColorScheme.automatic, cornerRadius}) => { + const preference = useColorScheme(); + const [colorScheme, setColorSchemeInternal] = + useState(defaultValue); + + const setColorScheme = useCallback((colorScheme: ColorScheme) => { + if (colorScheme === ColorScheme.automatic) { + Appearance.setColorScheme(null); + } else { + Appearance.setColorScheme( + colorScheme === ColorScheme.dark ? 'dark' : 'light', + ); + } + setColorSchemeInternal(colorScheme); + }, []); + + const value = useMemo( + () => ({ + cornerRadius, + colors: getColors(colorScheme, preference), + preference, + colorScheme, + setColorScheme, + }), + [preference, colorScheme, setColorScheme, cornerRadius], + ); + + return ( + {children} + ); +}; + +export const useTheme = () => React.useContext(ThemeContext); diff --git a/react-native/sample/src/hooks/useCheckoutEventHandlers.ts b/react-native/sample/src/hooks/useCheckoutEventHandlers.ts new file mode 100644 index 00000000..2511b5eb --- /dev/null +++ b/react-native/sample/src/hooks/useCheckoutEventHandlers.ts @@ -0,0 +1,55 @@ +import {useMemo} from 'react'; + +import {createDebugLogger} from '../utils'; + +import {useCart} from '../context/Cart'; +import type { + CheckoutCompletedEvent, + CheckoutException, + PixelEvent, + RenderStateChangeEvent, +} from '@shopify/checkout-sheet-kit'; +import {Linking} from 'react-native'; + +interface EventHandlers { + onFail?: (error: CheckoutException) => void; + onComplete?: (event: CheckoutCompletedEvent) => void; + onCancel?: () => void; + onRenderStateChange?: (event: RenderStateChangeEvent) => void; + onShouldRecoverFromError?: (error: {message: string}) => boolean; + onWebPixelEvent?: (event: PixelEvent) => void; + onClickLink?: (url: string) => void; +} + +export function useShopifyEventHandlers(name?: string): EventHandlers { + const {clearCart} = useCart(); + + return useMemo(() => { + const log = createDebugLogger(name ?? ''); + return { + onFail: error => { + log('onFail', error); + }, + onComplete: event => { + log('onComplete', event.orderDetails.id); + clearCart(); + }, + onCancel: () => { + log('onCancel'); + }, + onRenderStateChange: event => { + log('onRenderStateChange', event); + }, + onWebPixelEvent: event => { + log('onWebPixelEvent', event.name); + }, + onClickLink: async url => { + log('onClickLink', url); + + if (await Linking.canOpenURL(url)) { + await Linking.openURL(url); + } + }, + }; + }, [clearCart, name]); +} diff --git a/react-native/sample/src/hooks/useShopify.ts b/react-native/sample/src/hooks/useShopify.ts new file mode 100644 index 00000000..573ad335 --- /dev/null +++ b/react-native/sample/src/hooks/useShopify.ts @@ -0,0 +1,230 @@ +/* +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import {gql, useLazyQuery, useMutation} from '@apollo/client'; +import type {Edges, ShopifyProduct, ShopifyCart} from '../../@types'; +import {getLocale} from '../utils'; + +const moneyFragment = gql` + fragment Price on MoneyV2 { + currencyCode + amount + } +`; + +const productFragment = gql` + fragment Product on ProductVariant { + id + price { + ...Price + } + product { + title + } + image { + id + width + height + url + thumbnailUrl: url(transform: {maxWidth: 80, maxHeight: 80}) + } + } + + ${moneyFragment} +`; + +const cartCostFragment = gql` + fragment Cost on CartCost { + subtotalAmount { + ...Price + } + totalAmount { + ...Price + } + totalTaxAmount { + ...Price + } + } +`; + +const PRODUCTS_QUERY = gql` + query FetchProducts($country: CountryCode = CA) + @inContext(country: $country) { + products(first: 10) { + edges { + node { + id + title + description + variants(first: 1) { + edges { + node { + id + unitPrice { + ...Price + } + price { + ...Price + } + } + } + } + images(first: 1) { + edges { + node { + id + width + height + url + thumbnailUrl: url(transform: {maxWidth: 150, maxHeight: 150}) + } + } + } + } + } + } + } + + ${moneyFragment} +`; + +const CART_QUERY = gql` + query FetchCart($cartId: ID!, $country: CountryCode = CA) + @inContext(country: $country) { + cart(id: $cartId) { + id + totalQuantity + cost { + ...Cost + } + lines(first: 100) { + edges { + node { + id + quantity + merchandise { + ...Product + } + cost { + totalAmount { + ...Price + } + } + } + } + } + } + } + + ${productFragment} + ${moneyFragment} + ${cartCostFragment} +`; + +const CREATE_CART_MUTATION = gql` + mutation CreateCart($input: CartInput, $country: CountryCode = CA) + @inContext(country: $country) { + cartCreate(input: $input) { + cart { + id + checkoutUrl + } + userErrors { + code + field + message + } + } + } +`; + +const ADD_TO_CART_MUTATION = gql` + mutation AddToCart( + $cartId: ID! + $lines: [CartLineInput!]! + $country: CountryCode = CA + ) @inContext(country: $country) { + cartLinesAdd(cartId: $cartId, lines: $lines) { + cart { + id + checkoutUrl + totalQuantity + } + } + } +`; + +const REMOVE_FROM_CART_MUTATION = gql` + mutation RemoveFromCart( + $cartId: ID! + $lineIds: [ID!]! + $country: CountryCode = CA + ) @inContext(country: $country) { + cartLinesRemove(cartId: $cartId, lineIds: $lineIds) { + cart { + id + checkoutUrl + totalQuantity + } + } + } +`; + +function useShopify() { + const [, country] = getLocale().split('-'); + const includeCountry = { + variables: { + country, + }, + }; + const products = useLazyQuery<{products: Edges}>( + PRODUCTS_QUERY, + { + ...includeCountry, + }, + ); + const cart = useLazyQuery<{cart: ShopifyCart}>(CART_QUERY, { + fetchPolicy: 'network-only', + ...includeCountry, + }); + const cartCreate = useMutation(CREATE_CART_MUTATION, includeCountry); + const cartLinesAdd = useMutation(ADD_TO_CART_MUTATION, includeCountry); + const cartLinesRemove = useMutation( + REMOVE_FROM_CART_MUTATION, + includeCountry, + ); + + return { + queries: { + cart, + products, + }, + mutations: { + cartCreate, + cartLinesAdd, + cartLinesRemove, + }, + }; +} + +export default useShopify; diff --git a/react-native/sample/src/screens/AccountScreen.tsx b/react-native/sample/src/screens/AccountScreen.tsx new file mode 100644 index 00000000..c7d52c42 --- /dev/null +++ b/react-native/sample/src/screens/AccountScreen.tsx @@ -0,0 +1,151 @@ +import React from 'react'; +import { + ActivityIndicator, + Pressable, + SafeAreaView, + StyleSheet, + Text, + View, +} from 'react-native'; +import Icon from 'react-native-vector-icons/Entypo'; +import type {NativeStackScreenProps} from '@react-navigation/native-stack'; +import type {AccountStackParamList} from '../App'; +import type {Colors} from '../context/Theme'; +import {useTheme} from '../context/Theme'; +import {useAuth} from '../context/Auth'; + +type Props = NativeStackScreenProps; + +function AccountScreen({navigation}: Props) { + const {isAuthenticated, customerEmail, isLoading} = useAuth(); + const {colors} = useTheme(); + const styles = createStyles(colors); + + if (isLoading) { + return ( + + + + ); + } + + if (isAuthenticated) { + return ; + } + + return ( + navigation.navigate('Login')} + /> + ); +} + +function AuthenticatedView({ + email, + styles, +}: { + email: string | null; + styles: ReturnType; +}) { + const {logout} = useAuth(); + + return ( + + + + Signed In + {email && {email}} + + Your checkout will be pre-filled with your account information. + + + Sign Out + + + + ); +} + +function UnauthenticatedView({ + styles, + onSignIn, +}: { + styles: ReturnType; + onSignIn: () => void; +}) { + return ( + + + + Sign in to your account + + Get faster checkout, order tracking, and more. + + + • Faster checkout experience + • Pre-filled shipping details + • Order history and tracking + + + Sign In + + + + ); +} + +function createStyles(colors: Colors) { + return StyleSheet.create({ + container: { + flex: 1, + }, + centered: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + paddingHorizontal: 32, + }, + heading: { + fontSize: 22, + fontWeight: 'bold', + color: colors.text, + marginTop: 16, + marginBottom: 8, + }, + email: { + fontSize: 16, + color: colors.primary, + marginBottom: 8, + }, + description: { + fontSize: 14, + color: colors.textSubdued, + textAlign: 'center', + marginBottom: 16, + }, + benefitsList: { + alignSelf: 'stretch', + marginBottom: 24, + paddingHorizontal: 32, + }, + benefitItem: { + fontSize: 14, + color: colors.textSubdued, + paddingVertical: 4, + }, + button: { + backgroundColor: colors.secondary, + paddingHorizontal: 32, + paddingVertical: 12, + borderRadius: 10, + }, + buttonText: { + fontSize: 16, + fontWeight: 'bold', + color: colors.secondaryText, + }, + }); +} + +export default AccountScreen; diff --git a/react-native/sample/src/screens/CartScreen.tsx b/react-native/sample/src/screens/CartScreen.tsx new file mode 100644 index 00000000..e5712df3 --- /dev/null +++ b/react-native/sample/src/screens/CartScreen.tsx @@ -0,0 +1,406 @@ +/* +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import React, {useCallback, useEffect} from 'react'; +import { + SafeAreaView, + ScrollView, + View, + StyleSheet, + Text, + Image, + ActivityIndicator, + Pressable, + RefreshControl, +} from 'react-native'; +import Icon from 'react-native-vector-icons/Entypo'; + +import { + useShopifyCheckoutSheet, + AcceleratedCheckoutButtons, + ApplePayLabel, + AcceleratedCheckoutWallet, +} from '@shopify/checkout-sheet-kit'; +import {useConfig} from '../context/Config'; +import useShopify from '../hooks/useShopify'; +import type {CartLineItem} from '../../@types'; +import type {Colors} from '../context/Theme'; +import {useTheme} from '../context/Theme'; +import {useCart} from '../context/Cart'; +import {currency} from '../utils'; +import {useShopifyEventHandlers} from '../hooks/useCheckoutEventHandlers'; + +function CartScreen(): React.JSX.Element { + const ShopifyCheckout = useShopifyCheckoutSheet(); + const [refreshing, setRefreshing] = React.useState(false); + const {cartId, checkoutURL, totalQuantity, removeFromCart, addingToCart} = + useCart(); + const {queries} = useShopify(); + const {appConfig} = useConfig(); + const eventHandlers = useShopifyEventHandlers( + 'Cart - AcceleratedCheckoutButtons', + ); + + const [fetchCart, {data, loading, error}] = queries.cart; + + const {colors, cornerRadius} = useTheme(); + const styles = createStyles(colors, cornerRadius); + + useEffect(() => { + if (cartId) { + fetchCart({ + variables: { + cartId, + }, + }); + } + }, [fetchCart, cartId]); + + const onRefresh = useCallback(() => { + setRefreshing(true); + fetchCart({ + variables: { + cartId, + }, + }).then(() => setRefreshing(false)); + }, [cartId, fetchCart]); + + const presentCheckout = async () => { + if (checkoutURL) { + ShopifyCheckout.present(checkoutURL); + } + }; + + if (error) { + return ( + + + An error occurred while fetching the cart + + + {error?.name} {error?.message} + + + ); + } + + if (loading) { + return ( + + + Loading cart... + + ); + } + + if (!data || !data.cart || data.cart.lines.edges.length === 0 || !cartId) { + return ( + + + Your cart is empty. + + ); + } + + return ( + + + }> + + {data?.cart.lines.edges.map(({node}) => ( + removeFromCart(node.id)} + /> + ))} + + + + + Subtotal + + {price(data.cart.cost.subtotalAmount)} + + + + + Taxes + Estimated at checkout + + + + Total + + {price(data.cart.cost.totalAmount)} + + + + + {totalQuantity > 0 && cartId && ( + + + + + + Checkout + + {price(data.cart.cost.totalAmount)} + + + + {/* Empty wallets, should not render anything */} + + + + )} + + + ); +} + +function price(value: {amount: string; currencyCode: string}) { + if (!value) { + return '-'; + } + + const {amount, currencyCode} = value; + return currency(amount, currencyCode); +} + +function CartItem({ + item, + quantity, + onRemove, + loading, +}: { + item: CartLineItem; + quantity: number; + loading?: boolean; + onRemove: () => void; +}) { + const {colors, cornerRadius} = useTheme(); + const styles = createStyles(colors, cornerRadius); + + return ( + + {item.merchandise.image?.thumbnailUrl && ( + {item.merchandise.image?.altText} + )} + + + + {item.merchandise.product.title} + + Quantity: {quantity} + + + + {price(item.cost?.totalAmount)} + + + {loading ? ( + + ) : ( + Remove + )} + + + + + ); +} + +function createStyles(colors: Colors, cornerRadius: number) { + return StyleSheet.create({ + loading: { + flex: 1, + padding: 2, + justifyContent: 'center', + alignItems: 'center', + }, + loadingText: { + marginVertical: 20, + color: colors.text, + }, + scrollView: { + paddingBottom: 10, + }, + checkoutContainer: { + paddingHorizontal: 20, + paddingVertical: 10, + gap: 8, + }, + cartButton: { + flex: 1, + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + width: 'auto', + height: 48, + borderRadius: cornerRadius, + paddingHorizontal: 30, + paddingVertical: 2, + backgroundColor: colors.secondary, + fontWeight: 'bold', + }, + cartButtonText: { + fontSize: 22, + lineHeight: 24, + textAlign: 'center', + color: colors.secondaryText, + fontWeight: 'bold', + }, + cartButtonTextSubtitle: { + fontSize: 12, + textAlign: 'center', + color: colors.textSubdued, + fontWeight: 'bold', + }, + productList: { + marginVertical: 20, + paddingHorizontal: 16, + }, + productItem: { + display: 'flex', + flexDirection: 'row', + marginBottom: 10, + padding: 10, + backgroundColor: colors.backgroundSubdued, + borderRadius: 5, + }, + productItemLoading: { + opacity: 0.6, + }, + productTextContainer: { + flex: 1, + }, + productText: { + paddingLeft: 10, + display: 'flex', + flex: 1, + color: colors.textSubdued, + justifyContent: 'center', + flexDirection: 'row', + alignItems: 'center', + }, + productTitle: { + fontSize: 16, + marginBottom: 5, + fontWeight: 'bold', + lineHeight: 20, + color: colors.text, + }, + productDescription: { + fontSize: 14, + color: colors.textSubdued, + }, + productPrice: { + fontSize: 15, + alignSelf: 'flex-start', + paddingTop: 10, + paddingHorizontal: 10, + paddingBottom: 2, + fontWeight: 'bold', + color: colors.text, + }, + removeButton: { + alignSelf: 'flex-end', + marginRight: 10, + marginTop: 2, + }, + removeButtonText: { + color: colors.textSubdued, + }, + productImage: { + width: 60, + height: 60, + borderRadius: 6, + }, + costContainer: { + marginBottom: 10, + marginHorizontal: 20, + paddingTop: 10, + paddingBottom: 65, + paddingHorizontal: 2, + borderTopWidth: 1, + borderTopColor: colors.border, + }, + costBlock: { + display: 'flex', + flexDirection: 'row', + justifyContent: 'space-between', + paddingHorizontal: 5, + paddingVertical: 5, + }, + costBlockText: { + fontSize: 14, + color: colors.textSubdued, + }, + costBlockTextStrong: { + fontSize: 16, + color: colors.text, + fontWeight: 'bold', + }, + }); +} + +export default CartScreen; diff --git a/react-native/sample/src/screens/CatalogScreen.tsx b/react-native/sample/src/screens/CatalogScreen.tsx new file mode 100644 index 00000000..90ad13dd --- /dev/null +++ b/react-native/sample/src/screens/CatalogScreen.tsx @@ -0,0 +1,280 @@ +/* +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import React, {useEffect} from 'react'; +import { + SafeAreaView, + ScrollView, + View, + StyleSheet, + Text, + Image, + Pressable, + ActivityIndicator, +} from 'react-native'; + +import useShopify from '../hooks/useShopify'; + +import type {ShopifyProduct} from '../../@types'; +import type {Colors} from '../context/Theme'; +import {useTheme} from '../context/Theme'; +import {useCart} from '../context/Cart'; +import type {NativeStackScreenProps} from '@react-navigation/native-stack'; +import type {RootStackParamList} from '../App'; +import {currency} from '../utils'; + +type Props = NativeStackScreenProps; + +function CatalogScreen({navigation}: Props) { + const {addToCart, addingToCart} = useCart(); + const {colors} = useTheme(); + const styles = createStyles(colors); + const {queries} = useShopify(); + + const [fetchProducts, {loading, data, error}] = queries.products; + + useEffect(() => { + fetchProducts(); + }, [fetchProducts]); + + if (error) { + return ( + + + An error occurred while loading the catalog. + + "{error?.message}" + + ); + } + + if (loading) { + return ( + + + Loading catalog... + + ); + } + + return ( + + + + {data?.products.edges.map(({node}, index) => ( + { + navigation.navigate('ProductDetails', { + product: node, + variant: getVariant(node), + }); + }} + loading={addingToCart.has(getVariant(node)?.id ?? '')} + onAddToCart={addToCart} + /> + ))} + + + + ); +} + +function getVariant(node: ShopifyProduct) { + return node.variants.edges[0]?.node; +} + +function Product({ + product, + onAddToCart, + loading = false, + onPress, + testID, +}: { + product: ShopifyProduct; + loading?: boolean; + onPress: () => void; + onAddToCart: (variantId: string) => void; + testID: string; +}) { + const {colors} = useTheme(); + const styles = createStyles(colors); + const image = product.images?.edges[0]?.node; + const variant = getVariant(product); + + return ( + + {image?.url && ( + {image?.altText} + )} + + + {product.title} + + {currency(variant?.price.amount, variant?.price.currencyCode)} + + + + {loading ? ( + + + + ) : ( + variant?.id && onAddToCart(variant.id)}> + Add to cart + + )} + + + + ); +} + +export default CatalogScreen; + +function createStyles(colors: Colors) { + return StyleSheet.create({ + container: { + maxHeight: '100%', + }, + loading: { + flex: 1, + padding: 2, + justifyContent: 'center', + alignItems: 'center', + }, + loadingText: { + marginVertical: 20, + color: colors.text, + }, + scrollView: { + paddingBottom: 55, + }, + cartButton: { + position: 'absolute', + width: 'auto', + bottom: 10, + height: 55, + left: 0, + right: 0, + borderRadius: 10, + marginHorizontal: 20, + padding: 10, + backgroundColor: colors.secondary, + fontWeight: 'bold', + }, + cartButtonText: { + fontSize: 16, + lineHeight: 20, + textAlign: 'center', + color: colors.secondaryText, + fontWeight: 'bold', + }, + cartButtonTextSubtitle: { + fontSize: 12, + textAlign: 'center', + color: colors.textSubdued, + fontWeight: 'bold', + }, + productList: { + marginVertical: 20, + paddingHorizontal: 16, + }, + productItem: { + flex: 1, + flexDirection: 'row', + marginBottom: 10, + padding: 10, + backgroundColor: colors.backgroundSubdued, + borderRadius: 5, + }, + productText: { + paddingLeft: 20, + paddingTop: 10, + flexShrink: 1, + flexGrow: 1, + color: colors.textSubdued, + justifyContent: 'space-between', + }, + productTitle: { + fontSize: 16, + marginBottom: 5, + fontWeight: 'bold', + lineHeight: 20, + color: colors.text, + }, + productPrice: { + fontSize: 14, + flex: 1, + color: colors.textSubdued, + }, + productImage: { + width: 100, + height: 100, + marginRight: 5, + borderRadius: 6, + }, + addToCartLoading: { + padding: 10, + marginRight: 20, + }, + addToCartButtonContainer: { + alignItems: 'flex-end', + flexShrink: 1, + flexGrow: 0, + }, + addToCartButton: { + borderRadius: 10, + fontSize: 8, + margin: 5, + backgroundColor: 'transparent', + paddingHorizontal: 10, + paddingVertical: 5, + }, + addToCartButtonText: { + fontSize: 14, + lineHeight: 20, + color: colors.primary, + fontWeight: 'bold', + textAlign: 'center', + }, + }); +} diff --git a/react-native/sample/src/screens/LoginScreen.tsx b/react-native/sample/src/screens/LoginScreen.tsx new file mode 100644 index 00000000..3fc34d60 --- /dev/null +++ b/react-native/sample/src/screens/LoginScreen.tsx @@ -0,0 +1,105 @@ +import React, {useCallback, useMemo, useState} from 'react'; +import {ActivityIndicator, StyleSheet, View} from 'react-native'; +import {WebView} from 'react-native-webview'; +import type {ShouldStartLoadRequest} from 'react-native-webview/lib/WebViewTypes'; +import type {NativeStackScreenProps} from '@react-navigation/native-stack'; +import type {AccountStackParamList} from '../App'; +import {useAuth} from '../context/Auth'; +import {useConfig} from '../context/Config'; +import {BuyerIdentityMode} from '../auth/types'; +import { + CustomerAccountManager, + customerAccountManager, +} from '../auth/customerAccountManager'; +import type {Colors} from '../context/Theme'; +import {useTheme} from '../context/Theme'; + +type Props = NativeStackScreenProps; + +function LoginScreen({navigation}: Props) { + const {handleAuthCallback} = useAuth(); + const {appConfig, setAppConfig} = useConfig(); + const {colors} = useTheme(); + const styles = createStyles(colors); + const [isProcessing, setIsProcessing] = useState(false); + + const authorizationURL = useMemo( + () => customerAccountManager.buildAuthorizationURL(), + [], + ); + const callbackScheme = CustomerAccountManager.callbackScheme; + + const handleNavigationRequest = useCallback( + (request: ShouldStartLoadRequest): boolean => { + const {url} = request; + + if (url.startsWith(`${callbackScheme}://callback`)) { + setIsProcessing(true); + const urlParams = new URL(url); + const code = urlParams.searchParams.get('code'); + const state = urlParams.searchParams.get('state'); + + if (code && state) { + handleAuthCallback(code, state) + .then(() => { + setAppConfig({ + ...appConfig, + buyerIdentityMode: BuyerIdentityMode.CustomerAccount, + }); + navigation.goBack(); + }) + .catch(() => { + setIsProcessing(false); + navigation.goBack(); + }); + } else { + navigation.goBack(); + } + + return false; + } + + return true; + }, + [appConfig, callbackScheme, handleAuthCallback, navigation, setAppConfig], + ); + + if (isProcessing) { + return ( + + + + ); + } + + return ( + + + + ); +} + +function createStyles(colors: Colors) { + return StyleSheet.create({ + container: { + flex: 1, + backgroundColor: colors.background, + }, + loading: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, + webview: { + flex: 1, + }, + }); +} + +export default LoginScreen; diff --git a/react-native/sample/src/screens/ProductDetailsScreen.tsx b/react-native/sample/src/screens/ProductDetailsScreen.tsx new file mode 100644 index 00000000..8835a824 --- /dev/null +++ b/react-native/sample/src/screens/ProductDetailsScreen.tsx @@ -0,0 +1,233 @@ +/* +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import React from 'react'; +import { + SafeAreaView, + ScrollView, + View, + StyleSheet, + Text, + Image, + Pressable, + ActivityIndicator, +} from 'react-native'; + +import type {ShopifyProduct} from '../../@types'; +import type {Colors} from '../context/Theme'; +import {useTheme} from '../context/Theme'; +import {useCart} from '../context/Cart'; +import type {NativeStackScreenProps} from '@react-navigation/native-stack'; +import type {RootStackParamList} from '../App'; +import { + AcceleratedCheckoutButtons, + AcceleratedCheckoutWallet, + ApplePayLabel, + useShopifyCheckoutSheet, +} from '@shopify/checkout-sheet-kit'; +import {useConfig} from '../context/Config'; +import {useShopifyEventHandlers} from '../hooks/useCheckoutEventHandlers'; + +type Props = NativeStackScreenProps; + +function ProductDetailsScreen({route}: Props) { + const {colors, cornerRadius} = useTheme(); + const {addToCart, addingToCart} = useCart(); + const styles = createStyles(colors, cornerRadius); + + if (!route?.params) { + return null; + } + + return ( + + + + + + ); +} + +function getVariant(node: ShopifyProduct) { + return node.variants.edges[0]?.node; +} + +function ProductDetails({ + product, + onAddToCart, + loading = false, +}: { + product: ShopifyProduct; + loading?: boolean; + onAddToCart: (variantId: string) => void; +}) { + const {appConfig} = useConfig(); + const {colors, cornerRadius} = useTheme(); + const styles = createStyles(colors, cornerRadius); + const image = product.images?.edges[0]?.node; + const variant = getVariant(product); + const {acceleratedCheckoutsAvailable} = useShopifyCheckoutSheet(); + + const eventHandlers = useShopifyEventHandlers( + 'PDP - AcceleratedCheckoutButtons', + ); + + return ( + + {image?.url && ( + {image?.altText} + )} + + + {product.title} + + {product.description.slice(0, 100)}... + + + + + {acceleratedCheckoutsAvailable && variant?.id && ( + + )} + + variant?.id && onAddToCart(variant.id)}> + {loading ? ( + + ) : ( + + Add to cart + + )} + + + + + ); +} + +export default ProductDetailsScreen; + +function createStyles(colors: Colors, cornerRadius: number) { + return StyleSheet.create({ + container: { + maxHeight: '100%', + }, + scrollView: { + paddingBottom: 55, + }, + productItem: { + flex: 1, + flexDirection: 'column', + marginBottom: 10, + padding: 20, + backgroundColor: colors.backgroundSubdued, + borderRadius: 5, + }, + productText: { + paddingTop: 10, + flexShrink: 1, + flexGrow: 1, + color: colors.textSubdued, + justifyContent: 'space-between', + }, + productTitle: { + fontSize: 20, + marginTop: 10, + marginBottom: 5, + marginHorizontal: 5, + fontWeight: 'bold', + lineHeight: 28, + textAlign: 'left', + color: colors.text, + }, + productDescription: { + fontSize: 16, + marginTop: 5, + marginBottom: 10, + marginHorizontal: 5, + lineHeight: 20, + textAlign: 'left', + color: colors.text, + }, + productPrice: { + fontSize: 14, + flex: 1, + color: colors.textSubdued, + }, + productImage: { + width: '100%', + height: 400, + marginTop: 5, + borderRadius: cornerRadius, + }, + buttonContainer: { + marginTop: 20, + gap: 8, + }, + addToCartButton: { + borderRadius: cornerRadius, + backgroundColor: colors.secondary, + paddingHorizontal: 10, + paddingVertical: 14, + height: 48, + }, + addToCartButtonText: { + fontSize: 20, + lineHeight: 24, + color: colors.secondaryText, + fontWeight: 'bold', + textAlign: 'center', + }, + }); +} diff --git a/react-native/sample/src/screens/SettingsScreen.tsx b/react-native/sample/src/screens/SettingsScreen.tsx new file mode 100644 index 00000000..5de4ce4c --- /dev/null +++ b/react-native/sample/src/screens/SettingsScreen.tsx @@ -0,0 +1,547 @@ +/* +MIT License + +Copyright 2023 - Present, Shopify Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +import React, {useCallback, useMemo, useEffect, useState} from 'react'; +import { + Pressable, + SafeAreaView, + SectionList, + StyleSheet, + Switch, + Text, + View, +} from 'react-native'; +import pkg from '../../../package.json'; +import Config from 'react-native-config'; +import {useConfig} from '../context/Config'; +import { + ApplePayStyle, + ColorScheme, + useShopifyCheckoutSheet, +} from '@shopify/checkout-sheet-kit'; +import type {Colors} from '../context/Theme'; +import {useTheme} from '../context/Theme'; +import {useNavigation} from '@react-navigation/native'; +import {useAuth} from '../context/Auth'; +import { + BuyerIdentityMode, + BuyerIdentityModeDisplayNames, +} from '../auth/types'; + +enum SectionType { + Switch = 'switch', + SingleSelect = 'single-select', + Text = 'text', +} + +interface SwitchItem { + type: SectionType.Switch; + title: string; + description?: string; + value: boolean; + handler: () => void; +} + +interface SingleSelectItem { + type: SectionType.SingleSelect; + title: string; + value: ColorScheme | BuyerIdentityMode | ApplePayStyle; + selected: boolean; +} + +interface TextItem { + type: SectionType.Text; + title: string; + value?: string; +} + +function isSwitchItem(item: any): item is SwitchItem { + return item.type === SectionType.Switch; +} + +function isSingleSelectItem(item: any): item is SingleSelectItem { + return item.type === SectionType.SingleSelect; +} + +function isTextItem(item: any): item is TextItem { + return item.type === SectionType.Text; +} + +interface SectionData { + title: string; + footer?: string; + data: readonly (SwitchItem | SingleSelectItem | TextItem)[]; +} + +function SettingsScreen() { + const shopify = useShopifyCheckoutSheet(); + const {appConfig, setAppConfig} = useConfig(); + const {colors, setColorScheme} = useTheme(); + const styles = createStyles(colors); + const [preloadingEnabled, setPreloadingEnabled] = useState(false); + + useEffect(() => { + async function loadConfig() { + const config = await shopify.getConfig(); + setPreloadingEnabled(config?.preloading ?? false); + } + loadConfig(); + }, [shopify]); + + const handleColorSchemeChange = useCallback( + (item: SingleSelectItem) => { + setAppConfig({ + ...appConfig, + colorScheme: item.value as ColorScheme, + }); + setColorScheme(item.value as ColorScheme); + }, + [appConfig, setAppConfig, setColorScheme], + ); + + const handleTogglePreloading = useCallback(async () => { + const currentConfig = await shopify.getConfig(); + const newPreloadingValue = !currentConfig?.preloading; + shopify.setConfig({ + ...currentConfig, + preloading: newPreloadingValue, + }); + setPreloadingEnabled(newPreloadingValue); + }, [shopify]); + + const {isAuthenticated, customerEmail, tokenExpiresAt, logout} = useAuth(); + + const handleApplePayStyleChange = useCallback( + (item: SingleSelectItem) => { + setAppConfig({ + ...appConfig, + applePayStyle: item.value as ApplePayStyle, + }); + }, + [appConfig, setAppConfig], + ); + + const handleBuyerIdentityModeChange = useCallback( + (item: SingleSelectItem) => { + const newMode = item.value as BuyerIdentityMode; + if ( + appConfig.buyerIdentityMode === BuyerIdentityMode.CustomerAccount && + newMode !== BuyerIdentityMode.CustomerAccount + ) { + logout(); + } + setAppConfig({ + ...appConfig, + buyerIdentityMode: newMode, + }); + }, + [appConfig, logout, setAppConfig], + ); + + const configurationOptions: readonly SwitchItem[] = useMemo( + () => [ + { + title: 'Preload checkout', + type: SectionType.Switch, + value: preloadingEnabled, + handler: handleTogglePreloading, + }, + ], + [preloadingEnabled, handleTogglePreloading], + ); + + const buyerIdentityOptions: readonly SingleSelectItem[] = useMemo( + () => + Object.values(BuyerIdentityMode).map(mode => ({ + title: BuyerIdentityModeDisplayNames[mode], + type: SectionType.SingleSelect as const, + value: mode, + selected: appConfig.buyerIdentityMode === mode, + })), + [appConfig.buyerIdentityMode], + ); + + const themeOptions: readonly SingleSelectItem[] = useMemo( + () => [ + { + title: 'Automatic', + type: SectionType.SingleSelect, + value: ColorScheme.automatic, + selected: appConfig.colorScheme === ColorScheme.automatic, + }, + { + title: 'Light', + type: SectionType.SingleSelect, + value: ColorScheme.light, + selected: appConfig.colorScheme === ColorScheme.light, + }, + { + title: 'Dark', + type: SectionType.SingleSelect, + value: ColorScheme.dark, + selected: appConfig.colorScheme === ColorScheme.dark, + }, + { + title: 'Web', + type: SectionType.SingleSelect, + value: ColorScheme.web, + selected: appConfig.colorScheme === ColorScheme.web, + }, + ], + [appConfig.colorScheme], + ); + + const applePayStyleDisplayNames: Record = useMemo( + () => ({ + [ApplePayStyle.automatic]: 'Automatic', + [ApplePayStyle.black]: 'Black', + [ApplePayStyle.white]: 'White', + [ApplePayStyle.whiteOutline]: 'White Outline', + }), + [], + ); + + const applePayStyleOptions: readonly SingleSelectItem[] = useMemo( + () => + Object.values(ApplePayStyle).map(style => ({ + title: applePayStyleDisplayNames[style], + type: SectionType.SingleSelect as const, + value: style, + selected: appConfig.applePayStyle === style, + })), + [appConfig.applePayStyle, applePayStyleDisplayNames], + ); + + const informationalItems: readonly TextItem[] = useMemo( + () => [ + { + title: 'SDK version', + type: SectionType.Text, + value: shopify.version, + }, + { + title: 'App version', + type: SectionType.Text, + value: pkg.version, + }, + { + title: 'Storefront Domain', + type: SectionType.Text, + value: Config.STOREFRONT_DOMAIN || 'undefined', + }, + ], + [shopify.version], + ); + + const sections: SectionData[] = useMemo( + () => [ + { + title: 'Features', + data: configurationOptions, + }, + { + title: 'Authentication', + footer: + 'Prefills buyer identity at checkout. Changing this setting will clear your cart.', + data: buyerIdentityOptions, + }, + { + title: 'Theme', + data: themeOptions, + }, + { + title: 'Apple Pay Style', + footer: + 'Configures the visual style of the Apple Pay button.', + data: applePayStyleOptions, + }, + { + title: 'Versions', + data: informationalItems, + }, + ], + [ + themeOptions, + configurationOptions, + buyerIdentityOptions, + applePayStyleOptions, + informationalItems, + ], + ); + + return ( + + item.title} + renderItem={({item, section}) => { + if (isSwitchItem(item)) { + return ( + + ); + } + + if (isSingleSelectItem(item)) { + const sectionHandlers: Record void> = { + Authentication: handleBuyerIdentityModeChange, + 'Apple Pay Style': handleApplePayStyleChange, + }; + const handler = sectionHandlers[section.title] ?? handleColorSchemeChange; + return ( + handler(item)} + /> + ); + } + + if (isTextItem(item)) { + return ; + } + + return null; + }} + renderSectionHeader={({section: {title}}) => ( + + {title} + + )} + renderSectionFooter={({section}) => { + const isAuthSection = section.title === 'Authentication'; + return ( + + {isAuthSection && ( + + )} + {section.footer ? ( + + + {section.footer} + + + ) : null} + + ); + }} + /> + + ); +} + +interface SwitchItemProps { + item: SwitchItem; + styles: ReturnType; + onChange: () => void; +} + +interface SelectItemProps { + item: SingleSelectItem; + styles: ReturnType; + onPress: () => void; +} + +interface TextItemProps { + item: TextItem; + styles: ReturnType; +} + +function SwitchItem({item, styles, onChange}: SwitchItemProps) { + return ( + + + {item.title} + + + {item.description && ( + {item.description} + )} + + ); +} + +function SelectItem({item, styles, onPress}: SelectItemProps) { + return ( + + {item.title} + + {item.selected && } + + ); +} + +function TextItem({item, styles}: TextItemProps) { + return ( + + {item.title} + {item.value} + + ); +} + +interface BuyerIdentityDetailsProps { + mode: BuyerIdentityMode; + isAuthenticated: boolean; + customerEmail: string | null; + tokenExpiresAt: number | null; + styles: ReturnType; +} + +function BuyerIdentityDetails({ + mode, + isAuthenticated: authenticated, + customerEmail: email, + tokenExpiresAt: expiresAt, + styles, +}: BuyerIdentityDetailsProps) { + const navigation = useNavigation(); + + switch (mode) { + case BuyerIdentityMode.Guest: + return null; + case BuyerIdentityMode.Hardcoded: + return ( + + + Populates Cart Buyer Identity with values from .env + + + ); + case BuyerIdentityMode.CustomerAccount: + if (authenticated) { + return ( + + + Changing Buyer Identity will log you out. + + + + User: {email ?? 'Unknown'} + + navigation.navigate('Account' as never)}> + Change user + + + {expiresAt && ( + + Expires: {new Date(expiresAt).toLocaleString()} + + )} + + ); + } + return ( + + navigation.navigate('Account' as never)}> + Sign in on the + Account tab + + + ); + } +} + +function createStyles(colors: Colors) { + return StyleSheet.create({ + list: { + borderColor: colors.border, + borderTopWidth: 1, + }, + listItem: { + flex: 1, + flexDirection: 'row', + justifyContent: 'space-between', + alignItems: 'center', + paddingHorizontal: 16, + paddingVertical: 10, + padding: 10, + backgroundColor: colors.backgroundSubdued, + borderColor: colors.border, + borderBottomWidth: 1, + }, + listItemText: { + flex: 1, + fontSize: 16, + color: colors.text, + }, + listItemDescription: { + color: colors.textSubdued, + fontSize: 12, + paddingHorizontal: 16, + paddingVertical: 10, + }, + listItemSecondaryText: { + color: colors.textSubdued, + }, + listItemSwitch: {}, + listItemCheck: { + color: colors.secondary, + fontWeight: 'bold', + }, + section: { + paddingHorizontal: 16, + paddingVertical: 20, + }, + sectionText: { + fontSize: 13, + color: '#9f9f9f', + marginTop: 10, + marginBottom: -10, + }, + sectionFooter: { + paddingHorizontal: 16, + paddingTop: 8, + paddingBottom: 4, + }, + sectionFooterText: { + fontSize: 12, + color: colors.textSubdued, + }, + detailRow: { + flexDirection: 'row', + alignItems: 'center', + gap: 4, + }, + linkText: { + fontSize: 12, + color: '#007AFF', + }, + }); +} + +export default SettingsScreen; diff --git a/react-native/sample/src/utils.ts b/react-native/sample/src/utils.ts new file mode 100644 index 00000000..d061a08b --- /dev/null +++ b/react-native/sample/src/utils.ts @@ -0,0 +1,90 @@ +import Config from 'react-native-config'; +import type {AppConfig} from './context/Config'; +import {BuyerIdentityMode} from './auth/types'; + +const { + EMAIL, + ADDRESS_1, + ADDRESS_2, + CITY, + COMPANY, + COUNTRY, + FIRST_NAME, + LAST_NAME, + PROVINCE, + ZIP, + PHONE, +} = Config; + +export function createBuyerIdentityCartInput( + appConfig: AppConfig, + customerAccessToken?: string, +) { + switch (appConfig.buyerIdentityMode) { + case BuyerIdentityMode.Guest: + return {}; + case BuyerIdentityMode.Hardcoded: + return { + buyerIdentity: { + email: EMAIL, + deliveryAddressPreferences: { + deliveryAddress: { + address1: ADDRESS_1, + address2: ADDRESS_2, + city: CITY, + company: COMPANY, + country: COUNTRY, + firstName: FIRST_NAME, + lastName: LAST_NAME, + phone: PHONE, + province: PROVINCE, + zip: ZIP, + }, + }, + }, + }; + case BuyerIdentityMode.CustomerAccount: + if (!customerAccessToken) { + return {}; + } + return { + buyerIdentity: { + customerAccessToken, + }, + }; + } +} + +const fallbackLocale = 'en-CA'; +export function getLocale(): string { + return Intl.DateTimeFormat().resolvedOptions().locale ?? fallbackLocale; +} + +export function currency(amount?: string, currency?: string): string { + if (typeof amount === 'undefined' && typeof currency === 'undefined') { + return ''; + } + + try { + const locale = getLocale(); + return new Intl.NumberFormat(locale, { + style: 'currency', + currency: currency, + }).format(Number(amount ?? 0)); + } catch (error) { + console.error(error); + const currencyCode = currency ? ` ${currency}` : ''; + return `${Number(amount ?? 0).toFixed(2)}` + currencyCode; + } +} + +export function debugLog(message: string, data?: any) { + if (__DEV__) { + console.log(message, data || ''); + } +} + +export function createDebugLogger(name: string) { + return (message: string, data?: any) => + debugLog(`[${name}] ${message}`, data); +} diff --git a/react-native/scripts/check_cache b/react-native/scripts/check_cache new file mode 100755 index 00000000..f00852f3 --- /dev/null +++ b/react-native/scripts/check_cache @@ -0,0 +1,30 @@ +#!/bin/bash + +set -e + +WORKSPACE=$1 +COMMAND=$2 + +if [ -z "$WORKSPACE" ]; then + echo "Workspace not found. Example usage: ./scripts/check_cache.sh sample build:android" + exit 1 +fi + +if [ -z "$COMMAND" ]; then + echo "Command not found. Example usage: ./scripts/check_cache.sh sample build:android" + exit 1 +fi + +TASK_ID="$1#$2" + +# Fetch the turbo cache for the specified command +CACHE=$(pnpm turbo run $2 --cache-dir=".turbo" --dry=json) + +# Check the status of the cache +RESULT=$(echo $CACHE | jq ".tasks[] | select(.taskId==\"$TASK_ID\") | .cache.status") + +if [[ $RESULT == "HIT" ]]; then + echo "1" +else + echo "0" +fi diff --git a/react-native/scripts/compare_snapshot b/react-native/scripts/compare_snapshot new file mode 100755 index 00000000..9a270f76 --- /dev/null +++ b/react-native/scripts/compare_snapshot @@ -0,0 +1,40 @@ +#!/bin/bash + +FILENAME="modules/@shopify/checkout-sheet-kit/package.snapshot.json" +TMP_SNAPSHOT_FILENAME="snapshot.json" + +# Set up some colors to use later +red=$(tput setaf 1) +green=$(tput setaf 2) +reset=$(tput sgr0) + +./scripts/create_snapshot $TMP_SNAPSHOT_FILENAME + +if [ $? -ne 0 ]; then + echo "${red}Failed to create snapshot.${reset}" + exit 1 +fi + +diff $FILENAME $TMP_SNAPSHOT_FILENAME &>/dev/null + +if [ $? -eq 0 ] +then + echo "${green}SUCCESS${reset}: The package files have not changed." +else + echo " + + ${red}ERROR${reset}: The package files have changed. + + If this is intentional, run \"pnpm snapshot\" at the root of this repo to update the \"$FILENAME\" file. + + If it NOT intentional, please manually check the diff below to discern if the file(s) should be included in the public package or not: + + +${green}EXISTING SNAPSHOT${reset} ${red}NEW SNAPSHOT${reset} + " + diff --color --side-by-side $FILENAME $TMP_SNAPSHOT_FILENAME + + exit 1 +fi + +rm $TMP_SNAPSHOT_FILENAME diff --git a/react-native/scripts/copy_license b/react-native/scripts/copy_license new file mode 100755 index 00000000..ad8aa264 --- /dev/null +++ b/react-native/scripts/copy_license @@ -0,0 +1,125 @@ +#!/usr/bin/env ruby + +require 'find' + +## +# Builds the canonical license block that will be inserted at the top of files +# when needed. +def build_license_block + license_body = File.read('LICENSE') + "/*\n#{license_body}*/\n\n" +end + +## +# Normalizes a string by collapsing all whitespace (spaces, tabs, newlines) +# into single spaces and trimming the ends. Used to compare license text while +# ignoring formatting differences such as indentation or wrapping. +def normalize_whitespace(text) + text.gsub(/\s+/, ' ').strip +end + +## +# Given the contents of a file, extracts the very first comment at the top of +# the file if it exists and returns a tuple of: +# - full_comment_with_markers: the raw comment including comment markers +# - inner_comment_text: the text inside the comment with common decorations removed +# - range_end_index: the index where the comment ends in the file contents +# If there is no top-of-file comment, returns [nil, nil, 0]. +def extract_top_comment(content) + # Handle optional UTF-8 BOM at start + content = content.sub(/^\uFEFF/, '') + + if content.start_with?('/*') + end_idx = content.index('*/', 2) + return [nil, nil, 0] unless end_idx + full = content[0..(end_idx + 1)] + inner = full[2..-3] + # Remove leading '*' decoration often found in block comments + inner = inner.lines.map { |line| line.sub(/^\s*\*\s?/, '') }.join + return [full, inner, end_idx + 2] + elsif content.start_with?('//') + lines = [] + consumed = 0 + content.each_line do |line| + break unless line.start_with?('//') + lines << line + consumed += line.bytesize + end + full = lines.join + inner = lines.map { |l| l.sub(/^\/\/\s?/, '') }.join + return [full, inner, consumed] + else + return [nil, nil, 0] + end +end + +## +# Returns true if the provided file content begins with a license comment whose +# normalized text matches the repository LICENSE content, ignoring whitespace. +def license_at_top?(content, normalized_license) + _full, inner, _ = extract_top_comment(content) + return false unless inner + normalize_whitespace(inner) == normalized_license +end + +## +# Processes a single file, ensuring the license block exists at the very top of +# the file. If the file already has the license (with any whitespace formatting), +# it is left unchanged. Otherwise, the license block is prepended above any +# existing content. +def process_file(path, license_block, normalized_license, write: true) + content = File.read(path) + + # If license already at the very top (ignoring whitespace differences), do nothing. + return false if license_at_top?(content, normalized_license) + + # Remove leading BOM and leading blank lines to place license at true top. + content = content.sub(/^\uFEFF/, '') + content = content.sub(/^\n+/, '') + + updated = license_block + content + if write + File.write(path, updated) + end + true +end + +## +# Iterates through a directory tree and applies the license update for supported +# file extensions. Returns the list of files that would be or were modified. +def copy_license(dir, license_block, normalized_license, check_only: false) + modified = [] + supported_exts = %w[.swift .h .mm .java .js .ts .tsx] + + Find.find(dir) do |path| + next unless File.file?(path) && supported_exts.any? { |ext| path.end_with?(ext) } + + changed = process_file(path, license_block, normalized_license, write: !check_only) + if changed + modified << path + puts("[copy_license] #{check_only ? 'would update' : 'updated'} #{path}") + end + end + + modified +end + +license_block = build_license_block +normalized_license = normalize_whitespace(File.read('LICENSE')) + +check_only = ARGV.include?('--check') + +modified = [] +modified += copy_license('modules/@shopify/checkout-sheet-kit/ios', license_block, normalized_license, check_only: check_only) +modified += copy_license('modules/@shopify/checkout-sheet-kit/android', license_block, normalized_license, check_only: check_only) +modified += copy_license('modules/@shopify/checkout-sheet-kit/src', license_block, normalized_license, check_only: check_only) + +if check_only + if modified.empty? + puts('[copy_license] all files compliant') + exit 0 + else + puts("[copy_license] non-compliant files: #{modified.count}") + exit 1 + end +end diff --git a/react-native/scripts/create_snapshot b/react-native/scripts/create_snapshot new file mode 100755 index 00000000..878ff422 --- /dev/null +++ b/react-native/scripts/create_snapshot @@ -0,0 +1,5 @@ +#!/bin/bash + +OUTPUT_FILE=${1:-modules/@shopify/checkout-sheet-kit/package.snapshot.json} + +(cd modules/@shopify/checkout-sheet-kit && npm pack --dry-run --json) | jq ".[0].files | map(.path)" > $OUTPUT_FILE diff --git a/react-native/scripts/lint_swift b/react-native/scripts/lint_swift new file mode 100755 index 00000000..1995895f --- /dev/null +++ b/react-native/scripts/lint_swift @@ -0,0 +1,89 @@ +#!/bin/bash + +DIR=modules/@shopify/checkout-sheet-kit +MODE="${1:-check}" + +# Validate the mode +if [[ "$MODE" != "check" && "$MODE" != "fix" ]]; then + echo "❌ Invalid mode: $MODE" + echo "Usage: $0 [check|fix]" + echo " check: Run linters in check mode (default)" + echo " fix: Run linters in fix mode to auto-fix issues" + exit 1 +fi + +# Function to provide installation instructions +print_install_instructions() { + echo "🔧 FIX:" + echo " Shopify employee? Run 'dev up'" + echo " Not a Shopify employee? Install via homebrew:" + echo " - SwiftLint: 'brew install swiftlint' / https://github.com/realm/SwiftLint" + echo " - SwiftFormat: 'brew install swiftformat' / https://github.com/nicklockwood/SwiftFormat" +} + +# Check for SwiftLint +if ! which swiftlint >/dev/null; then + echo "⚠️ WARN: SwiftLint not installed" + print_install_instructions + exit 1 +fi + +# Check for SwiftFormat +if ! which swiftformat >/dev/null; then + echo "⚠️ WARN: SwiftFormat not installed" + print_install_instructions + exit 1 +fi + +# Run SwiftLint +if [[ "$MODE" == "check" ]]; then + echo "🔄 Running SwiftLint in check mode..." + swiftlint lint --strict $DIR --config .swiftlint.yml + LINT_STATUS=$? +else + echo "🔄 Running SwiftLint in fix mode..." + swiftlint lint --fix $DIR --config .swiftlint.yml + LINT_STATUS=$? +fi +echo "SwiftLint exit status: $LINT_STATUS" + +# Run SwiftFormat +if [[ "$MODE" == "check" ]]; then + echo "🔄 Running SwiftFormat in check mode..." + swiftformat $DIR --lint --config .swiftformat + FORMAT_STATUS=$? +else + echo "🔄 Running SwiftFormat in fix mode..." + swiftformat $DIR --config .swiftformat + FORMAT_STATUS=$? +fi +echo "SwiftFormat exit status: $FORMAT_STATUS" + +# Function to print error messages for linting issues +print_linting_error() { + local tool_name=$1 + echo "❌ $tool_name detected issues that need to be fixed." + echo "🔧 How to fix:" + echo " Shopify employee? Run 'dev fix' or 'dev check' to see detailed output" + echo " Not a Shopify employee? Run './scripts/lint_swift fix' to auto-fix issues" + if [[ "$tool_name" == "SwiftLint" ]]; then + echo " Then fix any remaining non-autofixable issues manually" + fi +} + +# Handle exit codes for check mode +if [[ "$MODE" == "check" ]]; then + if [ $LINT_STATUS -ne 0 ]; then + print_linting_error "SwiftLint" + exit 1 + fi + + if [ $FORMAT_STATUS -ne 0 ]; then + print_linting_error "SwiftFormat" + exit 1 + fi + + echo "✅ All linting checks passed!" +else + echo "✅ Linting fixes applied!" +fi diff --git a/react-native/tsconfig.json b/react-native/tsconfig.json new file mode 100644 index 00000000..8a05ab81 --- /dev/null +++ b/react-native/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "rootDir": ".", + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "jsx": "react", + "lib": ["esnext"], + "module": "esnext", + "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noImplicitUseStrict": false, + "noStrictGenericChecks": false, + "noUncheckedIndexedAccess": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "verbatimModuleSyntax": true, + "target": "esnext" + } +} diff --git a/react-native/turbo.json b/react-native/turbo.json new file mode 100644 index 00000000..2d8a88a3 --- /dev/null +++ b/react-native/turbo.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://turbo.build/schema.json", + "pipeline": { + "build": { + "outputs": ["./modules/@shopify/checkout-sheet-kit/lib/**"] + }, + "lint": {}, + "build:android": { + "dependsOn": ["build", "lint"], + "outputs": ["sample/android/app/build"] + }, + "build:ios": { + "dependsOn": ["build", "lint"], + "outputs": ["sample/ios/build"] + }, + "test:ios": { + "dependsOn": ["build", "lint"], + "cache": false + }, + "test:android": { + "dependsOn": ["build", "lint"], + "cache": false + } + } +}