diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 99c0192..8cbce70 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -9,6 +9,7 @@ # Adapter packages (React Native) /packages/google-signin/ @IronTony +/packages/apple-signin/ @IronTony # Examples /examples/expo/ @IronTony diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 3c34696..7accfb8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -15,6 +15,7 @@ body: options: - "@forward-software/react-auth" - "@forward-software/react-auth-google" + - "@forward-software/react-auth-apple" validations: required: true diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index c4925b6..337b26d 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -15,6 +15,7 @@ body: options: - "@forward-software/react-auth" - "@forward-software/react-auth-google" + - "@forward-software/react-auth-apple" validations: required: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2969df5..817aca2 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -21,6 +21,7 @@ updates: - "/" - "/lib" - "/packages/google-signin" + - "/packages/apple-signin" exclude-paths: - "examples/*" schedule: diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml index 8c23f7c..628e54e 100644 --- a/.github/workflows/build-test.yml +++ b/.github/workflows/build-test.yml @@ -18,6 +18,7 @@ jobs: package: - "@forward-software/react-auth" - "@forward-software/react-auth-google" + - "@forward-software/react-auth-apple" fail-fast: false runs-on: "ubuntu-latest" @@ -52,6 +53,7 @@ jobs: package: - "@forward-software/react-auth" - "@forward-software/react-auth-google" + - "@forward-software/react-auth-apple" fail-fast: false runs-on: "ubuntu-latest" diff --git a/.release-please-manifest.json b/.release-please-manifest.json index cabd10c..75e80a5 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1 +1 @@ -{"lib":"2.0.4","packages/google-signin":"1.1.2"} \ No newline at end of file +{"lib":"2.0.4","packages/google-signin":"1.1.2","packages/apple-signin":"1.0.0"} diff --git a/AGENTS.md b/AGENTS.md index 38098ed..025b258 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,6 +14,7 @@ This is a monorepo for **React Auth**, a library that simplifies authentication | --- | --- | --- | --- | | Core library | `lib/` | `@forward-software/react-auth` | Framework-agnostic auth primitives: `AuthClient` interface, `createAuth()`, `AuthProvider`, `useAuthClient` hook, `EnhancedAuthClient` wrapper with event emitter and state management | | Google Sign-In adapter | `packages/google-signin/` | `@forward-software/react-auth-google` | Ready-made `AuthClient` implementation and `GoogleSignInButton` for Web (Google Identity Services) and React Native (Expo native module) | +| Apple Sign-In adapter | `packages/apple-signin/` | `@forward-software/react-auth-apple` | Ready-made `AuthClient` implementation and `AppleSignInButton` for Web (Sign in with Apple JS) and React Native (Expo native module) | ### Examples diff --git a/README.md b/README.md index 1c49b4b..223a8e9 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ This monorepo contains the following packages: | Package | Version | Description | | --- | --- | --- | | [`@forward-software/react-auth`](./lib) | [![npm](https://img.shields.io/npm/v/@forward-software/react-auth)](https://npmjs.com/package/@forward-software/react-auth) | Core library — provides `AuthClient`, `AuthProvider`, `createAuth`, and `useAuthClient` for integrating authentication flows in any React or React Native app | -| [`@forward-software/react-auth-google`](./packages/google-signin) | [![npm](https://img.shields.io/npm/v/@forward-software/react-auth-google)](https://npmjs.com/package/@forward-software/react-auth-google) | Google Sign-In adapter — ready-made `AuthClient` implementation and drop-in `GoogleSignInButton` for Web and React Native (Expo) | +| [`@forward-software/react-auth-google`](./packages/google-signin) | [![npm](https://img.shields.io/npm/v/@forward-software/react-auth-google)](https://npmjs.com/package/@forward-software/react-auth-google) | Google Sign-In adapter -- ready-made `AuthClient` implementation and drop-in `GoogleSignInButton` for Web and React Native (Expo) | +| [`@forward-software/react-auth-apple`](./packages/apple-signin) | [![npm](https://img.shields.io/npm/v/@forward-software/react-auth-apple)](https://npmjs.com/package/@forward-software/react-auth-apple) | Apple Sign-In adapter -- ready-made `AuthClient` implementation and drop-in `AppleSignInButton` for Web and React Native (Expo) | ## Examples @@ -74,7 +75,7 @@ function LoginButton() { For more details, see the [`@forward-software/react-auth` README](./lib/README.md). -> **Looking for a ready-made integration?** The [`@forward-software/react-auth-google`](./packages/google-signin) package provides a drop-in Google Sign-In adapter with a pre-built `AuthClient` and `GoogleSignInButton` for both Web and React Native. See its [README](./packages/google-signin/README.md) for setup instructions. +> **Looking for a ready-made integration?** The [`@forward-software/react-auth-google`](./packages/google-signin) package provides a drop-in Google Sign-In adapter and the [`@forward-software/react-auth-apple`](./packages/apple-signin) package provides Apple Sign-In -- both with a pre-built `AuthClient` and sign-in button for Web and React Native. See their READMEs for setup instructions. ## Project Structure @@ -84,8 +85,25 @@ This project is a monorepo managed with [pnpm workspaces](https://pnpm.io/worksp react-auth/ ├── lib/ # @forward-software/react-auth (core library) ├── packages/ -│ └── google-signin/ # @forward-software/react-auth-google (Google Sign-In adapter) -└── examples/ # Example applications (base, reqres, refresh-token, expo) +│ ├── google-signin/ # @forward-software/react-auth-google (Google Sign-In adapter) +│ │ ├── src/ +│ │ │ ├── web/ # Web implementation (Google Identity Services) +│ │ │ └── native/ # React Native implementation (Expo module) +│ │ ├── android/ # Android native module +│ │ ├── ios/ # iOS native module +│ │ └── test/ # Unit tests +│ └── apple-signin/ # @forward-software/react-auth-apple (Apple Sign-In adapter) +│ ├── src/ +│ │ ├── web/ # Web implementation (Sign in with Apple JS) +│ │ └── native/ # React Native implementation (Expo module) +│ ├── android/ # Android native module +│ ├── ios/ # iOS native module +│ └── test/ # Unit tests +└── examples/ # Example applications + ├── base/ # Basic React example + ├── reqres/ # ReqRes API example + ├── refresh-token/ # Token refresh example + └── expo/ # React Native (Expo) example ``` For a detailed breakdown of the source layout and architecture, see the [Contributing Guide](CONTRIBUTING.md#project-architecture). diff --git a/SECURITY.md b/SECURITY.md index 9361019..ae04996 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -33,6 +33,7 @@ Security updates are provided for the latest major version of each package: | --- | --- | | `@forward-software/react-auth` | `2.x` | | `@forward-software/react-auth-google` | `1.x` | +| `@forward-software/react-auth-apple` | `1.x` | ## Scope diff --git a/packages/apple-signin/README.md b/packages/apple-signin/README.md new file mode 100644 index 0000000..f375217 --- /dev/null +++ b/packages/apple-signin/README.md @@ -0,0 +1,340 @@ +# @forward-software/react-auth-apple + +> Apple Sign-In adapter for [@forward-software/react-auth](https://github.com/forwardsoftware/react-auth) - Web and React Native + +[![license](https://img.shields.io/github/license/forwardsoftware/react-auth.svg)](https://github.com/forwardsoftware/react-auth/blob/main/LICENSE) [![Build & Test](https://github.com/forwardsoftware/react-auth/actions/workflows/build-test.yml/badge.svg)](https://github.com/forwardsoftware/react-auth/actions/workflows/build-test.yml) [![Github Issues](https://img.shields.io/github/issues/forwardsoftware/react-auth.svg)](https://github.com/forwardsoftware/react-auth/issues) + +[![npm](https://img.shields.io/npm/v/@forward-software/react-auth-apple)](https://npmjs.com/package/@forward-software/react-auth-apple) [![NPM downloads](https://img.shields.io/npm/dm/@forward-software/react-auth-apple.svg)](https://npmjs.com/package/@forward-software/react-auth-apple) + +Self-contained Apple Sign-In integration with no external auth wrapper dependencies. Provides a ready-made `AuthClient` implementation and a drop-in `AppleSignInButton` for both platforms. + +## Installation + +```bash +npm install @forward-software/react-auth-apple @forward-software/react-auth +# or +pnpm add @forward-software/react-auth-apple @forward-software/react-auth +``` + +### React Native (Expo) + +This package includes an Expo native module. You need a **development build** (not Expo Go): + +```bash +npx expo prebuild +npx expo run:ios +``` + +No additional CocoaPods are required -- Apple Sign-In uses the system `AuthenticationServices` framework. + +## Quick Start + +### Web + +```tsx +import { createAuth } from '@forward-software/react-auth'; +import { AppleAuthClient, AppleSignInButton } from '@forward-software/react-auth-apple'; + +const appleClient = new AppleAuthClient({ + clientId: 'com.example.service', // Your Apple Services ID + redirectURI: 'https://example.com/auth/apple/callback', +}); + +const { AuthProvider, useAuthClient } = createAuth(appleClient); + +function App() { + return ( + + + + ); +} + +function LoginScreen() { + const auth = useAuthClient(); + + return ( + auth.login(credentials)} + onError={(error) => console.error(error)} + /> + ); +} +``` + +### React Native + +```tsx +import { createAuth } from '@forward-software/react-auth'; +import { AppleAuthClient, AppleSignInButton } from '@forward-software/react-auth-apple'; +import { MMKV } from 'react-native-mmkv'; + +const mmkv = new MMKV(); +const storage = { + getItem: (key: string) => mmkv.getString(key) ?? null, + setItem: (key: string, value: string) => mmkv.set(key, value), + removeItem: (key: string) => mmkv.delete(key), +}; + +const appleClient = new AppleAuthClient({ + clientId: 'com.example.app', + storage, +}); + +const { AuthProvider, useAuthClient } = createAuth(appleClient); + +function LoginScreen() { + const auth = useAuthClient(); + + return ( + auth.login(credentials)} + onError={(error) => console.error(error)} + /> + ); +} +``` + +## Web Setup + +1. Register a **Services ID** in the [Apple Developer Console](https://developer.apple.com/account/resources/identifiers/list/serviceId) +2. Configure the **Sign in with Apple** capability with your domain and redirect URL +3. Pass the Services ID as `clientId` and your registered redirect URL as `redirectURI` + +## React Native Setup + +### iOS + +No additional setup is needed beyond enabling the **Sign in with Apple** capability in your Xcode project: + +1. Open your project in Xcode +2. Go to **Signing & Capabilities** +3. Click **+ Capability** and add **Sign in with Apple** + +### Android + +Apple Sign-In on Android uses a web-based OAuth flow via Chrome Custom Tabs. Because Apple uses `response_mode=form_post`, a **backend proxy** is required to receive Apple's POST response and redirect it back to your app via a deep link. + +#### How it works + +1. The app opens a Chrome Custom Tab pointing to Apple's authorization page +2. The user signs in on Apple's page +3. Apple **POSTs** the `id_token` and `code` to your registered redirect URI (your backend) +4. Your backend extracts the tokens from the POST body and redirects to your app using a deep link +5. The app receives the deep link, calls `handleCallback()`, and the sign-in promise resolves + +#### 1. Apple Developer Console + +You need a **Services ID** (separate from your App ID): + +1. Go to [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list/serviceId) +2. Create or select a **Services ID** (e.g., `com.example.app.service`) +3. Enable **Sign in with Apple** and click **Configure** +4. Set the **Primary App ID** to your app (it must have Sign in with Apple capability enabled) +5. Add your backend's **Domain** (e.g., `api.example.com`) +6. Add the **Return URL** pointing to your backend callback endpoint (e.g., `https://api.example.com/auth/apple/callback`) + +#### 2. Backend proxy + +A minimal Express server that receives Apple's POST and redirects to your app: + +```js +const express = require('express'); +const app = express(); + +const APP_SCHEME = 'myapp'; // Your app's URL scheme + +app.use(express.urlencoded({ extended: true })); + +app.post('/auth/apple/callback', (req, res) => { + const { id_token, code, state, user } = req.body; + + const params = new URLSearchParams(); + if (id_token) params.set('id_token', id_token); + if (code) params.set('code', code); + if (state) params.set('state', state); + if (user) params.set('user', user); + + // Redirect to the app via deep link + res.redirect(302, `${APP_SCHEME}://auth/apple?${params.toString()}`); +}); + +app.listen(3000); +``` + +#### 3. App URL scheme + +Make sure your app has a URL scheme configured in `app.json`: + +```json +{ + "expo": { + "scheme": "myapp" + } +} +``` + +#### 4. Deep link handler + +Create a route to handle the callback deep link. With Expo Router, add a file at `app/auth/apple.tsx`: + +```tsx +import { useEffect } from 'react'; +import { Platform } from 'react-native'; +import { useLocalSearchParams, router } from 'expo-router'; +import { AppleSignInModule } from '@forward-software/react-auth-apple'; + +export default function AppleCallback() { + const params = useLocalSearchParams<{ id_token?: string; code?: string; user?: string }>(); + + useEffect(() => { + if (Platform.OS === 'android' && params.id_token) { + AppleSignInModule.handleCallback({ + id_token: params.id_token, + code: params.code, + user: params.user, + }); + } + router.replace('/login'); + }, [params]); + + return null; +} +``` + +When the deep link `myapp://auth/apple?id_token=...&code=...` arrives, Expo Router matches it to this route. The route calls `handleCallback()` to resolve the pending sign-in promise, then navigates to the login screen where the auth state updates. + +#### 5. App configuration + +Pass the `androidRedirectUri` in both the client and button config. On Android, use the Services ID as `clientId` (not your app bundle ID): + +```tsx +import { Platform } from 'react-native'; + +const ANDROID_REDIRECT_URI = 'https://api.example.com/auth/apple/callback'; + +const appleClient = new AppleAuthClient({ + clientId: Platform.OS === 'android' ? 'com.example.app.service' : 'com.example.app', + storage, + ...(Platform.OS === 'android' && { androidRedirectUri: ANDROID_REDIRECT_URI }), +}); +``` + +```tsx + auth.login(credentials)} + onError={(error) => console.error(error)} +/> +``` + +> **Note:** On iOS, the `clientId` should be your App Bundle ID. On Android, it must be the Apple Services ID since the flow uses web-based OAuth. + +## Button Props + +### Web (`AppleSignInButton`) + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `config` | `AppleWebAuthConfig` | required | Apple Sign-In configuration | +| `onCredential` | `(credentials) => void` | required | Called with credentials on success | +| `onError` | `(error) => void` | - | Called on error | +| `color` | `'black' \| 'white' \| 'white-outline'` | `'black'` | Button color scheme | +| `type` | `'sign-in' \| 'continue' \| 'sign-up'` | `'sign-in'` | Button label type | +| `label` | `string` | Based on `type` | Custom label for localization | +| `width` | `number` | auto | Button width in pixels | +| `height` | `number` | `44` | Button height in pixels | + +### React Native (`AppleSignInButton`) + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `config` | `AppleNativeAuthConfig` | required | Apple Sign-In configuration | +| `onCredential` | `(credentials) => void` | required | Called with credentials on success | +| `onError` | `(error) => void` | - | Called on error | +| `style` | `StyleProp` | - | Additional button styles | +| `disabled` | `boolean` | `false` | Disable the button | +| `color` | `'black' \| 'white'` | `'black'` | Button color scheme | +| `label` | `string` | `'Sign in with Apple'` | Custom label for localization | + +## Manual Integration + +You can use the SDK wrapper directly for custom flows: + +```ts +// Web +import { loadAppleIdScript, initializeAppleAuth, signInWithApple } from '@forward-software/react-auth-apple/web/appleid'; + +await loadAppleIdScript(); +initializeAppleAuth({ + clientId: 'com.example.service', + scope: 'name email', + redirectURI: 'https://example.com/callback', + usePopup: true, +}); +const response = await signInWithApple(); +``` + +```ts +// React Native +import { Platform } from 'react-native'; +import { AppleSignInModule } from '@forward-software/react-auth-apple'; + +AppleSignInModule.configure({ scopes: ['name', 'email'] }); +const credentials = await AppleSignInModule.signIn(); + +// getCredentialState is iOS-only; it rejects with UNSUPPORTED on Android +if (Platform.OS === 'ios') { + const state = await AppleSignInModule.getCredentialState(credentials.user); +} +``` + +## Token Behavior + +- **Identity Token**: Apple issues a JWT `identityToken` (similar to Google's `idToken`). The `exp` claim is extracted automatically for expiration tracking. +- **First Authorization Only**: Apple provides user info (email, name) only on the **first** authorization. Subsequent sign-ins return only the `identityToken` and `user` ID. Store user info on your backend after the first login. +- **Credential State**: On iOS, you can check if the user's Apple ID is still authorized via `getCredentialState()`. This is used during token refresh instead of silent re-authentication. +- **No Client-Side Refresh**: Apple does not support client-side token refresh. When the identity token expires, the user must re-authenticate. + +## API Reference + +### Types + +- `AppleAuthTokens` - Token object stored after sign-in +- `AppleAuthCredentials` - Credentials passed to `login()` +- `AppleFullName` - Structured name (givenName, familyName, etc.) +- `AppleWebAuthConfig` - Web configuration +- `AppleNativeAuthConfig` - Native configuration +- `AppleScope` - `'name' | 'email'` +- `TokenStorage` - Storage interface for persistence + +### Classes + +- `AppleAuthClient` - Implements `AuthClient` + +### Functions (Web SDK) + +- `loadAppleIdScript()` - Load the Apple JS SDK +- `initializeAppleAuth(config)` - Initialize the SDK +- `signInWithApple()` - Trigger sign-in flow + +### Functions (Native Module) + +- `AppleSignInModule.configure(config)` - Configure the native module +- `AppleSignInModule.signIn()` - Trigger native sign-in +- `AppleSignInModule.getCredentialState(userID)` - Check credential state (iOS only) +- `AppleSignInModule.handleCallback(params)` - Complete a pending Android sign-in from a deep-link callback +- `AppleSignInModule.signOut()` - Sign out (no-op, clears JS-side storage) + +## License + +MIT diff --git a/packages/apple-signin/android/build.gradle b/packages/apple-signin/android/build.gradle new file mode 100644 index 0000000..fbe7dc2 --- /dev/null +++ b/packages/apple-signin/android/build.gradle @@ -0,0 +1,35 @@ +apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' + +group = 'expo.modules.applesignin' +version = '1.0.0' + +android { + namespace "expo.modules.applesignin" + compileSdkVersion safeExtGet("compileSdkVersion", 34) + + defaultConfig { + minSdkVersion safeExtGet("minSdkVersion", 23) + targetSdkVersion safeExtGet("targetSdkVersion", 34) + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + + kotlinOptions { + jvmTarget = "17" + } +} + +dependencies { + implementation project(':expo-modules-core') + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${safeExtGet('kotlinVersion', '1.9.24')}" + + implementation 'androidx.browser:browser:1.8.0' +} + +def safeExtGet(prop, fallback) { + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback +} diff --git a/packages/apple-signin/android/src/main/java/expo/modules/applesignin/AppleSignInModule.kt b/packages/apple-signin/android/src/main/java/expo/modules/applesignin/AppleSignInModule.kt new file mode 100644 index 0000000..e30b479 --- /dev/null +++ b/packages/apple-signin/android/src/main/java/expo/modules/applesignin/AppleSignInModule.kt @@ -0,0 +1,145 @@ +package expo.modules.applesignin + +import android.net.Uri +import android.os.Handler +import android.os.Looper +import androidx.browser.customtabs.CustomTabsIntent +import expo.modules.kotlin.Promise +import expo.modules.kotlin.exception.CodedException +import expo.modules.kotlin.modules.Module +import expo.modules.kotlin.modules.ModuleDefinition + +/** + * Apple Sign-In on Android uses web-based OAuth via Custom Tabs. + * + * Apple's authorization endpoint uses response_mode=form_post, which means + * the response is POSTed to your redirectUri. A backend intermediary is required + * to convert this POST into a deep link redirect back to your app with the + * id_token and authorization code as query parameters. + */ +class AppleSignInModule : Module() { + private var clientId: String? = null + private var redirectUri: String? = null + private var scopes: List = listOf("name", "email") + private var nonce: String? = null + private var state: String? = null + private var pendingPromise: Promise? = null + private var signInInProgress = false + private val handler = Handler(Looper.getMainLooper()) + + override fun definition() = ModuleDefinition { + Name("AppleSignIn") + + OnActivityEntersForeground { + if (signInInProgress) { + // Delay slightly to allow handleCallback to resolve the promise first + // if the user completed sign-in (deep link fires before this). + handler.postDelayed({ + if (signInInProgress) { + signInInProgress = false + pendingPromise?.reject(CodedException("CANCELLED", "Sign-in was cancelled by the user", null)) + pendingPromise = null + } + }, 1000) + } + } + + Function("configure") { config: Map -> + clientId = config["clientId"] as? String + redirectUri = config["redirectUri"] as? String + scopes = (config["scopes"] as? List<*>)?.filterIsInstance() ?: listOf("name", "email") + nonce = config["nonce"] as? String + state = config["state"] as? String + } + + AsyncFunction("signIn") { promise: Promise -> + val cid = clientId + if (cid == null) { + promise.reject(CodedException("NOT_CONFIGURED", "AppleSignIn has not been configured with a clientId. Call configure() first.", null)) + return@AsyncFunction + } + + val redirect = redirectUri + if (redirect == null) { + promise.reject(CodedException("MISSING_REDIRECT_URI", "androidRedirectUri is required for Apple Sign-In on Android.", null)) + return@AsyncFunction + } + + val activity = appContext.currentActivity + if (activity == null) { + promise.reject(CodedException("NO_ACTIVITY", "No current activity available", null)) + return@AsyncFunction + } + + val scopeString = scopes.joinToString(" ") + val uriBuilder = Uri.parse("https://appleid.apple.com/auth/authorize").buildUpon() + .appendQueryParameter("client_id", cid) + .appendQueryParameter("redirect_uri", redirect) + .appendQueryParameter("response_type", "code id_token") + .appendQueryParameter("response_mode", "form_post") + .appendQueryParameter("scope", scopeString) + + nonce?.let { uriBuilder.appendQueryParameter("nonce", it) } + state?.let { uriBuilder.appendQueryParameter("state", it) } + + pendingPromise?.reject(CodedException("CANCELLED", "Sign-in was superseded by a new request", null)) + pendingPromise = promise + signInInProgress = true + + try { + val customTabsIntent = CustomTabsIntent.Builder().build() + customTabsIntent.launchUrl(activity, uriBuilder.build()) + } catch (e: Exception) { + signInInProgress = false + pendingPromise = null + promise.reject(CodedException("SIGN_IN_FAILED", "Failed to launch Apple Sign-In: ${e.message}", e)) + } + } + + /** + * Called from your app's deep link handler after the backend redirects + * with the Apple Sign-In response parameters. + */ + Function("handleCallback") { params: Map -> + signInInProgress = false + val promise = pendingPromise + if (promise == null) { + throw CodedException("NO_PENDING_SIGN_IN", "No pending sign-in to handle", null) + } + + pendingPromise = null + + val identityToken = params["id_token"] as? String + if (identityToken == null) { + promise.reject(CodedException("MISSING_TOKEN", "No identity token in callback", null)) + return@Function + } + + val response = mutableMapOf( + "identityToken" to identityToken, + ) + + val code = params["code"] as? String + if (code != null) { + response["authorizationCode"] = code + } + + val user = params["user"] as? String + if (user != null) { + response["user"] = user + } + + promise.resolve(response) + } + + AsyncFunction("getCredentialState") { _: String, promise: Promise -> + // Apple credential state is not available on Android + promise.reject(CodedException("UNSUPPORTED", "getCredentialState is not supported on Android", null)) + } + + AsyncFunction("signOut") { promise: Promise -> + // Apple has no sign-out API; clearing is handled on the JS side + promise.resolve(null) + } + } +} diff --git a/packages/apple-signin/expo-module.config.json b/packages/apple-signin/expo-module.config.json new file mode 100644 index 0000000..c645847 --- /dev/null +++ b/packages/apple-signin/expo-module.config.json @@ -0,0 +1,9 @@ +{ + "platforms": ["apple", "android"], + "apple": { + "modules": ["AppleSignInModule"] + }, + "android": { + "modules": ["expo.modules.applesignin.AppleSignInModule"] + } +} diff --git a/packages/apple-signin/ios/AppleSignInModule.swift b/packages/apple-signin/ios/AppleSignInModule.swift new file mode 100644 index 0000000..c30db4c --- /dev/null +++ b/packages/apple-signin/ios/AppleSignInModule.swift @@ -0,0 +1,201 @@ +import ExpoModulesCore +import AuthenticationServices +import CryptoKit + +public class AppleSignInModule: Module { + private var scopes: [ASAuthorization.Scope] = [] + private var rawNonce: String? + + public func definition() -> ModuleDefinition { + Name("AppleSignIn") + + Function("configure") { (config: [String: Any]) in + self.scopes = [] + + if let scopeStrings = config["scopes"] as? [String] { + for scope in scopeStrings { + switch scope { + case "name": + self.scopes.append(.fullName) + case "email": + self.scopes.append(.email) + default: + break + } + } + } + + self.rawNonce = config["nonce"] as? String + } + + AsyncFunction("signIn") { (promise: Promise) in + guard let presentingViewController = self.getPresentingViewController() else { + promise.reject(AppleSignInError.noPresentingViewController) + return + } + + let provider = ASAuthorizationAppleIDProvider() + let request = provider.createRequest() + request.requestedScopes = self.scopes + + if let nonce = self.rawNonce { + request.nonce = self.sha256(nonce) + } + + let delegate = SignInDelegate(promise: promise, rawNonce: self.rawNonce) + let controller = ASAuthorizationController(authorizationRequests: [request]) + controller.delegate = delegate + controller.presentationContextProvider = PresentationContextProvider(window: presentingViewController.view.window) + + // Prevent delegate from being deallocated during async flow + objc_setAssociatedObject(controller, "delegate", delegate, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) + + controller.performRequests() + } + + AsyncFunction("getCredentialState") { (userID: String, promise: Promise) in + let provider = ASAuthorizationAppleIDProvider() + provider.getCredentialState(forUserID: userID) { state, error in + if let error = error { + promise.reject(AppleSignInError.credentialStateFailed(error.localizedDescription)) + return + } + + switch state { + case .authorized: + promise.resolve("authorized") + case .revoked: + promise.resolve("revoked") + case .notFound: + promise.resolve("notFound") + case .transferred: + promise.resolve("transferred") + @unknown default: + promise.resolve("notFound") + } + } + } + + AsyncFunction("signOut") { (promise: Promise) in + // Apple has no sign-out API; clearing is handled on the JS side + promise.resolve(nil) + } + } + + private func getPresentingViewController() -> UIViewController? { + guard let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene, + let window = scene.windows.first(where: { $0.isKeyWindow }), + let rootViewController = window.rootViewController else { + return nil + } + + var topController = rootViewController + while let presented = topController.presentedViewController { + topController = presented + } + + return topController + } + + private func sha256(_ input: String) -> String { + let inputData = Data(input.utf8) + let hashed = SHA256.hash(data: inputData) + return hashed.compactMap { String(format: "%02x", $0) }.joined() + } +} + +private class SignInDelegate: NSObject, ASAuthorizationControllerDelegate { + private let promise: Promise + private let rawNonce: String? + + init(promise: Promise, rawNonce: String?) { + self.promise = promise + self.rawNonce = rawNonce + super.init() + } + + func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { + guard let credential = authorization.credential as? ASAuthorizationAppleIDCredential else { + promise.reject(AppleSignInError.signInFailed("Unexpected credential type")) + return + } + + var response: [String: Any] = [:] + + if let identityTokenData = credential.identityToken, + let identityToken = String(data: identityTokenData, encoding: .utf8) { + response["identityToken"] = identityToken + } else { + promise.reject(AppleSignInError.signInFailed("No identity token returned")) + return + } + + if let authCodeData = credential.authorizationCode, + let authorizationCode = String(data: authCodeData, encoding: .utf8) { + response["authorizationCode"] = authorizationCode + } + + response["user"] = credential.user + + if let email = credential.email { + response["email"] = email + } + + if let fullName = credential.fullName { + var nameDict: [String: String] = [:] + if let givenName = fullName.givenName { nameDict["givenName"] = givenName } + if let familyName = fullName.familyName { nameDict["familyName"] = familyName } + if let middleName = fullName.middleName { nameDict["middleName"] = middleName } + if let namePrefix = fullName.namePrefix { nameDict["namePrefix"] = namePrefix } + if let nameSuffix = fullName.nameSuffix { nameDict["nameSuffix"] = nameSuffix } + if let nickname = fullName.nickname { nameDict["nickname"] = nickname } + if !nameDict.isEmpty { + response["fullName"] = nameDict + } + } + + promise.resolve(response) + } + + func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) { + let authError = error as? ASAuthorizationError + if authError?.code == .canceled { + promise.reject(AppleSignInError.cancelled) + } else { + promise.reject(AppleSignInError.signInFailed(error.localizedDescription)) + } + } +} + +private class PresentationContextProvider: NSObject, ASAuthorizationControllerPresentationContextProviding { + private let window: UIWindow? + + init(window: UIWindow?) { + self.window = window + super.init() + } + + func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor { + return window ?? ASPresentationAnchor() + } +} + +enum AppleSignInError: Error, CustomStringConvertible { + case noPresentingViewController + case signInFailed(String) + case cancelled + case credentialStateFailed(String) + + var description: String { + switch self { + case .noPresentingViewController: + return "Could not find a presenting view controller" + case .signInFailed(let message): + return "Sign-in failed: \(message)" + case .cancelled: + return "Sign-in was cancelled by the user" + case .credentialStateFailed(let message): + return "Credential state check failed: \(message)" + } + } +} diff --git a/packages/apple-signin/ios/react-auth-apple.podspec b/packages/apple-signin/ios/react-auth-apple.podspec new file mode 100644 index 0000000..4be7bc0 --- /dev/null +++ b/packages/apple-signin/ios/react-auth-apple.podspec @@ -0,0 +1,20 @@ +require 'json' + +package = JSON.parse(File.read(File.join(__dir__, '..', 'package.json'))) + +Pod::Spec.new do |s| + s.name = 'react-auth-apple' + s.version = package['version'] + s.summary = package['description'] || 'Apple Sign-In for react-auth' + s.homepage = 'https://github.com/forwardsoftware/react-auth' + s.license = package['license'] + s.author = 'ForWarD Software' + s.source = { :git => 'https://github.com/forwardsoftware/react-auth.git', :tag => s.version.to_s } + + s.platform = :ios, '15.1' + s.swift_version = '5.0' + s.source_files = '**/*.{swift,h,m}' + s.frameworks = 'AuthenticationServices', 'CryptoKit' + + s.dependency 'ExpoModulesCore' +end diff --git a/packages/apple-signin/package.json b/packages/apple-signin/package.json new file mode 100644 index 0000000..346368d --- /dev/null +++ b/packages/apple-signin/package.json @@ -0,0 +1,92 @@ +{ + "name": "@forward-software/react-auth-apple", + "description": "Apple Sign-In adapter for @forward-software/react-auth - Web and React Native/Expo", + "version": "1.0.0", + "author": "ForWarD Software (https://forwardsoftware.solutions/)", + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/forwardsoftware/react-auth.git", + "directory": "packages/apple-signin" + }, + "homepage": "https://github.com/forwardsoftware/react-auth/tree/main/packages/apple-signin#readme", + "keywords": [ + "react", + "react-native", + "auth", + "authentication", + "apple", + "apple-sign-in", + "sign-in-with-apple", + "expo", + "expo-modules-core" + ], + "main": "dist/index.js", + "types": "dist/index.d.ts", + "react-native": "dist/index.native.js", + "source": "src/index.ts", + "exports": { + ".": { + "react-native": "./dist/index.native.js", + "default": "./dist/index.js" + }, + "./web/appleid": { + "types": "./dist/web/appleid.d.ts", + "default": "./dist/web/appleid.js" + } + }, + "files": [ + "dist", + "src", + "ios", + "android", + "expo-module.config.json", + "react-auth-apple.podspec" + ], + "scripts": { + "build:code": "tsc --removeComments", + "build:types": "tsc --declaration --emitDeclarationOnly", + "build": "npm-run-all clean build:*", + "lint": "eslint src", + "test": "vitest", + "test:watch": "vitest watch", + "clean": "rimraf dist" + }, + "devDependencies": { + "@forward-software/react-auth": "^2.0.0", + "@testing-library/dom": "^10.4.1", + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@types/node": "^25.2.3", + "@types/react": "catalog:", + "@vitejs/plugin-react": "catalog:", + "expo-modules-core": "^55.0.17", + "jsdom": "catalog:", + "react": "catalog:", + "react-dom": "catalog:", + "react-native": "^0.84.1", + "react-native-svg": "^15.15.4", + "rimraf": "catalog:", + "typescript": "catalog:", + "vite": "catalog:", + "vitest": "catalog:" + }, + "peerDependencies": { + "@forward-software/react-auth": ">=2.0.0", + "expo-modules-core": ">=2.0.0", + "react": ">=16.8", + "react-native": ">=0.73.0", + "react-native-svg": ">=13.0.0" + }, + "peerDependenciesMeta": { + "expo-modules-core": { + "optional": true + }, + "react-native": { + "optional": true + }, + "react-native-svg": { + "optional": true + } + } +} diff --git a/packages/apple-signin/react-auth-apple.podspec b/packages/apple-signin/react-auth-apple.podspec new file mode 100644 index 0000000..1db91b8 --- /dev/null +++ b/packages/apple-signin/react-auth-apple.podspec @@ -0,0 +1,20 @@ +require 'json' + +package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) + +Pod::Spec.new do |s| + s.name = 'react-auth-apple' + s.version = package['version'] + s.summary = package['description'] || 'Apple Sign-In for react-auth' + s.homepage = 'https://github.com/forwardsoftware/react-auth' + s.license = package['license'] + s.author = 'ForWarD Software' + s.source = { :git => 'https://github.com/forwardsoftware/react-auth.git', :tag => s.version.to_s } + + s.platform = :ios, '15.1' + s.swift_version = '5.0' + s.source_files = 'ios/**/*.{swift,h,m}' + s.frameworks = 'AuthenticationServices', 'CryptoKit' + + s.dependency 'ExpoModulesCore' +end diff --git a/packages/apple-signin/src/AppleLogo.native.tsx b/packages/apple-signin/src/AppleLogo.native.tsx new file mode 100644 index 0000000..46cd645 --- /dev/null +++ b/packages/apple-signin/src/AppleLogo.native.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import { View } from 'react-native'; +import Svg, { Path } from 'react-native-svg'; + +const APPLE_PATH = + 'M15.5 14.7c-.4.9-.6 1.3-1.1 2.1-.7 1.1-1.7 2.5-2.9 2.5-1.1 0-1.4-.7-2.9-.7-1.5 0-1.9.7-3 .7-1.2 0-2.1-1.2-2.8-2.3C1.2 14.6.5 11.4 1.6 9.2c.8-1.5 2.1-2.4 3.5-2.4 1.3 0 2.2.8 3.2.8 1 0 1.7-.8 3.1-.8 1.2 0 2.4.7 3.2 1.8-2.8 1.5-2.4 5.5.3 6.7l-.4-.6zM11.3 4.5c.5-.7.9-1.6.8-2.5-.8.1-1.7.5-2.3 1.2-.5.6-1 1.5-.8 2.4.9 0 1.7-.4 2.3-1.1z'; + +export function AppleLogo({ color }: { color: string }) { + return ( + + + + + + ); +} diff --git a/packages/apple-signin/src/AppleLogo.tsx b/packages/apple-signin/src/AppleLogo.tsx new file mode 100644 index 0000000..1ff94ff --- /dev/null +++ b/packages/apple-signin/src/AppleLogo.tsx @@ -0,0 +1,19 @@ +import React from 'react'; + +const APPLE_PATH = + 'M15.5 14.7c-.4.9-.6 1.3-1.1 2.1-.7 1.1-1.7 2.5-2.9 2.5-1.1 0-1.4-.7-2.9-.7-1.5 0-1.9.7-3 .7-1.2 0-2.1-1.2-2.8-2.3C1.2 14.6.5 11.4 1.6 9.2c.8-1.5 2.1-2.4 3.5-2.4 1.3 0 2.2.8 3.2.8 1 0 1.7-.8 3.1-.8 1.2 0 2.4.7 3.2 1.8-2.8 1.5-2.4 5.5.3 6.7l-.4-.6zM11.3 4.5c.5-.7.9-1.6.8-2.5-.8.1-1.7.5-2.3 1.2-.5.6-1 1.5-.8 2.4.9 0 1.7-.4 2.3-1.1z'; + +export function AppleLogo({ color }: { color: string }) { + return ( + + + + ); +} diff --git a/packages/apple-signin/src/index.native.ts b/packages/apple-signin/src/index.native.ts new file mode 100644 index 0000000..4b747da --- /dev/null +++ b/packages/apple-signin/src/index.native.ts @@ -0,0 +1,4 @@ +export { AppleAuthClient } from './native'; +export { AppleSignInButton } from './native'; +export { AppleSignInModule } from './native'; +export * from './types'; diff --git a/packages/apple-signin/src/index.ts b/packages/apple-signin/src/index.ts new file mode 100644 index 0000000..fb35506 --- /dev/null +++ b/packages/apple-signin/src/index.ts @@ -0,0 +1,3 @@ +export { AppleAuthClient } from './web'; +export { AppleSignInButton } from './web'; +export * from './types'; diff --git a/packages/apple-signin/src/native/AppleAuthClient.ts b/packages/apple-signin/src/native/AppleAuthClient.ts new file mode 100644 index 0000000..1004f77 --- /dev/null +++ b/packages/apple-signin/src/native/AppleAuthClient.ts @@ -0,0 +1,179 @@ +import type { AuthClient } from '@forward-software/react-auth'; +import type { + AppleAuthCredentials, + AppleAuthTokens, + AppleNativeAuthConfig, + TokenStorage, +} from '../types'; +import { DEFAULT_SCOPES, DEFAULT_STORAGE_KEY } from '../types'; +import * as AppleSignInModule from './AppleSignInModule'; + +export class AppleAuthClient implements AuthClient { + private config: AppleNativeAuthConfig; + private storage: TokenStorage; + private storageKey: string; + private configured = false; + + constructor(config: AppleNativeAuthConfig) { + this.config = { + scopes: DEFAULT_SCOPES, + persistTokens: true, + ...config, + }; + this.storage = config.storage; + this.storageKey = config.storageKey ?? DEFAULT_STORAGE_KEY; + } + + async onInit(): Promise { + if (!this.configured) { + AppleSignInModule.configure(this.config); + this.configured = true; + } + + if (!this.config.persistTokens) { + return null; + } + + const raw = await this.storage.getItem(this.storageKey); + if (!raw) { + return null; + } + + try { + const tokens: AppleAuthTokens = JSON.parse(raw); + + // Check credential state if we have a user ID + if (tokens.user) { + try { + const state = await AppleSignInModule.getCredentialState(tokens.user); + if (state !== 'authorized') { + await this.storage.removeItem(this.storageKey); + return null; + } + } catch { + // If credential state check fails, fall back to expiry check + } + } + + if (tokens.expiresAt && Date.now() >= tokens.expiresAt) { + await this.storage.removeItem(this.storageKey); + return null; + } + + return tokens; + } catch { + await this.storage.removeItem(this.storageKey); + return null; + } + } + + async onLogin(credentials?: AppleAuthCredentials): Promise { + if (!this.configured) { + AppleSignInModule.configure(this.config); + this.configured = true; + } + + if (!credentials?.identityToken) { + throw new Error( + 'AppleAuthClient: credentials with identityToken are required. ' + + 'Trigger Apple Sign-In via AppleSignInButton or AppleSignInModule.signIn() and pass the result to login().' + ); + } + + const tokens = this.mapToTokens(credentials); + await this.persistTokens(tokens); + return tokens; + } + + async onRefresh(currentTokens: AppleAuthTokens): Promise { + if (!this.configured) { + AppleSignInModule.configure(this.config); + this.configured = true; + } + + // First, honor local expiry. If token is still valid, check for revocation. + if (currentTokens.expiresAt && Date.now() < currentTokens.expiresAt) { + if (currentTokens.user) { + try { + const state = await AppleSignInModule.getCredentialState(currentTokens.user); + if (state !== 'authorized') { + throw new Error( + `Apple credential state is '${state}'. User must re-authenticate.` + ); + } + } catch (err) { + if (err instanceof Error && err.message.includes('credential state')) { + throw err; + } + // Android UNSUPPORTED or other native error -- treat non-expired token as valid + } + } + return currentTokens; + } + + // Token is expired or has no known expiry. + // Use getCredentialState only for a clearer error, not to keep using expired tokens. + if (currentTokens.user) { + try { + const state = await AppleSignInModule.getCredentialState(currentTokens.user); + if (state !== 'authorized') { + throw new Error( + `Apple credential state is '${state}'. User must re-authenticate.` + ); + } + } catch (err) { + if (err instanceof Error && err.message.includes('credential state')) { + throw err; + } + } + } + + throw new Error( + 'Apple identity token has expired or is otherwise invalid. User must re-authenticate.' + ); + } + + async onLogout(): Promise { + await AppleSignInModule.signOut(); + await this.storage.removeItem(this.storageKey); + } + + private mapToTokens(credentials: AppleAuthCredentials): AppleAuthTokens { + return { + identityToken: credentials.identityToken, + authorizationCode: credentials.authorizationCode, + email: credentials.email, + fullName: credentials.fullName, + user: credentials.user, + expiresAt: this.extractExpiration(credentials.identityToken), + }; + } + + private async persistTokens(tokens: AppleAuthTokens): Promise { + if (this.config.persistTokens) { + await this.storage.setItem(this.storageKey, JSON.stringify(tokens)); + } + } + + private base64UrlDecode(input: string): string { + const base64 = input.replace(/-/g, '+').replace(/_/g, '/'); + const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '='); + return atob(padded); + } + + private extractExpiration(identityToken: string): number | undefined { + try { + const payload = identityToken.split('.')[1]; + if (!payload) return undefined; + + const decodedJson = this.base64UrlDecode(payload); + const decoded = JSON.parse(decodedJson); + if (typeof decoded.exp === 'number') { + return decoded.exp * 1000; + } + return undefined; + } catch { + return undefined; + } + } +} diff --git a/packages/apple-signin/src/native/AppleSignInButton.tsx b/packages/apple-signin/src/native/AppleSignInButton.tsx new file mode 100644 index 0000000..9bfd5a2 --- /dev/null +++ b/packages/apple-signin/src/native/AppleSignInButton.tsx @@ -0,0 +1,92 @@ +import React, { useState, useCallback } from 'react'; +import type { StyleProp, ViewStyle } from 'react-native'; +import type { AppleAuthCredentials, AppleNativeAuthConfig } from '../types'; +import { AppleLogo } from '../AppleLogo'; +import * as AppleSignInModule from './AppleSignInModule'; + +type AppleSignInButtonColor = 'black' | 'white'; + +type AppleSignInButtonProps = { + config: AppleNativeAuthConfig; + onCredential: (credentials: AppleAuthCredentials) => void; + onError?: (error: Error) => void; + style?: StyleProp; + disabled?: boolean; + color?: AppleSignInButtonColor; + /** Button label text. Defaults to "Sign in with Apple". Pass a custom string for localization. */ + label?: string; +}; + +export function AppleSignInButton({ + config, + onCredential, + onError, + style, + disabled = false, + color = 'black', + label = 'Sign in with Apple', +}: AppleSignInButtonProps) { + const [isLoading, setIsLoading] = useState(false); + + const isBlack = color === 'black'; + + const handlePress = useCallback(async () => { + if (isLoading || disabled) return; + + setIsLoading(true); + try { + AppleSignInModule.configure(config); + const credentials = await AppleSignInModule.signIn(); + onCredential(credentials); + } catch (err) { + onError?.( + err instanceof Error ? err : new Error('Apple Sign-In failed') + ); + } finally { + setIsLoading(false); + } + }, [config, onCredential, onError, isLoading, disabled]); + + const { View, Text, Pressable, ActivityIndicator } = require('react-native'); + + return ( + + {isLoading ? ( + + ) : ( + + + + {label} + + + )} + + ); +} diff --git a/packages/apple-signin/src/native/AppleSignInModule.ts b/packages/apple-signin/src/native/AppleSignInModule.ts new file mode 100644 index 0000000..7912f4b --- /dev/null +++ b/packages/apple-signin/src/native/AppleSignInModule.ts @@ -0,0 +1,67 @@ +import { requireNativeModule } from 'expo-modules-core'; +import { Platform } from 'react-native'; +import type { AppleAuthCredentials, AppleNativeAuthConfig } from '../types'; + +type AppleCredentialState = 'authorized' | 'revoked' | 'notFound' | 'transferred'; + +type NativeAppleSignInModule = { + configure(config: { + scopes?: string[]; + nonce?: string; + clientId?: string; + redirectUri?: string; + state?: string; + }): void; + + signIn(): Promise; + getCredentialState(userID: string): Promise; + signOut(): Promise; + handleCallback(params: { id_token: string; code?: string; user?: string }): void; +}; + +const NativeModule = requireNativeModule('AppleSignIn'); + +export function configure(config: AppleNativeAuthConfig): void { + const nativeConfig: { scopes?: string[]; nonce?: string; clientId?: string; redirectUri?: string; state?: string } = { + scopes: config.scopes, + nonce: config.nonce, + }; + + if (Platform.OS === 'android') { + nativeConfig.clientId = config.clientId; + nativeConfig.redirectUri = config.androidRedirectUri; + nativeConfig.state = config.state; + } + + NativeModule.configure(nativeConfig); +} + +export function signIn(): Promise { + return NativeModule.signIn(); +} + +export function getCredentialState(userID: string): Promise { + return NativeModule.getCredentialState(userID); +} + +export function signOut(): Promise { + return NativeModule.signOut(); +} + +export type HandleCallbackParams = { + id_token: string; + code?: string; + user?: string; +}; + +/** + * Complete a pending Android sign-in from a deep-link callback. + * Call this from your app's deep link handler after the backend redirects + * with the Apple Sign-In response parameters. + */ +export function handleCallback(params: HandleCallbackParams): void { + if (Platform.OS !== 'android') { + throw new Error('AppleSignIn.handleCallback is only supported on Android.'); + } + return NativeModule.handleCallback(params); +} diff --git a/packages/apple-signin/src/native/index.ts b/packages/apple-signin/src/native/index.ts new file mode 100644 index 0000000..bb4a59e --- /dev/null +++ b/packages/apple-signin/src/native/index.ts @@ -0,0 +1,3 @@ +export { AppleAuthClient } from './AppleAuthClient'; +export { AppleSignInButton } from './AppleSignInButton'; +export * as AppleSignInModule from './AppleSignInModule'; diff --git a/packages/apple-signin/src/types.ts b/packages/apple-signin/src/types.ts new file mode 100644 index 0000000..1c74232 --- /dev/null +++ b/packages/apple-signin/src/types.ts @@ -0,0 +1,97 @@ +/** + * Structured name returned by Apple Sign-In. + * Apple only provides this on the first authorization. + */ +export type AppleFullName = { + givenName?: string; + familyName?: string; + middleName?: string; + namePrefix?: string; + nameSuffix?: string; + nickname?: string; +}; + +/** + * Tokens returned by Apple Sign-In + */ +export type AppleAuthTokens = { + identityToken: string; + authorizationCode?: string; + email?: string; + fullName?: AppleFullName; + /** Stable user identifier. Consistent across sessions for the same Apple ID + app. */ + user?: string; + expiresAt?: number; +}; + +/** + * Credentials passed to authClient.login() after an Apple Sign-In flow + */ +export type AppleAuthCredentials = { + identityToken: string; + authorizationCode?: string; + email?: string; + fullName?: AppleFullName; + user?: string; +}; + +/** + * Platform-agnostic storage interface for token persistence. + * Compatible with localStorage (web), MMKV, and AsyncStorage (React Native). + */ +export interface TokenStorage { + getItem(key: string): string | null | Promise; + setItem(key: string, value: string): void | Promise; + removeItem(key: string): void | Promise; +} + +/** + * Scopes supported by Apple Sign-In. + * Apple only supports 'name' and 'email'. + */ +export type AppleScope = 'name' | 'email'; + +/** + * Base configuration shared by web and native adapters + */ +export type AppleAuthConfig = { + /** Apple Services ID (web) or App Bundle ID context */ + clientId: string; + scopes?: AppleScope[]; + persistTokens?: boolean; + storage?: TokenStorage; + storageKey?: string; + /** Nonce to bind the identity token to a session and prevent replay attacks. */ + nonce?: string; +}; + +/** + * Web-specific configuration (Sign in with Apple JS options) + */ +export type AppleWebAuthConfig = AppleAuthConfig & { + /** Required redirect URI registered in Apple Developer Console */ + redirectURI: string; + /** Use popup flow instead of redirect (default: false) */ + usePopup?: boolean; + /** Opaque state value for CSRF protection */ + state?: string; +}; + +/** + * React Native-specific configuration + */ +export type AppleNativeAuthConfig = Omit & { + /** Storage adapter is required on native (e.g., MMKV or AsyncStorage wrapper). */ + storage: TokenStorage; + /** + * Redirect URI for Android web-based Apple OAuth. + * Apple uses response_mode=form_post, so a backend intermediary is needed + * to convert the POST into a deep link redirect back to your app. + */ + androidRedirectUri?: string; + /** Opaque state value for CSRF protection (used on Android OAuth flow). */ + state?: string; +}; + +export const DEFAULT_SCOPES: AppleScope[] = ['name', 'email']; +export const DEFAULT_STORAGE_KEY = '@react-auth/apple-tokens'; diff --git a/packages/apple-signin/src/web/AppleAuthClient.ts b/packages/apple-signin/src/web/AppleAuthClient.ts new file mode 100644 index 0000000..f40535b --- /dev/null +++ b/packages/apple-signin/src/web/AppleAuthClient.ts @@ -0,0 +1,107 @@ +import type { AuthClient } from '@forward-software/react-auth'; +import type { + AppleAuthCredentials, + AppleAuthTokens, + AppleWebAuthConfig, + TokenStorage, +} from '../types'; +import { DEFAULT_SCOPES, DEFAULT_STORAGE_KEY } from '../types'; + +const defaultWebStorage: TokenStorage = { + getItem: (key) => localStorage.getItem(key), + setItem: (key, value) => localStorage.setItem(key, value), + removeItem: (key) => localStorage.removeItem(key), +}; + +export class AppleAuthClient implements AuthClient { + private config: AppleWebAuthConfig; + private storage: TokenStorage; + private storageKey: string; + + constructor(config: AppleWebAuthConfig) { + this.config = { + scopes: DEFAULT_SCOPES, + persistTokens: true, + ...config, + }; + this.storage = config.storage ?? defaultWebStorage; + this.storageKey = config.storageKey ?? DEFAULT_STORAGE_KEY; + } + + async onInit(): Promise { + if (!this.config.persistTokens) { + return null; + } + + const raw = await this.storage.getItem(this.storageKey); + if (!raw) { + return null; + } + + try { + const tokens: AppleAuthTokens = JSON.parse(raw); + + if (tokens.expiresAt && Date.now() >= tokens.expiresAt) { + await this.storage.removeItem(this.storageKey); + return null; + } + + return tokens; + } catch { + await this.storage.removeItem(this.storageKey); + return null; + } + } + + async onLogin(credentials?: AppleAuthCredentials): Promise { + if (!credentials?.identityToken) { + throw new Error( + 'AppleAuthClient: credentials with identityToken are required. ' + + 'Trigger Apple Sign-In via AppleSignInButton or the Apple JS API and pass the result to login().' + ); + } + + const expiresAt = this.extractExpiration(credentials.identityToken); + + const tokens: AppleAuthTokens = { + identityToken: credentials.identityToken, + authorizationCode: credentials.authorizationCode, + email: credentials.email, + fullName: credentials.fullName, + user: credentials.user, + expiresAt, + }; + + if (this.config.persistTokens) { + await this.storage.setItem(this.storageKey, JSON.stringify(tokens)); + } + + return tokens; + } + + async onLogout(): Promise { + await this.storage.removeItem(this.storageKey); + } + + private base64UrlDecode(input: string): string { + const base64 = input.replace(/-/g, '+').replace(/_/g, '/'); + const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '='); + return atob(padded); + } + + private extractExpiration(identityToken: string): number | undefined { + try { + const payload = identityToken.split('.')[1]; + if (!payload) return undefined; + + const decodedJson = this.base64UrlDecode(payload); + const decoded = JSON.parse(decodedJson); + if (typeof decoded.exp === 'number') { + return decoded.exp * 1000; + } + return undefined; + } catch { + return undefined; + } + } +} diff --git a/packages/apple-signin/src/web/AppleSignInButton.tsx b/packages/apple-signin/src/web/AppleSignInButton.tsx new file mode 100644 index 0000000..d427b61 --- /dev/null +++ b/packages/apple-signin/src/web/AppleSignInButton.tsx @@ -0,0 +1,136 @@ +import React, { useEffect, useRef, useCallback, useState } from 'react'; +import type { AppleAuthCredentials, AppleWebAuthConfig } from '../types'; +import { DEFAULT_SCOPES } from '../types'; +import { AppleLogo } from '../AppleLogo'; +import { loadAppleIdScript, initializeAppleAuth, signInWithApple } from './appleid'; + +type AppleSignInButtonColor = 'black' | 'white' | 'white-outline'; +type AppleSignInButtonType = 'sign-in' | 'continue' | 'sign-up'; + +type AppleSignInButtonProps = { + config: AppleWebAuthConfig; + onCredential: (credentials: AppleAuthCredentials) => void; + onError?: (error: Error) => void; + color?: AppleSignInButtonColor; + type?: AppleSignInButtonType; + /** Button label text. Defaults based on `type` prop. Pass a custom string for localization. */ + label?: string; + width?: number; + height?: number; +}; + +const DEFAULT_LABELS: Record = { + 'sign-in': 'Sign in with Apple', + 'continue': 'Continue with Apple', + 'sign-up': 'Sign up with Apple', +}; + +const BUTTON_STYLES: Record = { + 'black': { bg: '#000000', text: '#ffffff', border: '#000000' }, + 'white': { bg: '#ffffff', text: '#000000', border: '#ffffff' }, + 'white-outline': { bg: '#ffffff', text: '#000000', border: '#000000' }, +}; + +export function AppleSignInButton({ + config, + onCredential, + onError, + color = 'black', + type = 'sign-in', + label, + width, + height = 44, +}: AppleSignInButtonProps) { + const [isLoading, setIsLoading] = useState(false); + const onCredentialRef = useRef(onCredential); + const onErrorRef = useRef(onError); + + onCredentialRef.current = onCredential; + onErrorRef.current = onError; + + const displayLabel = label ?? DEFAULT_LABELS[type]; + const style = BUTTON_STYLES[color]; + + const initialize = useCallback(async () => { + try { + await loadAppleIdScript(); + + const scopes = (config.scopes ?? DEFAULT_SCOPES).join(' '); + initializeAppleAuth({ + clientId: config.clientId, + scope: scopes, + redirectURI: config.redirectURI, + state: config.state, + nonce: config.nonce, + usePopup: config.usePopup, + }); + } catch (err) { + onErrorRef.current?.( + err instanceof Error ? err : new Error('Failed to initialize Apple Sign-In') + ); + } + }, [config.clientId, config.scopes, config.redirectURI, config.state, config.nonce, config.usePopup]); + + useEffect(() => { + initialize(); + }, [initialize]); + + const handleClick = useCallback(async () => { + if (isLoading) return; + + setIsLoading(true); + try { + const response = await signInWithApple(); + + const credentials: AppleAuthCredentials = { + identityToken: response.authorization.id_token, + authorizationCode: response.authorization.code, + email: response.user?.email, + fullName: response.user?.name + ? { + givenName: response.user.name.firstName, + familyName: response.user.name.lastName, + } + : undefined, + }; + + onCredentialRef.current(credentials); + } catch (err) { + const error = err instanceof Error + ? err + : new Error(typeof err === 'object' && err !== null ? JSON.stringify(err) : 'Apple Sign-In failed'); + onErrorRef.current?.(error); + } finally { + setIsLoading(false); + } + }, [isLoading]); + + return ( + + ); +} diff --git a/packages/apple-signin/src/web/appleid.ts b/packages/apple-signin/src/web/appleid.ts new file mode 100644 index 0000000..478d139 --- /dev/null +++ b/packages/apple-signin/src/web/appleid.ts @@ -0,0 +1,163 @@ +/** + * Thin wrapper around the Sign in with Apple JavaScript API. + * Dynamically loads the Apple ID script and provides typed access to its functionality. + */ + +const APPLE_SCRIPT_SRC = 'https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js'; +const APPLE_SCRIPT_ID = '__apple-signin-script'; +const APPLE_SCRIPT_TIMEOUT_MS = 10_000; + +export type AppleSignInResponse = { + authorization: { + code: string; + id_token: string; + state?: string; + }; + user?: { + email?: string; + name?: { + firstName?: string; + lastName?: string; + }; + }; +}; + +export type AppleSignInError = { + error: string; +}; + +export type AppleAuthInitConfig = { + clientId: string; + scope: string; + redirectURI: string; + state?: string; + nonce?: string; + usePopup?: boolean; +}; + +interface AppleIDAuth { + init(config: AppleAuthInitConfig): void; + signIn(): Promise; +} + +declare global { + interface Window { + AppleID?: { + auth: AppleIDAuth; + }; + } +} + +let scriptLoadPromise: Promise | null = null; + +/** + * Loads the Sign in with Apple JS script if not already loaded. + * Returns a promise that resolves when the script is ready. + */ +export function loadAppleIdScript(): Promise { + if (scriptLoadPromise) { + return scriptLoadPromise; + } + + if (typeof window === 'undefined') { + return Promise.reject(new Error('Apple Sign-In script can only be loaded in a browser environment')); + } + + if (window.AppleID?.auth) { + scriptLoadPromise = Promise.resolve(); + return scriptLoadPromise; + } + + scriptLoadPromise = new Promise((resolve, reject) => { + const existingScript = document.getElementById(APPLE_SCRIPT_ID); + if (existingScript) { + if (window.AppleID?.auth) { + resolve(); + return; + } + + let settled = false; + const timeoutId = setTimeout(() => { + if (!settled) { + settled = true; + scriptLoadPromise = null; + reject(new Error('Sign in with Apple script load timed out')); + } + }, APPLE_SCRIPT_TIMEOUT_MS); + + existingScript.addEventListener('load', () => { + if (!settled) { + settled = true; + clearTimeout(timeoutId); + resolve(); + } + }); + existingScript.addEventListener('error', () => { + if (!settled) { + settled = true; + clearTimeout(timeoutId); + scriptLoadPromise = null; + reject(new Error('Failed to load Sign in with Apple script')); + } + }); + return; + } + + let settled = false; + const timeoutId = setTimeout(() => { + if (!settled) { + settled = true; + scriptLoadPromise = null; + reject(new Error('Sign in with Apple script load timed out')); + } + }, APPLE_SCRIPT_TIMEOUT_MS); + + const script = document.createElement('script'); + script.id = APPLE_SCRIPT_ID; + script.src = APPLE_SCRIPT_SRC; + script.async = true; + script.defer = true; + script.onload = () => { + if (!settled) { + settled = true; + clearTimeout(timeoutId); + resolve(); + } + }; + script.onerror = () => { + if (!settled) { + settled = true; + clearTimeout(timeoutId); + scriptLoadPromise = null; + reject(new Error('Failed to load Sign in with Apple script')); + } + }; + document.head.appendChild(script); + }); + + return scriptLoadPromise; +} + +/** + * Returns the AppleID.auth API, throwing if the script is not loaded. + */ +function getAppleAuth(): AppleIDAuth { + if (!window.AppleID?.auth) { + throw new Error('Sign in with Apple script is not loaded. Call loadAppleIdScript() first.'); + } + return window.AppleID.auth; +} + +/** + * Initializes the Apple Sign-In client with the given configuration. + */ +export function initializeAppleAuth(config: AppleAuthInitConfig): void { + getAppleAuth().init(config); +} + +/** + * Triggers the Apple Sign-In flow and returns the response. + */ +export function signInWithApple(): Promise { + return getAppleAuth().signIn(); +} diff --git a/packages/apple-signin/src/web/index.ts b/packages/apple-signin/src/web/index.ts new file mode 100644 index 0000000..d25435f --- /dev/null +++ b/packages/apple-signin/src/web/index.ts @@ -0,0 +1,2 @@ +export { AppleAuthClient } from './AppleAuthClient'; +export { AppleSignInButton } from './AppleSignInButton'; diff --git a/packages/apple-signin/test/AppleAuthClient.native.spec.ts b/packages/apple-signin/test/AppleAuthClient.native.spec.ts new file mode 100644 index 0000000..e6aae02 --- /dev/null +++ b/packages/apple-signin/test/AppleAuthClient.native.spec.ts @@ -0,0 +1,176 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { MockTokenStorage, createMockAppleIdToken, createExpiredMockAppleIdToken } from './test-utils'; +import { DEFAULT_STORAGE_KEY } from '../src/types'; + +// Mock react-native Platform +vi.mock('react-native', () => ({ + Platform: { OS: 'ios' }, +})); + +// Mock the native module +vi.mock('expo-modules-core', () => ({ + requireNativeModule: () => ({ + configure: vi.fn(), + signIn: vi.fn(), + getCredentialState: vi.fn(), + signOut: vi.fn(), + }), +})); + +// Import after mocking +import { AppleAuthClient } from '../src/native/AppleAuthClient'; +import * as AppleSignInModule from '../src/native/AppleSignInModule'; + +describe('AppleAuthClient (Native)', () => { + let storage: MockTokenStorage; + let client: AppleAuthClient; + + beforeEach(() => { + vi.clearAllMocks(); + storage = new MockTokenStorage(); + client = new AppleAuthClient({ + clientId: 'com.example.app', + storage, + }); + }); + + describe('onInit', () => { + it('should configure the native module and return null when no tokens are stored', async () => { + const configureSpy = vi.spyOn(AppleSignInModule, 'configure'); + + const result = await client.onInit(); + + expect(configureSpy).toHaveBeenCalledWith( + expect.objectContaining({ clientId: 'com.example.app' }) + ); + expect(result).toBeNull(); + }); + + it('should return stored tokens when they are valid', async () => { + const identityToken = createMockAppleIdToken(); + const tokens = { + identityToken, + user: '001234.abcdef1234567890.1234', + expiresAt: Date.now() + 3600000, + }; + storage.setItem(DEFAULT_STORAGE_KEY, JSON.stringify(tokens)); + + vi.spyOn(AppleSignInModule, 'getCredentialState').mockResolvedValueOnce('authorized'); + + const result = await client.onInit(); + expect(result).not.toBeNull(); + expect(result!.identityToken).toBe(identityToken); + }); + + it('should return null when credential state is revoked', async () => { + const tokens = { + identityToken: createMockAppleIdToken(), + user: '001234.abcdef1234567890.1234', + expiresAt: Date.now() + 3600000, + }; + storage.setItem(DEFAULT_STORAGE_KEY, JSON.stringify(tokens)); + + vi.spyOn(AppleSignInModule, 'getCredentialState').mockResolvedValueOnce('revoked'); + + const result = await client.onInit(); + expect(result).toBeNull(); + expect(storage.has(DEFAULT_STORAGE_KEY)).toBe(false); + }); + + it('should return null when tokens are expired and no user ID', async () => { + const expiredTokens = { + identityToken: createExpiredMockAppleIdToken(), + expiresAt: Date.now() - 3600000, + }; + storage.setItem(DEFAULT_STORAGE_KEY, JSON.stringify(expiredTokens)); + + const result = await client.onInit(); + expect(result).toBeNull(); + expect(storage.has(DEFAULT_STORAGE_KEY)).toBe(false); + }); + }); + + describe('onLogin', () => { + it('should store and return tokens when valid credentials are provided', async () => { + const identityToken = createMockAppleIdToken(); + const credentials = { + identityToken, + user: '001234.abcdef1234567890.1234', + }; + + const result = await client.onLogin(credentials); + + expect(result.identityToken).toBe(identityToken); + expect(result.user).toBe('001234.abcdef1234567890.1234'); + expect(storage.has(DEFAULT_STORAGE_KEY)).toBe(true); + }); + + it('should throw when no credentials are provided', async () => { + await expect(client.onLogin()).rejects.toThrow('credentials with identityToken are required'); + }); + }); + + describe('onRefresh', () => { + it('should return current tokens if credential state is authorized', async () => { + const identityToken = createMockAppleIdToken(); + const currentTokens = { + identityToken, + user: '001234.abcdef1234567890.1234', + expiresAt: Date.now() + 3600000, + }; + + vi.spyOn(AppleSignInModule, 'getCredentialState').mockResolvedValueOnce('authorized'); + + const result = await client.onRefresh(currentTokens); + expect(result).toBe(currentTokens); + }); + + it('should throw when credential state is revoked', async () => { + const currentTokens = { + identityToken: createMockAppleIdToken(), + user: '001234.abcdef1234567890.1234', + expiresAt: Date.now() + 3600000, + }; + + vi.spyOn(AppleSignInModule, 'getCredentialState').mockResolvedValueOnce('revoked'); + + await expect(client.onRefresh(currentTokens)).rejects.toThrow( + "Apple credential state is 'revoked'" + ); + }); + + it('should return current tokens if no user ID but not expired', async () => { + const identityToken = createMockAppleIdToken(); + const currentTokens = { + identityToken, + expiresAt: Date.now() + 3600000, + }; + + const result = await client.onRefresh(currentTokens); + expect(result).toBe(currentTokens); + }); + + it('should throw if no user ID and token is expired', async () => { + const currentTokens = { + identityToken: createExpiredMockAppleIdToken(), + expiresAt: Date.now() - 3600000, + }; + + await expect(client.onRefresh(currentTokens)).rejects.toThrow( + 'Apple identity token has expired' + ); + }); + }); + + describe('onLogout', () => { + it('should call signOut and clear storage', async () => { + const signOutSpy = vi.spyOn(AppleSignInModule, 'signOut').mockResolvedValueOnce(); + storage.setItem(DEFAULT_STORAGE_KEY, JSON.stringify({ identityToken: 'test' })); + + await client.onLogout(); + + expect(signOutSpy).toHaveBeenCalled(); + expect(storage.has(DEFAULT_STORAGE_KEY)).toBe(false); + }); + }); +}); diff --git a/packages/apple-signin/test/AppleAuthClient.web.spec.ts b/packages/apple-signin/test/AppleAuthClient.web.spec.ts new file mode 100644 index 0000000..760a75a --- /dev/null +++ b/packages/apple-signin/test/AppleAuthClient.web.spec.ts @@ -0,0 +1,184 @@ +import { describe, it, expect, beforeEach } from 'vitest'; +import { AppleAuthClient } from '../src/web/AppleAuthClient'; +import { + MockTokenStorage, + createMockAppleIdToken, + createExpiredMockAppleIdToken, +} from './test-utils'; +import { DEFAULT_STORAGE_KEY } from '../src/types'; + +describe('AppleAuthClient (Web)', () => { + let storage: MockTokenStorage; + let client: AppleAuthClient; + + beforeEach(() => { + storage = new MockTokenStorage(); + client = new AppleAuthClient({ + clientId: 'com.example.app', + redirectURI: 'https://example.com/callback', + storage, + }); + }); + + describe('onInit', () => { + it('should return null when no tokens are stored', async () => { + const result = await client.onInit(); + expect(result).toBeNull(); + }); + + it('should return stored tokens when they are valid', async () => { + const identityToken = createMockAppleIdToken(); + const tokens = { + identityToken, + expiresAt: Date.now() + 3600000, + }; + storage.setItem(DEFAULT_STORAGE_KEY, JSON.stringify(tokens)); + + const result = await client.onInit(); + expect(result).not.toBeNull(); + expect(result!.identityToken).toBe(identityToken); + }); + + it('should return null and clear storage when tokens are expired', async () => { + const tokens = { + identityToken: createExpiredMockAppleIdToken(), + expiresAt: Date.now() - 3600000, + }; + storage.setItem(DEFAULT_STORAGE_KEY, JSON.stringify(tokens)); + + const result = await client.onInit(); + expect(result).toBeNull(); + expect(storage.has(DEFAULT_STORAGE_KEY)).toBe(false); + }); + + it('should return null and clear storage when stored data is corrupted', async () => { + storage.setItem(DEFAULT_STORAGE_KEY, 'not-valid-json'); + + const result = await client.onInit(); + expect(result).toBeNull(); + expect(storage.has(DEFAULT_STORAGE_KEY)).toBe(false); + }); + + it('should return null when persistTokens is false', async () => { + const clientNoPersist = new AppleAuthClient({ + clientId: 'com.example.app', + redirectURI: 'https://example.com/callback', + storage, + persistTokens: false, + }); + + const tokens = { + identityToken: createMockAppleIdToken(), + expiresAt: Date.now() + 3600000, + }; + storage.setItem(DEFAULT_STORAGE_KEY, JSON.stringify(tokens)); + + const result = await clientNoPersist.onInit(); + expect(result).toBeNull(); + }); + }); + + describe('onLogin', () => { + it('should store and return tokens when valid credentials are provided', async () => { + const identityToken = createMockAppleIdToken(); + const credentials = { identityToken }; + + const result = await client.onLogin(credentials); + + expect(result.identityToken).toBe(identityToken); + expect(result.expiresAt).toBeDefined(); + expect(storage.has(DEFAULT_STORAGE_KEY)).toBe(true); + }); + + it('should include optional fields when provided', async () => { + const identityToken = createMockAppleIdToken(); + const credentials = { + identityToken, + authorizationCode: 'test-auth-code', + email: 'test@example.com', + fullName: { givenName: 'Test', familyName: 'User' }, + user: '001234.abcdef1234567890.1234', + }; + + const result = await client.onLogin(credentials); + + expect(result.authorizationCode).toBe('test-auth-code'); + expect(result.email).toBe('test@example.com'); + expect(result.fullName?.givenName).toBe('Test'); + expect(result.user).toBe('001234.abcdef1234567890.1234'); + }); + + it('should throw when no credentials are provided', async () => { + await expect(client.onLogin()).rejects.toThrow( + 'credentials with identityToken are required', + ); + }); + + it('should throw when credentials have no identityToken', async () => { + await expect(client.onLogin({ identityToken: '' })).rejects.toThrow( + 'credentials with identityToken are required', + ); + }); + + it('should not persist when persistTokens is false', async () => { + const clientNoPersist = new AppleAuthClient({ + clientId: 'com.example.app', + redirectURI: 'https://example.com/callback', + storage, + persistTokens: false, + }); + + const identityToken = createMockAppleIdToken(); + await clientNoPersist.onLogin({ identityToken }); + + expect(storage.has(DEFAULT_STORAGE_KEY)).toBe(false); + }); + + it('should use custom storage key when provided', async () => { + const customKey = 'my-custom-key'; + const customClient = new AppleAuthClient({ + clientId: 'com.example.app', + redirectURI: 'https://example.com/callback', + storage, + storageKey: customKey, + }); + + const identityToken = createMockAppleIdToken(); + await customClient.onLogin({ identityToken }); + + expect(storage.has(customKey)).toBe(true); + expect(storage.has(DEFAULT_STORAGE_KEY)).toBe(false); + }); + }); + + describe('onLogout', () => { + it('should clear stored tokens', async () => { + storage.setItem(DEFAULT_STORAGE_KEY, JSON.stringify({ identityToken: 'test' })); + + await client.onLogout(); + + expect(storage.has(DEFAULT_STORAGE_KEY)).toBe(false); + }); + }); + + describe('token expiration extraction', () => { + it('should extract expiration from a valid JWT', async () => { + const exp = Math.floor(Date.now() / 1000) + 7200; + const identityToken = createMockAppleIdToken({ exp }); + + const result = await client.onLogin({ identityToken }); + + expect(result.expiresAt).toBe(exp * 1000); + }); + + it('should handle tokens without exp claim', async () => { + const header = btoa(JSON.stringify({ alg: 'none' })); + const payload = btoa(JSON.stringify({ sub: '123' })); + const identityToken = `${header}.${payload}.sig`; + + const result = await client.onLogin({ identityToken }); + + expect(result.expiresAt).toBeUndefined(); + }); + }); +}); diff --git a/packages/apple-signin/test/test-utils.ts b/packages/apple-signin/test/test-utils.ts new file mode 100644 index 0000000..876170d --- /dev/null +++ b/packages/apple-signin/test/test-utils.ts @@ -0,0 +1,49 @@ +import type { TokenStorage } from '../src/types'; + +export class MockTokenStorage implements TokenStorage { + private store: Map = new Map(); + + getItem(key: string): string | null { + return this.store.get(key) ?? null; + } + + setItem(key: string, value: string): void { + this.store.set(key, value); + } + + removeItem(key: string): void { + this.store.delete(key); + } + + clear(): void { + this.store.clear(); + } + + has(key: string): boolean { + return this.store.has(key); + } +} + +export function createMockAppleIdToken(claims: Record = {}): string { + const header = btoa(JSON.stringify({ alg: 'RS256', typ: 'JWT' })); + const payload = btoa( + JSON.stringify({ + iss: 'https://appleid.apple.com', + sub: '001234.abcdef1234567890.1234', + aud: 'com.example.app', + email: 'test@privaterelay.appleid.com', + email_verified: true, + iat: Math.floor(Date.now() / 1000), + exp: Math.floor(Date.now() / 1000) + 3600, // 1 hour from now + ...claims, + }) + ); + const signature = btoa('mock-signature'); + return `${header}.${payload}.${signature}`; +} + +export function createExpiredMockAppleIdToken(): string { + return createMockAppleIdToken({ + exp: Math.floor(Date.now() / 1000) - 3600, // 1 hour ago + }); +} diff --git a/packages/apple-signin/tsconfig.json b/packages/apple-signin/tsconfig.json new file mode 100644 index 0000000..6f1bfe0 --- /dev/null +++ b/packages/apple-signin/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "declaration": false, + "target": "ES6", + "moduleResolution": "bundler", + "lib": ["ES2017", "DOM"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "removeComments": false, + "outDir": "./dist", + "rootDir": "./src", + "jsx": "react-jsx" + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "**/*.test.ts", + "**/*.spec.ts" + ] +} diff --git a/packages/apple-signin/vitest.config.ts b/packages/apple-signin/vitest.config.ts new file mode 100644 index 0000000..585aba6 --- /dev/null +++ b/packages/apple-signin/vitest.config.ts @@ -0,0 +1,16 @@ +import { defineConfig } from "vitest/config"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + plugins: [react()], + test: { + environment: "jsdom", + globals: true, + include: ["**/*.{test,spec}.{js,jsx,ts,tsx}"], + coverage: { + reporter: ["clover", "lcov", "html"], + include: ["src/**/*.{js,jsx,ts,tsx}"], + exclude: ["**/*.d.ts"], + }, + }, +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0019a9b..aeb2ec5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -83,7 +83,7 @@ importers: version: 1.5.0 '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.1(vite@8.0.3(@types/node@25.5.0)) + version: 6.0.1(vite@8.0.3(@types/node@25.5.0)(terser@5.46.1)(yaml@2.8.2)) jsdom: specifier: 'catalog:' version: 29.0.1 @@ -95,10 +95,64 @@ importers: version: 19.2.4(react@19.2.4) vite: specifier: 'catalog:' - version: 8.0.3(@types/node@25.5.0) + version: 8.0.3(@types/node@25.5.0)(terser@5.46.1)(yaml@2.8.2) vitest: specifier: 'catalog:' - version: 4.1.2(@types/node@25.5.0)(jsdom@29.0.1)(vite@8.0.3(@types/node@25.5.0)) + version: 4.1.2(@types/node@25.5.0)(jsdom@29.0.1)(vite@8.0.3(@types/node@25.5.0)(terser@5.46.1)(yaml@2.8.2)) + + packages/apple-signin: + devDependencies: + '@forward-software/react-auth': + specifier: ^2.0.0 + version: 2.0.3(react@19.2.4) + '@testing-library/dom': + specifier: ^10.4.1 + version: 10.4.1 + '@testing-library/jest-dom': + specifier: ^6.9.1 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.3.2 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@types/node': + specifier: ^25.2.3 + version: 25.5.0 + '@types/react': + specifier: 'catalog:' + version: 19.2.14 + '@vitejs/plugin-react': + specifier: 'catalog:' + version: 6.0.1(vite@8.0.3(@types/node@25.5.0)(terser@5.46.1)(yaml@2.8.2)) + expo-modules-core: + specifier: ^55.0.17 + version: 55.0.17(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) + jsdom: + specifier: 'catalog:' + version: 29.0.1 + react: + specifier: 'catalog:' + version: 19.2.4 + react-dom: + specifier: 'catalog:' + version: 19.2.4(react@19.2.4) + react-native: + specifier: ^0.84.1 + version: 0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4) + react-native-svg: + specifier: ^15.15.4 + version: 15.15.4(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) + rimraf: + specifier: 'catalog:' + version: 6.1.3 + typescript: + specifier: 'catalog:' + version: 6.0.2 + vite: + specifier: 'catalog:' + version: 8.0.3(@types/node@25.5.0)(terser@5.46.1)(yaml@2.8.2) + vitest: + specifier: 'catalog:' + version: 4.1.2(@types/node@25.5.0)(jsdom@29.0.1)(vite@8.0.3(@types/node@25.5.0)(terser@5.46.1)(yaml@2.8.2)) packages/google-signin: devDependencies: @@ -122,7 +176,7 @@ importers: version: 19.2.14 '@vitejs/plugin-react': specifier: 'catalog:' - version: 6.0.1(vite@8.0.3(@types/node@25.5.0)) + version: 6.0.1(vite@8.0.3(@types/node@25.5.0)(terser@5.46.1)(yaml@2.8.2)) jsdom: specifier: 'catalog:' version: 29.0.1 @@ -140,10 +194,10 @@ importers: version: 6.0.2 vite: specifier: 'catalog:' - version: 8.0.3(@types/node@25.5.0) + version: 8.0.3(@types/node@25.5.0)(terser@5.46.1)(yaml@2.8.2) vitest: specifier: 'catalog:' - version: 4.1.2(@types/node@25.5.0)(jsdom@29.0.1)(vite@8.0.3(@types/node@25.5.0)) + version: 4.1.2(@types/node@25.5.0)(jsdom@29.0.1)(vite@8.0.3(@types/node@25.5.0)(terser@5.46.1)(yaml@2.8.2)) packages: @@ -165,14 +219,156 @@ packages: resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + 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-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.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-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + 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/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.2': + resolution: {integrity: sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@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-import-attributes@7.28.6': + resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} + 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-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/runtime@7.29.2': resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} 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.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + '@bramus/specificity@2.4.2': resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} hasBin: true @@ -236,15 +432,121 @@ packages: peerDependencies: react: '>=16.8' + '@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/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/environment@29.7.0': + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.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/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/transform@29.7.0': + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + '@napi-rs/wasm-runtime@1.1.1': resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} '@oxc-project/types@0.122.0': resolution: {integrity: sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==} + '@react-native/assets-registry@0.84.1': + resolution: {integrity: sha512-lAJ6PDZv95FdT9s9uhc9ivhikW1Zwh4j9XdXM7J2l4oUA3t37qfoBmTSDLuPyE3Bi+Xtwa11hJm0BUTT2sc/gg==} + engines: {node: '>= 20.19.4'} + + '@react-native/codegen@0.84.1': + resolution: {integrity: sha512-n1RIU0QAavgCg1uC5+s53arL7/mpM+16IBhJ3nCFSd/iK5tUmCwxQDcIDC703fuXfpub/ZygeSjVN8bcOWn0gA==} + engines: {node: '>= 20.19.4'} + + '@react-native/community-cli-plugin@0.84.1': + resolution: {integrity: sha512-f6a+mJEJ6Joxlt/050TqYUr7uRRbeKnz8lnpL7JajhpsgZLEbkJRjH8HY5QiLcRdUwWFtizml4V+vcO3P4RxoQ==} + engines: {node: '>= 20.19.4'} + peerDependencies: + '@react-native-community/cli': '*' + '@react-native/metro-config': '*' + peerDependenciesMeta: + '@react-native-community/cli': + optional: true + '@react-native/metro-config': + optional: true + + '@react-native/debugger-frontend@0.84.1': + resolution: {integrity: sha512-rUU/Pyh3R5zT0WkVgB+yA6VwOp7HM5Hz4NYE97ajFS07OUIcv8JzBL3MXVdSSjLfldfqOuPEuKUaZcAOwPgabw==} + engines: {node: '>= 20.19.4'} + + '@react-native/debugger-shell@0.84.1': + resolution: {integrity: sha512-LIGhh4q4ette3yW5OzmukNMYwmINYrRGDZqKyTYc/VZyNpblZPw72coXVHXdfpPT6+YlxHqXzn3UjFZpNODGCQ==} + engines: {node: '>= 20.19.4'} + + '@react-native/dev-middleware@0.84.1': + resolution: {integrity: sha512-Z83ra+Gk6ElAhH3XRrv3vwbwCPTb04sPPlNpotxcFZb5LtRQZwT91ZQEXw3GOJCVIFp9EQ/gj8AQbVvtHKOUlQ==} + engines: {node: '>= 20.19.4'} + + '@react-native/gradle-plugin@0.84.1': + resolution: {integrity: sha512-7uVlPBE3uluRNRX4MW7PUJIO1LDBTpAqStKHU7LHH+GRrdZbHsWtOEAX8PiY4GFfBEvG8hEjiuTOqAxMjV+hDg==} + engines: {node: '>= 20.19.4'} + + '@react-native/js-polyfills@0.84.1': + resolution: {integrity: sha512-UsTe2AbUugsfyI7XIHMQq4E7xeC8a6GrYwuK+NohMMMJMxmyM3JkzIk+GB9e2il6ScEQNMJNaj+q+i5za8itxQ==} + engines: {node: '>= 20.19.4'} + + '@react-native/normalize-colors@0.84.1': + resolution: {integrity: sha512-/UPaQ4jl95soXnLDEJ6Cs6lnRXhwbxtT4KbZz+AFDees7prMV2NOLcHfCnzmTabf5Y3oxENMVBL666n4GMLcTA==} + + '@react-native/virtualized-lists@0.84.1': + resolution: {integrity: sha512-sJoDunzhci8ZsqxlUiKoLut4xQeQcmbIgvDHGQKeBz6uEq9HgU+hCWOijMRr6sLP0slQVfBAza34Rq7IbXZZOA==} + engines: {node: '>= 20.19.4'} + peerDependencies: + '@types/react': ^19.2.0 + react: '*' + react-native: '*' + peerDependenciesMeta: + '@types/react': + optional: true + '@rolldown/binding-android-arm64@1.0.0-rc.12': resolution: {integrity: sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==} engines: {node: ^20.19.0 || >=22.12.0} @@ -346,6 +648,15 @@ packages: '@rolldown/pluginutils@1.0.0-rc.7': resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} + '@sinclair/typebox@0.27.10': + resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@10.3.0': + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -378,6 +689,18 @@ packages: '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -387,6 +710,18 @@ packages: '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + '@types/graceful-fs@4.1.9': + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + + '@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/node@25.5.0': resolution: {integrity: sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==} @@ -398,9 +733,18 @@ packages: '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + '@types/use-sync-external-store@1.5.0': resolution: {integrity: sha512-5dyB8nLC/qogMrlCizZnYWQTA4lnb/v+It+sqNl5YnSRAPMlIqY/X0Xn+gZw8vOL+TgTTr28VEbn3uf8fUtAkw==} + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + '@vitejs/plugin-react@6.0.1': resolution: {integrity: sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -443,10 +787,34 @@ packages: '@vitest/utils@4.1.2': resolution: {integrity: sha512-xw2/TiX82lQHA06cgbqRKFb5lCAy3axQ4H4SoUFhUsg+wztiet+co86IAMDtF6Vm1hc7J6j09oh/rgDn+JdKIQ==} + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + accepts@2.0.0: + resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} + engines: {node: '>= 0.6'} + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + anser@1.4.10: + resolution: {integrity: sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} + 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'} @@ -455,6 +823,13 @@ packages: resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} engines: {node: '>=12'} + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} @@ -462,25 +837,143 @@ packages: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} + asap@2.0.6: + resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + 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-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + + 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-syntax-hermes-parser@0.32.0: + resolution: {integrity: sha512-m5HthL++AbyeEA2FcdwOLfVFvWYECOBObLHNqdR8ceY4TsEdn4LdX2oTvbB2QJSSElE2AWA/b2MXZ/PF/CqLZg==} + + babel-preset-current-node-syntax@1.2.0: + resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} + peerDependencies: + '@babel/core': ^7.0.0 || ^8.0.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 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + baseline-browser-mapping@2.10.9: + resolution: {integrity: sha512-OZd0e2mU11ClX8+IdXe3r0dbqMEznRiT4TfbhYIbcRPZkqJ7Qwer8ij3GZAmLsRKa+II9V1v5czCkvmHH3XZBg==} + engines: {node: '>=6.0.0'} + hasBin: true + bidi-js@1.0.3: resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + brace-expansion@5.0.4: resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==} engines: {node: 18 || 20 || >=22} + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + 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==} + + 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.30001780: + resolution: {integrity: sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==} + chai@6.2.2: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + 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'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + 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==} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + connect@3.7.0: + resolution: {integrity: sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==} + engines: {node: '>= 0.10.0'} + convert-source-map@2.0.0: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} @@ -488,10 +981,21 @@ packages: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + css-tree@1.1.3: + resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} + engines: {node: '>=8.0.0'} + css-tree@3.2.1: resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} @@ -502,13 +1006,38 @@ packages: resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decimal.js@10.6.0: resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + dequal@2.0.3: resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} engines: {node: '>=6'} + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + detect-libc@2.1.2: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} @@ -519,49 +1048,238 @@ packages: dom-accessibility-api@0.6.3: resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.321: + resolution: {integrity: sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + 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'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + entities@6.0.1: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} + error-stack-parser@2.1.4: + resolution: {integrity: sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==} + es-module-lexer@2.0.0: resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + 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@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'} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + 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'} + expect-type@1.3.0: resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} engines: {node: '>=12.0.0'} - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} + expo-modules-core@55.0.17: + resolution: {integrity: sha512-pw3cZiaSlBrqRJUD/pHuMnKGsRTW6XJ255FrjDd3HC4QrqErCnfSQPmz+Sv4Qkelcvd9UGdAewyTqZdFwjLwOw==} peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: + react: '*' + react-native: '*' + + exponential-backoff@3.1.3: + resolution: {integrity: sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fb-dotslash@0.5.8: + resolution: {integrity: sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==} + engines: {node: '>=20'} + hasBin: true + + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: picomatch: optional: true + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@1.1.2: + resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} + engines: {node: '>= 0.8'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + flow-enums-runtime@0.0.6: + resolution: {integrity: sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + 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] + 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-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + glob@13.0.6: resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} engines: {node: 18 || 20 || >=22} + 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 + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hermes-compiler@250829098.0.9: + resolution: {integrity: sha512-hZ5O7PDz1vQ99TS7HD3FJ9zVynfU1y+VWId6U1Pldvd8hmAYrNec/XLPYJKD3dLOW6NXak6aAQAuMuSo3ji0tQ==} + + hermes-estree@0.32.0: + resolution: {integrity: sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==} + + hermes-estree@0.33.3: + resolution: {integrity: sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==} + + hermes-parser@0.32.0: + resolution: {integrity: sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==} + + hermes-parser@0.33.3: + resolution: {integrity: sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==} + html-encoding-sniffer@6.0.0: resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + image-size@1.2.1: + resolution: {integrity: sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==} + engines: {node: '>=16.x'} + 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==} + + invariant@2.2.4: + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} @@ -569,9 +1287,60 @@ packages: resolution: {integrity: sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w==} engines: {node: '>=18'} + 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'} + + jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.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-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-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.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-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.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} + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + hasBin: true + + jsc-safe-url@0.2.4: + resolution: {integrity: sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==} + jsdom@29.0.1: resolution: {integrity: sha512-z6JOK5gRO7aMybVq/y/MlIpKh8JIi68FBKMUtKkK2KH/wMSRlCxQ682d08LB9fYXplyY/UXG8P4XXTScmdjApg==} engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} @@ -581,10 +1350,27 @@ packages: canvas: optional: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-parse-even-better-errors@4.0.0: resolution: {integrity: sha512-lR4MXjGNgkJc7tkQ97kb2nuEMnNCyU//XYVH0MKTGcXEiSudQ5MKGKen3C5QubYy0vmq+JGitUg92uuywGEwIA==} engines: {node: ^18.17.0 || >=20.5.0} + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + lighthouse-logger@1.4.2: + resolution: {integrity: sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==} + lightningcss-android-arm64@1.32.0: resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} engines: {node: '>= 12.0.0'} @@ -659,10 +1445,24 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + lodash.throttle@4.1.1: + resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + lru-cache@11.2.7: resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==} engines: {node: 20 || >=22} + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + lz-string@1.5.0: resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} hasBin: true @@ -670,13 +1470,103 @@ packages: magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + + marky@1.3.0: + resolution: {integrity: sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==} + + mdn-data@2.0.14: + resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + memoize-one@5.2.1: + resolution: {integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==} + memorystream@0.3.1: resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} engines: {node: '>= 0.10.0'} + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + metro-babel-transformer@0.83.5: + resolution: {integrity: sha512-d9FfmgUEVejTiSb7bkQeLRGl6aeno2UpuPm3bo3rCYwxewj03ymvOn8s8vnS4fBqAPQ+cE9iQM40wh7nGXR+eA==} + engines: {node: '>=20.19.4'} + + metro-cache-key@0.83.5: + resolution: {integrity: sha512-Ycl8PBajB7bhbAI7Rt0xEyiF8oJ0RWX8EKkolV1KfCUlC++V/GStMSGpPLwnnBZXZWkCC5edBPzv1Hz1Yi0Euw==} + engines: {node: '>=20.19.4'} + + metro-cache@0.83.5: + resolution: {integrity: sha512-oH+s4U+IfZyg8J42bne2Skc90rcuESIYf86dYittcdWQtPfcaFXWpByPyTuWk3rR1Zz3Eh5HOrcVImfEhhJLng==} + engines: {node: '>=20.19.4'} + + metro-config@0.83.5: + resolution: {integrity: sha512-JQ/PAASXH7yczgV6OCUSRhZYME+NU8NYjI2RcaG5ga4QfQ3T/XdiLzpSb3awWZYlDCcQb36l4Vl7i0Zw7/Tf9w==} + engines: {node: '>=20.19.4'} + + metro-core@0.83.5: + resolution: {integrity: sha512-YcVcLCrf0ed4mdLa82Qob0VxYqfhmlRxUS8+TO4gosZo/gLwSvtdeOjc/Vt0pe/lvMNrBap9LlmvZM8FIsMgJQ==} + engines: {node: '>=20.19.4'} + + metro-file-map@0.83.5: + resolution: {integrity: sha512-ZEt8s3a1cnYbn40nyCD+CsZdYSlwtFh2kFym4lo+uvfM+UMMH+r/BsrC6rbNClSrt+B7rU9T+Te/sh/NL8ZZKQ==} + engines: {node: '>=20.19.4'} + + metro-minify-terser@0.83.5: + resolution: {integrity: sha512-Toe4Md1wS1PBqbvB0cFxBzKEVyyuYTUb0sgifAZh/mSvLH84qA1NAWik9sISWatzvfWf3rOGoUoO5E3f193a3Q==} + engines: {node: '>=20.19.4'} + + metro-resolver@0.83.5: + resolution: {integrity: sha512-7p3GtzVUpbAweJeCcUJihJeOQl1bDuimO5ueo1K0BUpUtR41q5EilbQ3klt16UTPPMpA+tISWBtsrqU556mY1A==} + engines: {node: '>=20.19.4'} + + metro-runtime@0.83.5: + resolution: {integrity: sha512-f+b3ue9AWTVlZe2Xrki6TAoFtKIqw30jwfk7GQ1rDUBQaE0ZQ+NkiMEtb9uwH7uAjJ87U7Tdx1Jg1OJqUfEVlA==} + engines: {node: '>=20.19.4'} + + metro-source-map@0.83.5: + resolution: {integrity: sha512-VT9bb2KO2/4tWY9Z2yeZqTUao7CicKAOps9LUg2aQzsz+04QyuXL3qgf1cLUVRjA/D6G5u1RJAlN1w9VNHtODQ==} + engines: {node: '>=20.19.4'} + + metro-symbolicate@0.83.5: + resolution: {integrity: sha512-EMIkrjNRz/hF+p0RDdxoE60+dkaTLPN3vaaGkFmX5lvFdO6HPfHA/Ywznzkev+za0VhPQ5KSdz49/MALBRteHA==} + engines: {node: '>=20.19.4'} + hasBin: true + + metro-transform-plugins@0.83.5: + resolution: {integrity: sha512-KxYKzZL+lt3Os5H2nx7YkbkWVduLZL5kPrE/Yq+Prm/DE1VLhpfnO6HtPs8vimYFKOa58ncl60GpoX0h7Wm0Vw==} + engines: {node: '>=20.19.4'} + + metro-transform-worker@0.83.5: + resolution: {integrity: sha512-8N4pjkNXc6ytlP9oAM6MwqkvUepNSW39LKYl9NjUMpRDazBQ7oBpQDc8Sz4aI8jnH6AGhF7s1m/ayxkN1t04yA==} + engines: {node: '>=20.19.4'} + + metro@0.83.5: + resolution: {integrity: sha512-BgsXevY1MBac/3ZYv/RfNFf/4iuW9X7f4H8ZNkiH+r667HD9sVujxcmu4jvEzGCAm4/WyKdZCuyhAcyhTHOucQ==} + engines: {node: '>=20.19.4'} + hasBin: true + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@3.0.2: + resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==} + engines: {node: '>=18'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -685,15 +1575,43 @@ packages: resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} engines: {node: 18 || 20 || >=22} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + minipass@7.1.3: resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} 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 + negotiator@1.0.0: + resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} + engines: {node: '>= 0.6'} + + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + node-releases@2.0.36: + resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + npm-normalize-package-bin@4.0.0: resolution: {integrity: sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w==} engines: {node: ^18.17.0 || >=20.5.0} @@ -703,15 +1621,64 @@ packages: engines: {node: ^20.5.0 || >=22.0.0, npm: '>= 10'} hasBin: true + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + nullthrows@1.1.1: + resolution: {integrity: sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==} + + ob1@0.83.5: + resolution: {integrity: sha512-vNKPYC8L5ycVANANpF/S+WZHpfnRWKx/F3AYP4QMn6ZJTh+l2HOrId0clNkEmua58NB9vmI9Qh7YOoV/4folYg==} + engines: {node: '>=20.19.4'} + obug@2.1.1: resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + 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'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + open@7.4.2: + resolution: {integrity: sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==} + engines: {node: '>=8'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} parse5@8.0.0: resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + 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'} @@ -726,6 +1693,10 @@ packages: 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.4: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} @@ -735,6 +1706,10 @@ packages: engines: {node: '>=0.10'} hasBin: true + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + postcss@8.5.8: resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} engines: {node: ^10 || ^12 || >=14} @@ -743,10 +1718,27 @@ packages: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + promise@8.3.0: + resolution: {integrity: sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} + 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'} + + react-devtools-core@6.1.5: + resolution: {integrity: sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==} + react-dom@19.2.4: resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} peerDependencies: @@ -755,6 +1747,30 @@ packages: 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-native-svg@15.15.4: + resolution: {integrity: sha512-boT/vIRgj6zZKBpfTPJJiYWMbZE9duBMOwPK6kCSTgxsS947IFMOq9OgIFkpWZTB7t229H24pDRkh3W9ZK/J1A==} + peerDependencies: + react: '*' + react-native: '*' + + react-native@0.84.1: + resolution: {integrity: sha512-0PjxOyXRu3tZ8EobabxSukvhKje2HJbsZikR0U+pvS0pYZza2hXKjcSBiBdFN4h9D0S3v6a8kkrDK6WTRKMwzg==} + engines: {node: '>= 20.19.4'} + hasBin: true + peerDependencies: + '@types/react': ^19.1.1 + react: ^19.2.3 + peerDependenciesMeta: + '@types/react': + optional: true + + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} + react@19.2.4: resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} engines: {node: '>=0.10.0'} @@ -767,10 +1783,26 @@ packages: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} engines: {node: '>=8'} + regenerator-runtime@0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + rimraf@6.1.3: resolution: {integrity: sha512-LKg+Cr2ZF61fkcaK1UdkH2yEBBKnYjTyWzTJT6KNPcSPaiT7HSdhtMXQuN5wkTX0Xu72KQ1l8S42rlmexS2hSA==} engines: {node: 20 || >=22} @@ -788,6 +1820,30 @@ packages: scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + send@0.19.2: + resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} + 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.3: + resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} + engines: {node: '>= 0.8.0'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -803,23 +1859,91 @@ packages: siginfo@2.0.0: resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + 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'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + stackframe@1.3.4: + resolution: {integrity: sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==} + + stacktrace-parser@0.1.11: + resolution: {integrity: sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==} + engines: {node: '>=6'} + + statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + std-env@4.0.0: resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==} + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} + 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'} + symbol-tree@3.2.4: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + terser@5.46.1: + resolution: {integrity: sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==} + engines: {node: '>=10'} + hasBin: true + + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + + throat@5.0.0: + resolution: {integrity: sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==} + tinybench@2.9.0: resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} @@ -842,6 +1966,17 @@ packages: resolution: {integrity: sha512-WiGwQjr0qYdNNG8KpMKlSvpxz652lqa3Rd+/hSaDcY4Uo6SKWZq2LAF+hsAhUewTtYhXlorBKgNF3Kk8hnjGoQ==} hasBin: true + 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'} + tough-cookie@6.0.1: resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} engines: {node: '>=16'} @@ -853,6 +1988,14 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.7.1: + resolution: {integrity: sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==} + engines: {node: '>=8'} + typescript@6.0.2: resolution: {integrity: sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==} engines: {node: '>=14.17'} @@ -861,15 +2004,29 @@ packages: undici-types@7.18.2: resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} - undici@7.24.5: - resolution: {integrity: sha512-3IWdCpjgxp15CbJnsi/Y9TCDE7HWVN19j1hmzVhoAkY/+CJx449tVxT5wZc1Gwg8J+P0LWvzlBzxYRnHJ+1i7Q==} + undici@7.24.6: + resolution: {integrity: sha512-Xi4agocCbRzt0yYMZGMA6ApD7gvtUFaxm4ZmeacWI4cZxaF6C+8I8QfofC20NAePiB/IcvZmzkJ7XPa471AEtA==} engines: {node: '>=20.18.1'} + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + 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 + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + vite@8.0.3: resolution: {integrity: sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -948,14 +2105,26 @@ packages: jsdom: optional: true + vlq@1.0.1: + resolution: {integrity: sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==} + w3c-xmlserializer@5.0.0: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + + warn-once@0.1.1: + resolution: {integrity: sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==} + webidl-conversions@8.0.1: resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} engines: {node: '>=20'} + whatwg-fetch@3.6.20: + resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==} + whatwg-mimetype@5.0.0: resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} engines: {node: '>=20'} @@ -979,6 +2148,29 @@ packages: engines: {node: '>=8'} hasBin: true + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + 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} + + 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 + xml-name-validator@5.0.0: resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} engines: {node: '>=18'} @@ -986,6 +2178,26 @@ packages: xmlchars@2.2.0: resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@2.8.2: + resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + snapshots: '@adobe/css-tools@4.4.4': {} @@ -1014,25 +2226,194 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/helper-validator-identifier@7.28.5': {} + '@babel/compat-data@7.29.0': {} - '@babel/runtime@7.29.2': {} + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color - '@bramus/specificity@2.4.2': + '@babel/generator@7.29.1': dependencies: - css-tree: 3.2.1 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 - '@csstools/color-helpers@6.0.2': {} + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 - '@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': dependencies: - '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) - '@csstools/css-tokenizer': 4.0.0 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color - '@csstools/css-color-parser@4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': dependencies: - '@csstools/color-helpers': 6.0.2 - '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.29.2': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.2': + dependencies: + '@babel/types': 7.29.0 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/runtime@7.29.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.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.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.2.1 + + '@csstools/color-helpers@6.0.2': {} + + '@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.0.2(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.0.2 + '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) '@csstools/css-tokenizer': 4.0.0 @@ -1069,8 +2450,95 @@ snapshots: react: 19.2.4 use-sync-external-store: 1.6.0(react@19.2.4) + '@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.2 + resolve-from: 5.0.0 + + '@istanbuljs/schema@0.1.3': {} + + '@jest/create-cache-key-function@29.7.0': + dependencies: + '@jest/types': 29.6.3 + + '@jest/environment@29.7.0': + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 25.5.0 + jest-mock: 29.7.0 + + '@jest/fake-timers@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 25.5.0 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.10 + + '@jest/transform@29.7.0': + dependencies: + '@babel/core': 7.29.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.31 + 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/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': 25.5.0 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@jridgewell/sourcemap-codec@1.5.5': {} + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@napi-rs/wasm-runtime@1.1.1': dependencies: '@emnapi/core': 1.9.0 @@ -1080,6 +2548,78 @@ snapshots: '@oxc-project/types@0.122.0': {} + '@react-native/assets-registry@0.84.1': {} + + '@react-native/codegen@0.84.1': + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.2 + hermes-parser: 0.32.0 + invariant: 2.2.4 + nullthrows: 1.1.1 + tinyglobby: 0.2.15 + yargs: 17.7.2 + transitivePeerDependencies: + - supports-color + + '@react-native/community-cli-plugin@0.84.1': + dependencies: + '@react-native/dev-middleware': 0.84.1 + debug: 4.4.3 + invariant: 2.2.4 + metro: 0.83.5 + metro-config: 0.83.5 + metro-core: 0.83.5 + semver: 7.7.4 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@react-native/debugger-frontend@0.84.1': {} + + '@react-native/debugger-shell@0.84.1': + dependencies: + cross-spawn: 7.0.6 + debug: 4.4.3 + fb-dotslash: 0.5.8 + transitivePeerDependencies: + - supports-color + + '@react-native/dev-middleware@0.84.1': + dependencies: + '@isaacs/ttlcache': 1.4.1 + '@react-native/debugger-frontend': 0.84.1 + '@react-native/debugger-shell': 0.84.1 + chrome-launcher: 0.15.2 + chromium-edge-launcher: 0.2.0 + connect: 3.7.0 + debug: 4.4.3 + invariant: 2.2.4 + nullthrows: 1.1.1 + open: 7.4.2 + serve-static: 1.16.3 + ws: 7.5.10 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + '@react-native/gradle-plugin@0.84.1': {} + + '@react-native/js-polyfills@0.84.1': {} + + '@react-native/normalize-colors@0.84.1': {} + + '@react-native/virtualized-lists@0.84.1(@types/react@19.2.14)(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4)': + dependencies: + invariant: 2.2.4 + nullthrows: 1.1.1 + react: 19.2.4 + react-native: 0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4) + optionalDependencies: + '@types/react': 19.2.14 + '@rolldown/binding-android-arm64@1.0.0-rc.12': optional: true @@ -1131,6 +2671,16 @@ snapshots: '@rolldown/pluginutils@1.0.0-rc.7': {} + '@sinclair/typebox@0.27.10': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@10.3.0': + dependencies: + '@sinonjs/commons': 3.0.1 + '@standard-schema/spec@1.1.0': {} '@testing-library/dom@10.4.1': @@ -1170,6 +2720,27 @@ snapshots: '@types/aria-query@5.0.4': {} + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + 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.28.0': + dependencies: + '@babel/types': 7.29.0 + '@types/chai@5.2.3': dependencies: '@types/deep-eql': 4.0.2 @@ -1179,6 +2750,20 @@ snapshots: '@types/estree@1.0.8': {} + '@types/graceful-fs@4.1.9': + dependencies: + '@types/node': 25.5.0 + + '@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/node@25.5.0': dependencies: undici-types: 7.18.2 @@ -1191,12 +2776,20 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/stack-utils@2.0.3': {} + '@types/use-sync-external-store@1.5.0': {} - '@vitejs/plugin-react@6.0.1(vite@8.0.3(@types/node@25.5.0))': + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@vitejs/plugin-react@6.0.1(vite@8.0.3(@types/node@25.5.0)(terser@5.46.1)(yaml@2.8.2))': dependencies: '@rolldown/pluginutils': 1.0.0-rc.7 - vite: 8.0.3(@types/node@25.5.0) + vite: 8.0.3(@types/node@25.5.0)(terser@5.46.1)(yaml@2.8.2) '@vitest/expect@4.1.2': dependencies: @@ -1207,13 +2800,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.2(vite@8.0.3(@types/node@25.5.0))': + '@vitest/mocker@4.1.2(vite@8.0.3(@types/node@25.5.0)(terser@5.46.1)(yaml@2.8.2))': dependencies: '@vitest/spy': 4.1.2 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.3(@types/node@25.5.0) + vite: 8.0.3(@types/node@25.5.0)(terser@5.46.1)(yaml@2.8.2) '@vitest/pretty-format@4.1.2': dependencies: @@ -1239,32 +2832,214 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + accepts@2.0.0: + dependencies: + mime-types: 3.0.2 + negotiator: 1.0.0 + + acorn@8.16.0: {} + + agent-base@7.1.4: {} + + anser@1.4.10: {} + ansi-regex@5.0.1: {} + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + ansi-styles@5.2.0: {} ansi-styles@6.2.3: {} + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + aria-query@5.3.0: dependencies: dequal: 2.0.3 aria-query@5.3.2: {} + asap@2.0.6: {} + assertion-error@2.0.1: {} + babel-jest@29.7.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@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.29.0) + 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.28.6 + '@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-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.28.0 + + babel-plugin-syntax-hermes-parser@0.32.0: + dependencies: + hermes-parser: 0.32.0 + + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.0) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0) + + babel-preset-jest@29.6.3(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) + + balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + base64-js@1.5.1: {} + + baseline-browser-mapping@2.10.9: {} + bidi-js@1.0.3: dependencies: require-from-string: 2.0.2 + boolbase@1.0.0: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + brace-expansion@5.0.4: dependencies: balanced-match: 4.0.4 + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.10.9 + caniuse-lite: 1.0.30001780 + electron-to-chromium: 1.5.321 + node-releases: 2.0.36 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + buffer-from@1.1.2: {} + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001780: {} + chai@6.2.2: {} + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + chrome-launcher@0.15.2: + dependencies: + '@types/node': 25.5.0 + 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': 25.5.0 + 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: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + commander@12.1.0: {} + + commander@2.20.3: {} + + 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 + convert-source-map@2.0.0: {} cross-spawn@7.0.6: @@ -1273,71 +3048,355 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-tree@1.1.3: + dependencies: + mdn-data: 2.0.14 + source-map: 0.6.1 + css-tree@3.2.1: dependencies: - mdn-data: 2.27.1 - source-map-js: 1.2.1 + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + css-what@6.2.2: {} + + css.escape@1.5.1: {} + + csstype@3.2.3: {} + + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js@10.6.0: {} + + depd@2.0.0: {} + + dequal@2.0.3: {} + + destroy@1.2.0: {} + + detect-libc@2.1.2: {} + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.321: {} + + emoji-regex@8.0.0: {} + + encodeurl@1.0.2: {} + + encodeurl@2.0.0: {} + + entities@4.5.0: {} + + entities@6.0.1: {} + + error-stack-parser@2.1.4: + dependencies: + stackframe: 1.3.4 + + es-module-lexer@2.0.0: {} + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@2.0.0: {} + + escape-string-regexp@4.0.0: {} + + esprima@4.0.1: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + etag@1.8.1: {} + + event-target-shim@5.0.1: {} + + expect-type@1.3.0: {} + + expo-modules-core@55.0.17(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4): + dependencies: + invariant: 2.2.4 + react: 19.2.4 + react-native: 0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4) + + exponential-backoff@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fb-dotslash@0.5.8: {} + + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + 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-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + flow-enums-runtime@0.0.6: {} + + fresh@0.5.2: {} + + fs.realpath@1.0.0: {} + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-package-type@0.1.0: {} + + glob@13.0.6: + dependencies: + minimatch: 10.2.4 + minipass: 7.1.3 + path-scurry: 2.0.2 + + 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 + + graceful-fs@4.2.11: {} + + has-flag@4.0.0: {} + + hermes-compiler@250829098.0.9: {} + + hermes-estree@0.32.0: {} + + hermes-estree@0.33.3: {} + + hermes-parser@0.32.0: + dependencies: + hermes-estree: 0.32.0 + + hermes-parser@0.33.3: + dependencies: + hermes-estree: 0.33.3 + + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.15.0 + transitivePeerDependencies: + - '@noble/hashes' + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + image-size@1.2.1: + dependencies: + queue: 6.0.2 - css.escape@1.5.1: {} + imurmurhash@0.1.4: {} - csstype@3.2.3: {} + indent-string@4.0.0: {} - data-urls@7.0.0: + inflight@1.0.6: dependencies: - whatwg-mimetype: 5.0.0 - whatwg-url: 16.0.1 - transitivePeerDependencies: - - '@noble/hashes' - - decimal.js@10.6.0: {} + once: 1.4.0 + wrappy: 1.0.2 - dequal@2.0.3: {} + inherits@2.0.4: {} - detect-libc@2.1.2: {} + invariant@2.2.4: + dependencies: + loose-envify: 1.4.0 - dom-accessibility-api@0.5.16: {} + is-docker@2.2.1: {} - dom-accessibility-api@0.6.3: {} + is-fullwidth-code-point@3.0.0: {} - entities@6.0.1: {} + is-number@7.0.0: {} - es-module-lexer@2.0.0: {} + is-potential-custom-element-name@1.0.1: {} - estree-walker@3.0.3: + is-wsl@2.2.0: dependencies: - '@types/estree': 1.0.8 + is-docker: 2.2.1 - expect-type@1.3.0: {} + isexe@2.0.0: {} - fdir@6.5.0(picomatch@4.0.4): - optionalDependencies: - picomatch: 4.0.4 + isexe@3.1.5: {} - fsevents@2.3.3: - optional: true + istanbul-lib-coverage@3.2.2: {} - glob@13.0.6: + istanbul-lib-instrument@5.2.1: dependencies: - minimatch: 10.2.4 - minipass: 7.1.3 - path-scurry: 2.0.2 + '@babel/core': 7.29.0 + '@babel/parser': 7.29.2 + '@istanbuljs/schema': 0.1.3 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color - html-encoding-sniffer@6.0.0: + jest-environment-node@29.7.0: dependencies: - '@exodus/bytes': 1.15.0 - transitivePeerDependencies: - - '@noble/hashes' + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 25.5.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 - indent-string@4.0.0: {} + jest-get-type@29.6.3: {} - is-potential-custom-element-name@1.0.1: {} + jest-haste-map@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.9 + '@types/node': 25.5.0 + 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 - isexe@2.0.0: {} + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.29.0 + '@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-mock@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 25.5.0 + jest-util: 29.7.0 - isexe@3.1.5: {} + jest-regex-util@29.6.3: {} + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 25.5.0 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + + 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-worker@29.7.0: + dependencies: + '@types/node': 25.5.0 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 js-tokens@4.0.0: {} + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + jsc-safe-url@0.2.4: {} + jsdom@29.0.1: dependencies: '@asamuzakjp/css-color': 5.0.1 @@ -1355,7 +3414,7 @@ snapshots: saxes: 6.0.0 symbol-tree: 3.2.4 tough-cookie: 6.0.1 - undici: 7.24.5 + undici: 7.24.6 w3c-xmlserializer: 5.0.0 webidl-conversions: 8.0.1 whatwg-mimetype: 5.0.0 @@ -1364,8 +3423,21 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' + jsesc@3.1.0: {} + json-parse-even-better-errors@4.0.0: {} + json5@2.2.3: {} + + leven@3.1.0: {} + + lighthouse-logger@1.4.2: + dependencies: + debug: 2.6.9 + marky: 1.3.0 + transitivePeerDependencies: + - supports-color + lightningcss-android-arm64@1.32.0: optional: true @@ -1415,28 +3487,259 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + lodash.throttle@4.1.1: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + lru-cache@11.2.7: {} + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + lz-string@1.5.0: {} magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + + marky@1.3.0: {} + + mdn-data@2.0.14: {} + mdn-data@2.27.1: {} + memoize-one@5.2.1: {} + memorystream@0.3.1: {} + merge-stream@2.0.0: {} + + metro-babel-transformer@0.83.5: + dependencies: + '@babel/core': 7.29.0 + flow-enums-runtime: 0.0.6 + hermes-parser: 0.33.3 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + metro-cache-key@0.83.5: + dependencies: + flow-enums-runtime: 0.0.6 + + metro-cache@0.83.5: + dependencies: + exponential-backoff: 3.1.3 + flow-enums-runtime: 0.0.6 + https-proxy-agent: 7.0.6 + metro-core: 0.83.5 + transitivePeerDependencies: + - supports-color + + metro-config@0.83.5: + dependencies: + connect: 3.7.0 + flow-enums-runtime: 0.0.6 + jest-validate: 29.7.0 + metro: 0.83.5 + metro-cache: 0.83.5 + metro-core: 0.83.5 + metro-runtime: 0.83.5 + yaml: 2.8.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + metro-core@0.83.5: + dependencies: + flow-enums-runtime: 0.0.6 + lodash.throttle: 4.1.1 + metro-resolver: 0.83.5 + + metro-file-map@0.83.5: + dependencies: + debug: 4.4.3 + 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.83.5: + dependencies: + flow-enums-runtime: 0.0.6 + terser: 5.46.1 + + metro-resolver@0.83.5: + dependencies: + flow-enums-runtime: 0.0.6 + + metro-runtime@0.83.5: + dependencies: + '@babel/runtime': 7.29.2 + flow-enums-runtime: 0.0.6 + + metro-source-map@0.83.5: + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + metro-symbolicate: 0.83.5 + nullthrows: 1.1.1 + ob1: 0.83.5 + source-map: 0.5.7 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + + metro-symbolicate@0.83.5: + dependencies: + flow-enums-runtime: 0.0.6 + invariant: 2.2.4 + metro-source-map: 0.83.5 + nullthrows: 1.1.1 + source-map: 0.5.7 + vlq: 1.0.1 + transitivePeerDependencies: + - supports-color + + metro-transform-plugins@0.83.5: + dependencies: + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + flow-enums-runtime: 0.0.6 + nullthrows: 1.1.1 + transitivePeerDependencies: + - supports-color + + metro-transform-worker@0.83.5: + dependencies: + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/parser': 7.29.2 + '@babel/types': 7.29.0 + flow-enums-runtime: 0.0.6 + metro: 0.83.5 + metro-babel-transformer: 0.83.5 + metro-cache: 0.83.5 + metro-cache-key: 0.83.5 + metro-minify-terser: 0.83.5 + metro-source-map: 0.83.5 + metro-transform-plugins: 0.83.5 + nullthrows: 1.1.1 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + metro@0.83.5: + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/parser': 7.29.2 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + accepts: 2.0.0 + chalk: 4.1.2 + ci-info: 2.0.0 + connect: 3.7.0 + debug: 4.4.3 + error-stack-parser: 2.1.4 + flow-enums-runtime: 0.0.6 + graceful-fs: 4.2.11 + hermes-parser: 0.33.3 + image-size: 1.2.1 + invariant: 2.2.4 + jest-worker: 29.7.0 + jsc-safe-url: 0.2.4 + lodash.throttle: 4.1.1 + metro-babel-transformer: 0.83.5 + metro-cache: 0.83.5 + metro-cache-key: 0.83.5 + metro-config: 0.83.5 + metro-core: 0.83.5 + metro-file-map: 0.83.5 + metro-resolver: 0.83.5 + metro-runtime: 0.83.5 + metro-source-map: 0.83.5 + metro-symbolicate: 0.83.5 + metro-transform-plugins: 0.83.5 + metro-transform-worker: 0.83.5 + mime-types: 3.0.2 + 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.54.0: {} + + mime-types@3.0.2: + dependencies: + mime-db: 1.54.0 + + mime@1.6.0: {} + min-indent@1.0.1: {} minimatch@10.2.4: dependencies: brace-expansion: 5.0.4 + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.12 + minipass@7.1.3: {} + mkdirp@1.0.4: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + nanoid@3.3.11: {} + negotiator@1.0.0: {} + + node-int64@0.4.0: {} + + node-releases@2.0.36: {} + + normalize-path@3.0.0: {} + npm-normalize-package-bin@4.0.0: {} npm-run-all2@8.0.4: @@ -1450,14 +3753,57 @@ snapshots: shell-quote: 1.8.3 which: 5.0.0 + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + nullthrows@1.1.1: {} + + ob1@0.83.5: + dependencies: + flow-enums-runtime: 0.0.6 + obug@2.1.1: {} + on-finished@2.3.0: + dependencies: + ee-first: 1.1.1 + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + open@7.4.2: + dependencies: + is-docker: 2.2.1 + is-wsl: 2.2.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-try@2.2.0: {} + package-json-from-dist@1.0.1: {} parse5@8.0.0: dependencies: entities: 6.0.1 + parseurl@1.3.3: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + path-key@3.1.1: {} path-scurry@2.0.2: @@ -1469,10 +3815,14 @@ snapshots: picocolors@1.1.1: {} + picomatch@2.3.1: {} + picomatch@4.0.4: {} pidtree@0.6.0: {} + pirates@4.0.7: {} + postcss@8.5.8: dependencies: nanoid: 3.3.11 @@ -1485,8 +3835,32 @@ snapshots: ansi-styles: 5.2.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 + + promise@8.3.0: + dependencies: + asap: 2.0.6 + punycode@2.3.1: {} + queue@6.0.2: + dependencies: + inherits: 2.0.4 + + range-parser@1.2.1: {} + + react-devtools-core@6.1.5: + dependencies: + shell-quote: 1.8.3 + ws: 7.5.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + react-dom@19.2.4(react@19.2.4): dependencies: react: 19.2.4 @@ -1494,6 +3868,66 @@ snapshots: react-is@17.0.2: {} + react-is@18.3.1: {} + + react-native-svg@15.15.4(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4): + dependencies: + css-select: 5.2.2 + css-tree: 1.1.3 + react: 19.2.4 + react-native: 0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4) + warn-once: 0.1.1 + + react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4): + dependencies: + '@jest/create-cache-key-function': 29.7.0 + '@react-native/assets-registry': 0.84.1 + '@react-native/codegen': 0.84.1 + '@react-native/community-cli-plugin': 0.84.1 + '@react-native/gradle-plugin': 0.84.1 + '@react-native/js-polyfills': 0.84.1 + '@react-native/normalize-colors': 0.84.1 + '@react-native/virtualized-lists': 0.84.1(@types/react@19.2.14)(react-native@0.84.1(@babel/core@7.29.0)(@types/react@19.2.14)(react@19.2.4))(react@19.2.4) + abort-controller: 3.0.0 + anser: 1.4.10 + ansi-regex: 5.0.1 + babel-jest: 29.7.0(@babel/core@7.29.0) + babel-plugin-syntax-hermes-parser: 0.32.0 + base64-js: 1.5.1 + commander: 12.1.0 + flow-enums-runtime: 0.0.6 + hermes-compiler: 250829098.0.9 + invariant: 2.2.4 + jest-environment-node: 29.7.0 + memoize-one: 5.2.1 + metro-runtime: 0.83.5 + metro-source-map: 0.83.5 + nullthrows: 1.1.1 + pretty-format: 29.7.0 + promise: 8.3.0 + react: 19.2.4 + react-devtools-core: 6.1.5 + react-refresh: 0.14.2 + regenerator-runtime: 0.13.11 + scheduler: 0.27.0 + semver: 7.7.4 + stacktrace-parser: 0.1.11 + tinyglobby: 0.2.15 + whatwg-fetch: 3.6.20 + ws: 7.5.10 + yargs: 17.7.2 + optionalDependencies: + '@types/react': 19.2.14 + transitivePeerDependencies: + - '@babel/core' + - '@react-native-community/cli' + - '@react-native/metro-config' + - bufferutil + - supports-color + - utf-8-validate + + react-refresh@0.14.2: {} + react@19.2.4: {} read-package-json-fast@4.0.0: @@ -1506,8 +3940,18 @@ snapshots: indent-string: 4.0.0 strip-indent: 3.0.0 + regenerator-runtime@0.13.11: {} + + require-directory@2.1.1: {} + require-from-string@2.0.2: {} + resolve-from@5.0.0: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + rimraf@6.1.3: dependencies: glob: 13.0.6 @@ -1540,6 +3984,41 @@ snapshots: scheduler@0.27.0: {} + semver@6.3.1: {} + + semver@7.7.4: {} + + send@0.19.2: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.1 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serialize-error@2.1.0: {} + + serve-static@1.16.3: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.2 + transitivePeerDependencies: + - supports-color + + setprototypeof@1.2.0: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -1550,18 +4029,80 @@ snapshots: siginfo@2.0.0: {} + signal-exit@3.0.7: {} + + slash@3.0.0: {} + source-map-js@1.2.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: {} + + sprintf-js@1.0.3: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + stackback@0.0.2: {} + stackframe@1.3.4: {} + + stacktrace-parser@0.1.11: + dependencies: + type-fest: 0.7.1 + + statuses@1.5.0: {} + + statuses@2.0.2: {} + std-env@4.0.0: {} + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + strip-indent@3.0.0: dependencies: min-indent: 1.0.1 + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + symbol-tree@3.2.4: {} + terser@5.46.1: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.16.0 + 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 + + throat@5.0.0: {} + tinybench@2.9.0: {} tinyexec@1.0.4: {} @@ -1579,6 +4120,14 @@ snapshots: dependencies: tldts-core: 7.0.26 + tmpl@1.0.5: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + tough-cookie@6.0.1: dependencies: tldts: 7.0.26 @@ -1589,17 +4138,31 @@ snapshots: tslib@2.8.1: {} + type-detect@4.0.8: {} + + type-fest@0.7.1: {} + typescript@6.0.2: {} undici-types@7.18.2: {} - undici@7.24.5: {} + undici@7.24.6: {} + + unpipe@1.0.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 use-sync-external-store@1.6.0(react@19.2.4): dependencies: react: 19.2.4 - vite@8.0.3(@types/node@25.5.0): + utils-merge@1.0.1: {} + + vite@8.0.3(@types/node@25.5.0)(terser@5.46.1)(yaml@2.8.2): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -1609,11 +4172,13 @@ snapshots: optionalDependencies: '@types/node': 25.5.0 fsevents: 2.3.3 + terser: 5.46.1 + yaml: 2.8.2 - vitest@4.1.2(@types/node@25.5.0)(jsdom@29.0.1)(vite@8.0.3(@types/node@25.5.0)): + vitest@4.1.2(@types/node@25.5.0)(jsdom@29.0.1)(vite@8.0.3(@types/node@25.5.0)(terser@5.46.1)(yaml@2.8.2)): dependencies: '@vitest/expect': 4.1.2 - '@vitest/mocker': 4.1.2(vite@8.0.3(@types/node@25.5.0)) + '@vitest/mocker': 4.1.2(vite@8.0.3(@types/node@25.5.0)(terser@5.46.1)(yaml@2.8.2)) '@vitest/pretty-format': 4.1.2 '@vitest/runner': 4.1.2 '@vitest/snapshot': 4.1.2 @@ -1630,7 +4195,7 @@ snapshots: tinyexec: 1.0.4 tinyglobby: 0.2.15 tinyrainbow: 3.1.0 - vite: 8.0.3(@types/node@25.5.0) + vite: 8.0.3(@types/node@25.5.0)(terser@5.46.1)(yaml@2.8.2) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 25.5.0 @@ -1638,12 +4203,22 @@ snapshots: transitivePeerDependencies: - msw + vlq@1.0.1: {} + w3c-xmlserializer@5.0.0: dependencies: xml-name-validator: 5.0.0 + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + warn-once@0.1.1: {} + webidl-conversions@8.0.1: {} + whatwg-fetch@3.6.20: {} + whatwg-mimetype@5.0.0: {} whatwg-url@16.0.1: @@ -1667,6 +4242,39 @@ snapshots: siginfo: 2.0.0 stackback: 0.0.2 + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + write-file-atomic@4.0.2: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + + ws@7.5.10: {} + xml-name-validator@5.0.0: {} xmlchars@2.2.0: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yaml@2.8.2: {} + + yargs-parser@21.1.1: {} + + 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 diff --git a/release-please-config.json b/release-please-config.json index cdc6272..197f944 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -9,6 +9,9 @@ }, "packages/google-signin": { "include-component-in-tag": true + }, + "packages/apple-signin": { + "include-component-in-tag": true } } } \ No newline at end of file