diff --git a/.eas/workflows/build-android-production.yml b/.eas/workflows/build-android-production.yml
index 5f930af..e87dbf6 100644
--- a/.eas/workflows/build-android-production.yml
+++ b/.eas/workflows/build-android-production.yml
@@ -1,7 +1,7 @@
name: Build Android production
# Release-oriented triggers to conserve EAS build credits (free tier):
-# - push a version tag (e.g. v2.1) to build that release, or
+# - push a version tag (e.g. v3.3.1) to build that release, or
# - run it manually from the Expo dashboard ("Run workflow") / `eas workflow:run`.
# Intentionally NOT triggered on every push to main, to avoid burning build minutes.
on:
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
index b66170b..f255c74 100644
--- a/.github/ISSUE_TEMPLATE/config.yml
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -1,4 +1,4 @@
-blank_issues_enabled: true
+blank_issues_enabled: false
contact_links:
- name: Security vulnerability
url: https://github.com/TheStreamCode/keysoft/security/policy
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..28a49e8
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,11 @@
+version: 2
+
+updates:
+ - package-ecosystem: bun
+ directory: /
+ schedule:
+ interval: weekly
+ day: monday
+ time: '06:00'
+ timezone: Europe/Rome
+ open-pull-requests-limit: 5
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 9986a2e..235a3ce 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -32,12 +32,8 @@ jobs:
with:
bun-version: '1.3.14'
- # Dependabot edits package.json but cannot regenerate bun.lock, so a frozen
- # install fails on every one of its pull requests -- which is the sole reason
- # they are all red. Typecheck, lint and test still run either way; only the
- # lockfile constraint is relaxed, and only for that actor.
- name: Install dependencies
- run: bun install ${{ github.actor == 'dependabot[bot]' && '--no-frozen-lockfile' || '--frozen-lockfile' }}
+ run: bun install --frozen-lockfile
- name: Check formatting
run: bun run format:check
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4bbf61a..b1b1a8f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,8 +4,32 @@ All notable project changes are documented here.
## [Unreleased]
+No unreleased changes.
+
+## [3.3.1] - 2026-08-09
+
+### Release
+
+- Prepared Keysoft 3.3.1 with native runtime 3.3.1 and Android production build target 130. EAS remote versioning remains authoritative and auto-increments from build 129;
+ Google Play submission remains a separate manual step.
+
### Security
+- Made PIN rotation and transparent KDF upgrades transactional: preference reads now
+ happen before vault mutation, ciphertext and verifier metadata define the core commit,
+ and a later biometric cleanup failure can no longer roll back only one side of the
+ vault. An unrecoverable pre-commit rollback still closes the authenticated session.
+- Changed validated backup imports from repeated per-record writes to one encrypted batch.
+ Imported records are merged by ID, password limits are checked against the merged vault,
+ all ciphertext is prepared before persistence, and decrypted caches update only after
+ the batch succeeds.
+- Reject backup files whose size cannot be established before reading them, and reject KS1
+ payloads that lack a complete AES-CBC ciphertext block or are not block-aligned.
+- Require an active vault key before clearing password or note collections, and explicitly
+ remove `SYSTEM_ALERT_WINDOW` from generated Android manifests.
+- Prevent initial PIN setup from overwriting an existing verifier. If first-time database
+ initialization fails after a new verifier was saved, remove that verifier and clear all
+ authentication/key state before returning failure.
- Kept the cached master-key verifier unchanged when its SecureStore write fails, so an
unpersisted verifier cannot be observed later in the same session.
- Closed the authenticated session and cleared the in-memory vault key if a transparent
@@ -42,12 +66,28 @@ All notable project changes are documented here.
### Tooling
+- Updated Expo SDK 57 packages to the compatible versions selected by `expo install`,
+ refreshed compatible JavaScript dependencies, removed the duplicate TypeScript ESLint
+ plugin registration, and restored `expo-doctor` to 20/20 checks.
+- Added weekly Bun Dependabot updates and made every CI installation use the committed
+ lockfile with `bun install --frozen-lockfile`.
- Added `.codex/` to the ignored local agent-state directories, so a future Codex session
cannot leave working state staged by accident, and folded the redundant trailing
`.claude/settings.local.json` rule into the `.claude/` entry that already covered it.
### Documentation
+- Reworked the GitHub README around the product value proposition, an immediate Google
+ Play call to action, concise trust links, and a four-screen gallery sourced from the
+ production store listing. Removed fast-stale dependency/test counters from the product
+ overview and aligned contributor validation with the one-command verification workflow.
+- Disabled blank GitHub issues so bug, feature, and private security reports follow the
+ repository's structured intake paths.
+- Updated the public GitHub description around Keysoft's offline-first promise and changed
+ the repository homepage from the generic studio site to the product's Google Play page.
+- Added the dated full security and code-audit report in
+ `docs/security-audit-2026-08-08.md` and synchronized the security, development, and
+ release guides with the hardened import, PIN-change, dependency, and permission rules.
- Documented the accepted cryptographic and operational trade-offs in `docs/security.md`
under a new "Known Limitations" section: the shared AES/HMAC key, the PBKDF2-SHA1 backup
KDF, the six-digit master PIN keyspace, the absence of unlock throttling, opt-in
diff --git a/CITATION.cff b/CITATION.cff
index 546c66d..1b9cc40 100644
--- a/CITATION.cff
+++ b/CITATION.cff
@@ -5,5 +5,6 @@ authors:
- family-names: Gasperini
given-names: Michael
url: 'https://github.com/TheStreamCode/keysoft'
-version: '3.3.0'
+version: '3.3.1'
+date-released: '2026-08-09'
license: 'Apache-2.0'
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 815ebeb..c6ae12a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -9,10 +9,13 @@ biometrics, import/export, notifications, or release configuration.
## Development Setup
```bash
-bun install
+bun install --frozen-lockfile
bun run start
```
+Use Bun 1.3.14 and Node.js 22.13 or newer. The lockfile is part of the reviewed
+dependency graph and must remain frozen during clean validation.
+
Android development uses Expo Go for day-to-day work. Release-grade validation
for Argon2 and native modules requires an EAS/native Android build.
@@ -21,10 +24,8 @@ for Argon2 and native modules requires an EAS/native Android build.
Run:
```bash
-bun run typecheck
-bun run lint
-bun run test
-bunx expo-doctor
+bun run verify
+bun run deps:audit
```
For Android release-related changes, also run:
diff --git a/README.md b/README.md
index 8e71f60..c9b98b6 100644
--- a/README.md
+++ b/README.md
@@ -1,207 +1,180 @@
-
+
+
+
# Keysoft
-[](https://github.com/TheStreamCode/keysoft/actions/workflows/ci.yml)
-[](LICENSE)
-
-Keysoft is an offline-first password manager for Android, with cloud-simulator compatibility for iPhone and iPad, built with Expo, React Native, and TypeScript. It stores vault data locally and protects user content with authenticated encryption.
-
-The application is designed around a local-only operating model: the PIN/master password is never persisted, vault data is encrypted at rest, and the app does not require a backend service to operate.
-
-Keysoft works offline for vault management. Network access is limited to platform services such as Expo/EAS updates and is not used to sync, upload, or transmit vault contents, PINs, master passwords, or encryption keys.
-
-## Current Status
-
-| Area | Status |
-| ------------------- | ---------------------------------- |
-| Platform focus | Android release; iOS cloud testing |
-| App version | 3.3.0 |
-| Android versionCode | 129 (EAS production target) |
-| Expo SDK | 57.0.9 |
-| React Native | 0.86.2 |
-| TypeScript | 6.0.3, strict mode |
-| Test suite | 30 suites, 199 tests |
-| Health check | `expo-doctor` 20/20 |
-
-Keysoft 3.3.0 is published on [Google Play](https://play.google.com/store/apps/details?id=it.mikesoft.keysoft). Production build numbers are managed remotely by EAS; the 3.3.0 release uses Android versionCode 129. `app.config.js` mirrors that value for local manifest visibility, while `appVersionSource: "remote"` keeps EAS authoritative for future build numbers.
-
-## Core Capabilities
-
-- Password vault with create, read, update, delete, search, pagination, and categories.
-- Secure notes stored with the same vault encryption model.
-- Local password generator backed by cryptographically secure randomness.
-- Optional biometric unlock backed by SecureStore with device authentication.
-- Local notifications for security reminders and backup prompts.
-- Encrypted import/export workflow for user-managed backups.
-- Italian and English localization with system-language detection.
-- Automated i18n checks for Italian/English key parity, placeholder parity, and user-facing fallback regressions.
-- Source-structure regression checks for lowercase directories and shared settings hook placement.
-- Nocturne light/dark interface with responsive phone/tablet layouts, a dedicated PIN keypad, copy-first credential views, reduced-motion support, and accessible touch targets.
-- Local vault-health analysis for weak, reused, and expired credentials.
-
-## Design And Interaction
-
-- Semantic theme tokens provide automatic light and dark appearances without screen-specific color branching.
-- Responsive content bounds support compact phones, landscape, tablets, and split-view widths while preserving safe areas and 44-point minimum touch targets.
-- Alerts and confirmations use the shared dialog primitive, selection workflows use bottom sheets, and non-blocking feedback uses in-app toasts.
-- Entrance effects animate only opacity and transforms and respect the operating-system reduced-motion preference.
-- The profile photo is previewed locally and persisted only when profile changes are saved; the same avatar and initial fallback are rendered on unlock, vault, and settings screens.
-- The original Keysoft shield-and-eye artwork is the canonical launcher, adaptive, splash, favicon, and onboarding mark.
+
+ A private, offline-first password manager for Android.
+ No account, no ads, no cloud vault, and no tracking SDKs.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Google Play ·
+ Privacy ·
+ Security ·
+ Latest release
+
+
+Keysoft keeps passwords and secure notes encrypted on the device and works without a
+backend service. Network access is limited to platform services such as Expo/EAS update
+delivery; vault contents, PINs, master passwords, and encryption keys are never used for
+sync or remote storage.
+
+## Why Keysoft
+
+- **Private by default:** no registration, cloud vault, advertising, or analytics SDK.
+- **Useful offline:** create, search, organize, and copy credentials without connectivity.
+- **Encrypted locally:** authenticated vault encryption with tamper detection.
+- **Fast unlock:** optional biometric access backed by device-authenticated SecureStore.
+- **User-owned backups:** password-protected import/export without a remote backup account.
+- **Practical security tools:** secure notes, password generation, vault-health checks,
+ clipboard auto-clear, local reminders, and optional screenshot protection.
+- **Accessible and responsive:** Italian and English UI, light/dark themes, reduced-motion
+ support, and phone/tablet layouts.
+
+## Product Tour
+
+The screenshots below are taken from the production Android listing on Google Play.
+
+
+
+  |
+  |
+  |
+  |
+
+
+ | Quick entry |
+ Password generator |
+ Secure notes |
+ Encrypted backups |
+
+
## Security Model
-Keysoft uses the KS1 envelope for vault data:
-
-- AES-256-CBC for encryption.
-- HMAC-SHA256 for integrity verification.
-- Argon2id in EAS/native builds when available, PBKDF2 fallback in Expo Go or where required.
-- Expo Go is a development workflow. Vaults created in Expo Go use PBKDF2 fallback metadata; use EAS/native builds for release-grade Argon2 validation.
-- 64-character hex derived keys.
-- CSPRNG-backed salts, IVs, IDs, and password generation.
-- The vault key stays in memory by default. If biometrics are enabled, the vault key is stored in SecureStore with device authentication required and is updated or removed when the PIN changes.
-- PIN setup and PIN changes reuse the vault key derived during verifier creation to avoid duplicate KDF work while preserving the configured Argon2/PBKDF2 cost.
-- Verifier metadata is cached only after SecureStore accepts the write. A failed KDF-upgrade rollback closes the session rather than continuing with an uncertain vault key.
-
-Backup files use a password-encrypted payload format (`KS1-PW1`) with KDF metadata and authenticated KS1 ciphertext.
-
-See [Security Architecture](docs/security.md) for the full model, operational assumptions, and storage rules.
-
-## Documentation
-
-- [Architecture](docs/architecture.md)
-- [Security Architecture](docs/security.md)
-- [Development Guide](docs/development.md)
-- [Release Guide](docs/release.md)
-- [iOS Testing Without Apple Hardware](docs/ios-testing.md)
-- [Public Repository Checklist](docs/publication.md)
-- [First-Party Copyright And Scope Record](COPYRIGHT.md)
-- [Pre-Build Security/UI Review](docs/pre-build-security-ui-review.md)
-- [Keysoft 3.3.0 Release Notes](docs/releases/3.3.0.md)
-- [Changelog](CHANGELOG.md)
+Keysoft uses the versioned KS1 envelope for vault data:
-## Requirements
+- AES-256-CBC encryption with HMAC-SHA256 integrity verification.
+- Argon2id key derivation in native builds; Expo Go uses the documented PBKDF2
+ development fallback.
+- CSPRNG-backed salts, IVs, identifiers, and password generation.
+- A 64-character derived vault key held in memory by default.
+- Optional biometric unlock stores only the derived vault key in SecureStore with device
+ authentication required.
+- Encrypted backups use the versioned `KS1-PW1` format and a user-provided passphrase.
-- Bun 1.3.14 (the version declared in `package.json` and used by CI)
-- Node.js 22.13 or newer
-- Expo Go installed on the Android device used for development
-- Expo account access for EAS builds on expo.dev
-- Expo CLI via `bunx expo`
+Keysoft cannot recover a forgotten master PIN because it has no server-side recovery
+material. Read the [security architecture](docs/security.md) for the full model, accepted
+trade-offs, and migration rules. Vulnerabilities should be reported privately through the
+[security policy](SECURITY.md), never through a public issue.
-## Installation
+## Current Release
-```bash
-bun install
-```
-
-Use `bun install --frozen-lockfile` in CI and when validating a clean checkout.
+Keysoft 3.3.1 is the current source release and Android production-build target.
+[Google Play](https://play.google.com/store/apps/details?id=it.mikesoft.keysoft) currently
+distributes Keysoft 3.3.0 (build 129) until the separately reviewed 3.3.1 app bundle is
+submitted. Android is the production platform. iPhone and iPad remain cloud-simulator
+compatibility targets and are not currently distributed through the App Store.
-## Configuration
+The source of truth for shipped changes is the [changelog](CHANGELOG.md) and the
+[latest GitHub release](https://github.com/TheStreamCode/keysoft/releases/latest). Exact
+verification results are recorded in dated release and audit documents instead of being
+duplicated here.
-Keysoft needs no environment variables or backend service for local development. The tracked `.env.example` documents this intentionally empty configuration. Local `.env*` files, `.secrets/`, signing keys, and generated native projects are ignored by Git.
+## Developer Quick Start
-Never store secrets in `EXPO_PUBLIC_*` variables: Expo embeds those values in the client bundle. Store EAS credentials in EAS-managed secrets and repository automation credentials in GitHub Actions secrets. The Expo project identifier and update URLs in `app.config.js` are public application metadata, not credentials.
+Requirements:
-## Development
+- Bun 1.3.14.
+- Node.js 22.13 or newer.
+- Expo Go on an Android device for day-to-day development.
-Start the Expo development server for Expo Go:
+Install and start:
```bash
+bun install
bun run start
```
-Open on Android with Expo Go:
+Useful commands:
```bash
bun run android
-```
-
-Use a tunnel when the phone cannot reach the local machine over LAN:
-
-```bash
bun run start:tunnel
-```
-
-Run web for development/testing:
-
-```bash
bun run web
-```
-
-Build artifacts are produced on expo.dev through EAS:
-
-```bash
-bun run build:android:preview
-bun run build:android:production
-bun run build:ios:simulator
-```
-
-EAS builds upload the project to expo.dev. Start them only after the release checklist is complete and the upload has been explicitly approved.
-
-### Build from GitHub
-
-The repository is linked to EAS Build. The Android production workflow runs only for version tags matching `v*` or by manual dispatch. Google Play submission is currently manual; `bun run submit:android:production` is reserved for a future configured submission profile. iOS is retained as a cloud-simulator test target, and App Store publication is intentionally outside the project release workflow.
-
-## Verification
-
-Run the full local verification suite before shipping changes:
-
-```bash
bun run verify
bun run deps:audit
-bunx expo export --platform android --output-dir C:\tmp\keysoft-android-export
```
-`bun run verify` checks formatting, TypeScript, ESLint, the coverage-enabled Jest suite, and Expo project health. The individual commands remain available as `bun run format:check`, `bun run typecheck`, `bun run lint`, `bun run test`, and `bunx expo-doctor`.
-
-Current verified state:
+Use `bun install --frozen-lockfile` for clean validation. Keysoft requires no local
+environment variables or backend service. Never place credentials in `EXPO_PUBLIC_*`
+variables: Expo embeds them in the client bundle.
-- `bun run typecheck`: passing
-- `bun run lint`: passing
-- `bun run test:ci`: passing, 30 suites and 199 tests
-- `bunx expo-doctor`: passing, 20/20 checks
-- `bunx expo export --platform android --output-dir C:\tmp\keysoft-android-export`: passing
-- `bunx expo export --platform ios --output-dir C:\tmp\keysoft-ios-export`: passing
+Release artifacts are produced through EAS. EAS build, submission, tag, and release
+operations must only be started through the documented, explicitly approved release
+workflow. See the [development guide](docs/development.md) and
+[release guide](docs/release.md).
## Project Structure
```text
src/
- components/ Shared UI components
- brand/ Canonical in-app brand mark
- ui/ Dialog, bottom sheet, toast, motion, PIN keypad, and controls
- contexts/ Application state providers
- hooks/ Complex screen and behavior logic
- settings/ Settings workflows extracted from SettingsScreen
- (usePinManagement, useNotificationSettings,
- useProfileForm, useExportImport)
- locales/ i18n dictionaries (it.ts, en.ts) consumed by LanguageContext
+ components/ Shared UI and interaction primitives
+ contexts/ Authentication, language, theme, and alert state
+ hooks/ Complex screen and settings workflows
+ locales/ Italian and English dictionaries
models/ TypeScript domain models
- navigation/ Navigation configuration (typed via NativeStackScreenProps)
- screens/ User-facing screens
- services/ Business, crypto, storage, auth, and utility services
- utils/ Shared platform and security helpers (incl. withTimeout)
+ navigation/ Typed application navigation
+ screens/ User-facing application screens
+ services/ Auth, crypto, storage, import/export, and utilities
+ utils/ Shared platform and security helpers
```
-Path aliases (`@/*`, `@components/*`, `@services/*`, …) are configured in
-`tsconfig.json` and `babel.config.js` for use in new code.
-
-## Data Ownership
-
-Keysoft is local-first. The user owns their vault data and is responsible for keeping backup files and the master password secure. The app cannot recover a lost master password because no server-side recovery material exists.
+## Documentation
-## Support
+- [Architecture](docs/architecture.md)
+- [Security Architecture](docs/security.md)
+- [Security and Code Audit (2026-08-08)](docs/security-audit-2026-08-08.md)
+- [Development Guide](docs/development.md)
+- [Release Guide](docs/release.md)
+- [iOS Testing Without Apple Hardware](docs/ios-testing.md)
+- [Public Repository Checklist](docs/publication.md)
+- [Keysoft 3.3.1 Release Notes](docs/releases/3.3.1.md)
+- [Changelog](CHANGELOG.md)
-Product assistance is available over email at [keysoft@mikesoft.it](mailto:keysoft@mikesoft.it). Settings > Support opens a pre-addressed message and copies the address to the clipboard when the device has no mail client registered. Do not include passwords, PINs, encryption keys, or real vault data in support requests.
+## Contributing
-If Keysoft's local-first security work is useful to you, support continued development through GitHub Sponsors: [github.com/sponsors/TheStreamCode](https://github.com/sponsors/TheStreamCode).
+Contributions are welcome through focused pull requests. Read
+[CONTRIBUTING.md](CONTRIBUTING.md), keep security-sensitive changes small and testable,
+and run `bun run verify` before requesting review.
-The current public privacy notice is available in [English](https://mikesoft.it/en/keysoft-policy/) and [Italian](https://mikesoft.it/it/keysoft-policy/). It documents Android distribution, iOS cloud-simulator testing, local storage, Expo Updates traffic, permissions, and user-controlled backups.
+## Data Ownership and Support
-## License
+Users own their local vault data and are responsible for retaining backup files and the
+master passphrase. Product assistance is available at
+[keysoft@mikesoft.it](mailto:keysoft@mikesoft.it); never include real vault contents,
+PINs, encryption keys, or backup payloads in support requests.
-This project is licensed under the [Apache License 2.0](LICENSE) (`Apache-2.0`). See the [first-party copyright and scope record](COPYRIGHT.md), [third-party notices](THIRD_PARTY_NOTICES.md), and [TRADEMARKS.md](TRADEMARKS.md).
+Keysoft development can be supported through
+[GitHub Sponsors](https://github.com/sponsors/TheStreamCode).
-## Responsible Disclosure
+## License and Attribution
-Please do not open public issues for vulnerabilities. Follow [SECURITY.md](SECURITY.md) for private reporting guidance.
+Keysoft is licensed under the [Apache License 2.0](LICENSE). See
+[COPYRIGHT.md](COPYRIGHT.md), [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md), and
+[TRADEMARKS.md](TRADEMARKS.md) for first-party scope, dependency notices, and trademark
+terms.
diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md
index 2f791bf..34045ce 100644
--- a/THIRD_PARTY_NOTICES.md
+++ b/THIRD_PARTY_NOTICES.md
@@ -1,6 +1,6 @@
# Third-Party Notices
-Keysoft includes open-source dependencies. The inventory below records the direct runtime dependencies resolved for Keysoft 3.3.0. Transitive packages and their license files remain available in the package distribution and lockfile.
+Keysoft includes open-source dependencies. The inventory below records the direct runtime dependencies resolved for Keysoft 3.3.1. Transitive packages and their license files remain available in the package distribution and lockfile.
Unless noted otherwise, these direct dependencies are licensed under the MIT License:
diff --git a/app.config.js b/app.config.js
index afd85d7..64b94a6 100644
--- a/app.config.js
+++ b/app.config.js
@@ -1,7 +1,7 @@
module.exports = {
name: 'Keysoft',
slug: 'keysoft',
- version: '3.3.0',
+ version: '3.3.1',
scheme: 'keysoft',
orientation: 'default', // Supports both portrait and landscape
userInterfaceStyle: 'automatic',
@@ -55,7 +55,7 @@ module.exports = {
backgroundColor: '#FFFFFF',
},
package: 'it.mikesoft.keysoft',
- versionCode: 129,
+ versionCode: 130,
permissions: [
'INTERNET',
'USE_BIOMETRIC',
@@ -69,6 +69,7 @@ module.exports = {
'android.permission.RECORD_AUDIO',
'android.permission.READ_MEDIA_IMAGES',
'android.permission.READ_MEDIA_VIDEO',
+ 'android.permission.SYSTEM_ALERT_WINDOW',
],
allowBackup: false,
// Support multiple screen sizes
diff --git a/bun.lock b/bun.lock
index c8f2a80..686a0cd 100644
--- a/bun.lock
+++ b/bun.lock
@@ -9,31 +9,31 @@
"@expo/vector-icons": "^15.1.1",
"@react-native-async-storage/async-storage": "^2.2.0",
"@react-native-community/slider": "5.2.0",
- "@react-navigation/bottom-tabs": "^7.18.14",
- "@react-navigation/native": "^7.3.14",
- "@react-navigation/native-stack": "^7.18.6",
- "@react-navigation/stack": "^7.10.17",
+ "@react-navigation/bottom-tabs": "^7.18.16",
+ "@react-navigation/native": "^7.3.16",
+ "@react-navigation/native-stack": "^7.18.8",
+ "@react-navigation/stack": "^7.10.22",
"crypto-js": "^4.2.0",
- "expo": "~57.0.9",
- "expo-asset": "~57.0.8",
- "expo-build-properties": "~57.0.8",
+ "expo": "~57.0.11",
+ "expo-asset": "~57.0.9",
+ "expo-build-properties": "~57.0.9",
"expo-clipboard": "~57.0.1",
- "expo-constants": "~57.0.8",
+ "expo-constants": "~57.0.9",
"expo-crypto": "~57.0.1",
"expo-document-picker": "~57.0.1",
- "expo-file-system": "~57.0.1",
+ "expo-file-system": "~57.0.2",
"expo-font": "~57.0.1",
- "expo-image-picker": "~57.0.7",
+ "expo-image-picker": "~57.0.8",
"expo-local-authentication": "~57.0.2",
"expo-localization": "~57.0.1",
"expo-navigation-bar": "~57.0.2",
- "expo-notifications": "~57.0.8",
+ "expo-notifications": "~57.0.9",
"expo-screen-capture": "~57.0.1",
"expo-secure-store": "~57.0.1",
- "expo-sharing": "~57.0.8",
+ "expo-sharing": "~57.0.10",
"expo-status-bar": "~57.0.1",
"expo-system-ui": "~57.0.2",
- "expo-updates": "~57.0.11",
+ "expo-updates": "~57.0.12",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-native": "0.86.2",
@@ -50,14 +50,13 @@
},
"devDependencies": {
"@babel/core": "^7.29.7",
- "@babel/plugin-transform-export-namespace-from": "^7.27.1",
+ "@babel/plugin-transform-export-namespace-from": "^7.29.7",
"@react-native/jest-preset": "0.86.2",
"@testing-library/react-native": "^13.3.3",
"@types/jest": "29.5.14",
"@types/react": "~19.2.18",
- "@typescript-eslint/eslint-plugin": "^8.63.0",
"babel-plugin-module-resolver": "^5.0.3",
- "babel-preset-expo": "~57.0.5",
+ "babel-preset-expo": "~57.0.6",
"eslint": "^9.39.5",
"eslint-config-expo": "~57.0.1",
"globals": "^16.5.0",
@@ -102,7 +101,7 @@
"@babel/helper-optimise-call-expression": ["@babel/helper-optimise-call-expression@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" } }, "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw=="],
- "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+ "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
"@babel/helper-remap-async-to-generator": ["@babel/helper-remap-async-to-generator@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-wrap-function": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA=="],
@@ -184,7 +183,7 @@
"@babel/plugin-transform-destructuring": ["@babel/plugin-transform-destructuring@7.28.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/traverse": "^7.28.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw=="],
- "@babel/plugin-transform-export-namespace-from": ["@babel/plugin-transform-export-namespace-from@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ=="],
+ "@babel/plugin-transform-export-namespace-from": ["@babel/plugin-transform-export-namespace-from@7.29.7", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA=="],
"@babel/plugin-transform-flow-strip-types": ["@babel/plugin-transform-flow-strip-types@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-syntax-flow": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg=="],
@@ -272,13 +271,13 @@
"@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
- "@expo/cli": ["@expo/cli@57.0.11", "", { "dependencies": { "@expo/code-signing-certificates": "^0.0.6", "@expo/config": "~57.0.6", "@expo/config-plugins": "~57.0.6", "@expo/devcert": "^1.2.1", "@expo/env": "~2.4.2", "@expo/image-utils": "^0.11.4", "@expo/inline-modules": "^0.1.4", "@expo/json-file": "^11.0.1", "@expo/log-box": "^57.0.2", "@expo/metro": "~56.0.0", "@expo/metro-config": "~57.0.7", "@expo/metro-file-map": "^57.0.1", "@expo/osascript": "^2.7.1", "@expo/package-manager": "^1.13.1", "@expo/plist": "^0.8.1", "@expo/prebuild-config": "^57.0.10", "@expo/require-utils": "^57.0.4", "@expo/router-server": "^57.0.4", "@expo/schema-utils": "^57.0.2", "@expo/spawn-async": "^1.8.0", "@expo/ws-tunnel": "^2.0.0", "@expo/xcpretty": "^4.4.4", "@react-native/dev-middleware": "0.86.2", "accepts": "^1.3.8", "agent-cli-detector": "^0.1.2", "arg": "^5.0.2", "bplist-creator": "0.1.0", "bplist-parser": "^0.3.1", "chalk": "^4.0.0", "ci-info": "^3.3.0", "compression": "^1.7.4", "connect": "^3.7.0", "debug": "^4.3.4", "dnssd-advertise": "^1.1.4", "expo-server": "^57.0.1", "fetch-nodeshim": "^0.4.10", "getenv": "^2.0.0", "glob": "^13.0.0", "lan-network": "^0.2.1", "multitars": "^1.0.0", "node-forge": "^1.3.3", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "picomatch": "^4.0.4", "pretty-format": "^29.7.0", "progress": "^2.0.3", "prompts": "^2.3.2", "resolve-from": "^5.0.0", "semver": "^7.6.0", "send": "^0.19.0", "slugify": "^1.3.4", "stacktrace-parser": "^0.1.10", "structured-headers": "^0.4.1", "terminal-link": "^2.1.1", "toqr": "^0.1.1", "wrap-ansi": "^7.0.0", "ws": "^8.12.1", "zod": "^3.25.76" }, "peerDependencies": { "expo": "*", "expo-router": "*", "react-native": "*" }, "optionalPeers": ["expo-router", "react-native"], "bin": { "expo-internal": "main.js" } }, "sha512-ENXCwRyL8Q9qbabUmm0L6w9kXTmdGotW7eDEEWmUDXLPux+nEzNDqw0MzWQ5K3ZsXS51QmUJZg5yETB+6SnNRg=="],
+ "@expo/cli": ["@expo/cli@57.0.13", "", { "dependencies": { "@expo/code-signing-certificates": "^0.0.6", "@expo/config": "~57.0.6", "@expo/config-plugins": "~57.0.7", "@expo/devcert": "^1.2.1", "@expo/env": "~2.4.2", "@expo/image-utils": "^0.11.4", "@expo/inline-modules": "^0.1.4", "@expo/json-file": "^11.0.1", "@expo/log-box": "^57.0.2", "@expo/metro": "~56.0.0", "@expo/metro-config": "~57.0.7", "@expo/metro-file-map": "^57.0.1", "@expo/osascript": "^2.7.1", "@expo/package-manager": "^1.13.1", "@expo/plist": "^0.8.1", "@expo/prebuild-config": "^57.0.10", "@expo/require-utils": "^57.0.4", "@expo/router-server": "^57.0.5", "@expo/schema-utils": "^57.0.2", "@expo/spawn-async": "^1.8.0", "@expo/ws-tunnel": "^2.0.0", "@expo/xcpretty": "^4.4.4", "@react-native/dev-middleware": "0.86.2", "accepts": "^1.3.8", "agent-cli-detector": "^0.1.2", "arg": "^5.0.2", "bplist-creator": "0.1.0", "bplist-parser": "^0.3.1", "chalk": "^4.0.0", "ci-info": "^3.3.0", "compression": "^1.7.4", "connect": "^3.7.0", "debug": "^4.3.4", "dnssd-advertise": "^1.1.4", "expo-server": "^57.0.1", "fetch-nodeshim": "^0.4.10", "getenv": "^2.0.0", "glob": "^13.0.0", "lan-network": "^0.2.1", "multitars": "^1.0.0", "node-forge": "^1.3.3", "npm-package-arg": "^11.0.0", "ora": "^3.4.0", "picomatch": "^4.0.4", "pretty-format": "^29.7.0", "progress": "^2.0.3", "prompts": "^2.3.2", "resolve-from": "^5.0.0", "semver": "^7.6.0", "send": "^0.19.0", "slugify": "^1.3.4", "stacktrace-parser": "^0.1.10", "structured-headers": "^0.4.1", "terminal-link": "^2.1.1", "toqr": "^0.1.1", "wrap-ansi": "^7.0.0", "ws": "^8.12.1", "zod": "^3.25.76" }, "peerDependencies": { "expo": "*", "expo-router": "*", "react-native": "*" }, "optionalPeers": ["expo-router", "react-native"], "bin": { "expo-internal": "main.js" } }, "sha512-8gjLMyx+s0dLeDHlcfjM9D9x5yrCU5C6516rmC7q/Wiyuj1fxgr/cbDSmjdpQKkjlvvfvNwtRyMk2zhvhPohiw=="],
"@expo/code-signing-certificates": ["@expo/code-signing-certificates@0.0.6", "", { "dependencies": { "node-forge": "^1.3.3" } }, "sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w=="],
"@expo/config": ["@expo/config@57.0.6", "", { "dependencies": { "@expo/config-plugins": "~57.0.6", "@expo/config-types": "^57.0.2", "@expo/json-file": "^11.0.1", "@expo/require-utils": "^57.0.4", "deepmerge": "^4.3.1", "getenv": "^2.0.0", "glob": "^13.0.0", "resolve-workspace-root": "^2.0.0", "semver": "^7.6.0", "slugify": "^1.3.4" } }, "sha512-VpMJpB/De/fb9bBFVVBiK6Ntg9lt0kAleLH9hcZz85CYRUQ3jVFVA8rNC5f8y4cp2+FiiPNFp62+kEOFI6pDiw=="],
- "@expo/config-plugins": ["@expo/config-plugins@57.0.6", "", { "dependencies": { "@expo/config-types": "^57.0.2", "@expo/json-file": "~11.0.1", "@expo/plist": "^0.8.1", "@expo/require-utils": "^57.0.4", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", "debug": "^4.3.5", "getenv": "^2.0.0", "glob": "^13.0.0", "semver": "^7.5.4", "slugify": "^1.6.6", "xcode": "^3.0.1", "xml2js": "0.6.0" } }, "sha512-7CmKrS5Rnu8aSZyNlxH2qzA7Ls1HEa4EQvEVOAkHDKPr1e4Cg/nz7I7dUl09QDTVjMvkKYDH4Th0DuAsgqASaw=="],
+ "@expo/config-plugins": ["@expo/config-plugins@57.0.7", "", { "dependencies": { "@expo/config-types": "^57.0.2", "@expo/json-file": "~11.0.1", "@expo/plist": "^0.8.1", "@expo/require-utils": "^57.0.4", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", "debug": "^4.3.5", "getenv": "^2.0.0", "glob": "^13.0.0", "semver": "^7.5.4", "slugify": "^1.6.6", "xcode": "^3.0.1", "xml2js": "0.6.0" } }, "sha512-jvXMiNuH8W7fmU9yCk4/jVwDX2G/5rWUg5PZ22mriccEeQVS9HJjQiUHivMaK6MxEG5L9f0RPScxe/nQfnpQvg=="],
"@expo/config-types": ["@expo/config-types@57.0.2", "", {}, "sha512-ewW08OonrcRIsRKIlFvvcmmafE5zemb1ocu3HkNwtVPyRtj2w42pZCAkMIROYpcVBaPnc3mDT9UZDzwXWC3i6g=="],
@@ -322,7 +321,7 @@
"@expo/require-utils": ["@expo/require-utils@57.0.4", "", { "dependencies": { "@babel/code-frame": "^7.20.0", "@babel/core": "^7.25.2", "@babel/plugin-transform-modules-commonjs": "^7.24.8" }, "peerDependencies": { "typescript": "^5.0.0 || ^5.0.0-0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["typescript"] }, "sha512-e7xbg/9BTQcsZE/oErafZXtI7kh5IgfasLJ97J5sFSzX2cA74pDvdlhW1KHVSaDkQyQv6h1LSLhsY7dEeOk7hw=="],
- "@expo/router-server": ["@expo/router-server@57.0.4", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "@expo/metro-runtime": "^57.0.7", "expo": "*", "expo-constants": "^57.0.7", "expo-font": "^57.0.1", "expo-router": "*", "expo-server": "^57.0.1", "react": "*", "react-dom": "*", "react-server-dom-webpack": "~19.0.1 || ~19.1.2 || ~19.2.1" }, "optionalPeers": ["@expo/metro-runtime", "expo-router", "react-dom", "react-server-dom-webpack"] }, "sha512-ucqCP0hK8nZb9+S8QJYdQxNCkfRClzkKdg2RpWYDKDYgRIHyoRyxEDRgDgvbhH+q78yfY83abU8OTx8MMOrf1g=="],
+ "@expo/router-server": ["@expo/router-server@57.0.5", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "@expo/metro-runtime": "^57.0.8", "expo": "*", "expo-constants": "^57.0.9", "expo-font": "^57.0.1", "expo-router": "*", "expo-server": "^57.0.1", "react": "*", "react-dom": "*", "react-server-dom-webpack": "~19.0.1 || ~19.1.2 || ~19.2.1" }, "optionalPeers": ["@expo/metro-runtime", "expo-router", "react-dom", "react-server-dom-webpack"] }, "sha512-vke39l0bo3H2q9JB/KXpAJ7HpscdTG3Mktbxanc8yn3riWzzSsnv0uxwZGZCrZrnDQzFxlLTXgbZrGkU26ng1w=="],
"@expo/schema-utils": ["@expo/schema-utils@57.0.2", "", {}, "sha512-fMu/jyN0l1Wzv7XkeWR4IYCx1M8ryui3FdBNGrWwbRgJ7EhxXxK8E2jxP2W3pbgUwUY0V3hG8+GyfCZwny+Lxw=="],
@@ -440,19 +439,19 @@
"@react-native/virtualized-lists": ["@react-native/virtualized-lists@0.86.2", "", { "dependencies": { "invariant": "^2.2.4", "nullthrows": "^1.1.1" }, "peerDependencies": { "@types/react": "^19.2.0", "react": "*", "react-native": "0.86.2" }, "optionalPeers": ["@types/react"] }, "sha512-uO0J72gh3EvE+1/GHRk18QRyBDTRHRB0AraAfojsRjbT7VMuJwKrZYaKGshavoaEud6aw00ZB9/8mTMIKjjcAw=="],
- "@react-navigation/bottom-tabs": ["@react-navigation/bottom-tabs@7.18.14", "", { "dependencies": { "@react-navigation/elements": "^2.9.36", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { "@react-navigation/native": "^7.3.14", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-A3V9rDSut459TBPtkD7rb0npUUBlJBfMunyRT5nOGKqPguhuXWk1h91NfQMuqyW2DCUvvFZChzsbLbj42rXTdQ=="],
+ "@react-navigation/bottom-tabs": ["@react-navigation/bottom-tabs@7.18.16", "", { "dependencies": { "@react-navigation/elements": "^2.9.38", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0" }, "peerDependencies": { "@react-navigation/native": "^7.3.16", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-5tBFuxvsCQBvYauDZK6u/uPdD86i/caWgBzABlZfTh8W2J7YZSHjT9SWOsquSMmR6dpcNj6idMd4utCNzuOnZg=="],
- "@react-navigation/core": ["@react-navigation/core@7.21.11", "", { "dependencies": { "@react-navigation/routers": "^7.6.4", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "query-string": "^7.1.3", "react-is": "^19.1.0", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": ">= 18.2.0" } }, "sha512-bCW1PsLA/eOXDOukcJFEzlcL3Zpy8DJuDCfkDDwAQlAgoSZ/J9+ZeDRUMmCUi6xbnFgvFEEIMertaLeErOFP0Q=="],
+ "@react-navigation/core": ["@react-navigation/core@7.21.12", "", { "dependencies": { "@react-navigation/routers": "^7.6.4", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "query-string": "^7.1.3", "react-is": "^19.1.0", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "react": ">= 18.2.0" } }, "sha512-FopGMGy5df+IMqUYg9w7ygwK0oP4RaQbMnpb7VFP30+vZdd8MqsAGc+bZZwdiVSnzK9hzhgJtZV9XF0ZFlUYOQ=="],
- "@react-navigation/elements": ["@react-navigation/elements@2.9.36", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.3.14", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-+10x9s5v2Q7FwAYdSmPMgILtxZyC5e4hWJQu8g5o3u4p8DUToTBmGvys/UvmEr+h9xmm0Go42qw9Ff2ape53kQ=="],
+ "@react-navigation/elements": ["@react-navigation/elements@2.9.38", "", { "dependencies": { "color": "^4.2.3", "use-latest-callback": "^0.2.4", "use-sync-external-store": "^1.5.0" }, "peerDependencies": { "@react-native-masked-view/masked-view": ">= 0.2.0", "@react-navigation/native": "^7.3.16", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0" }, "optionalPeers": ["@react-native-masked-view/masked-view"] }, "sha512-M/2HWDiSO4zO7VyfAnovrTPWOAzTC+1DnVT7ZWZW93+/KVRF9OYlqixIUpPenQ0iQh2j1EaQ4zOe/134Arisqw=="],
- "@react-navigation/native": ["@react-navigation/native@7.3.14", "", { "dependencies": { "@react-navigation/core": "^7.21.11", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "standard-navigation": "^0.0.8", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-hcKTDNBuuAA1/xW6QeKYmMPVhk5W9dKGQpPmn5dQeeePwMpu5OZ14NOgwKH0w9D3tg2jupojTcVL0tsx5DTFXg=="],
+ "@react-navigation/native": ["@react-navigation/native@7.3.16", "", { "dependencies": { "@react-navigation/core": "^7.21.12", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.3.11", "standard-navigation": "^0.0.8", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "react": ">= 18.2.0", "react-native": "*" } }, "sha512-Wy/6mai2HpA5AEVFk7JFfvv4RUArwuN2UenP/fc3NK7kOyfSlNu1tmb/3JDPpMzPIRbF1MPB8PKAHjVB29qNEQ=="],
- "@react-navigation/native-stack": ["@react-navigation/native-stack@7.18.6", "", { "dependencies": { "@react-navigation/elements": "^2.9.36", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.3.14", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-KuvvSBddHrbKC4c6yKz+UCFey2xgTuPQHgjf2wJQAvA7JM8jCQa3G1+QzhO6d44nYNKir3y6EounXZvQE/BX6w=="],
+ "@react-navigation/native-stack": ["@react-navigation/native-stack@7.18.8", "", { "dependencies": { "@react-navigation/elements": "^2.9.38", "color": "^4.2.3", "sf-symbols-typescript": "^2.1.0", "warn-once": "^0.1.1" }, "peerDependencies": { "@react-navigation/native": "^7.3.16", "react": ">= 18.2.0", "react-native": "*", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-Abcdt2ndmbeNSzJCXggxduK7X9yvMIajPNPdHgxsAZaA+16aIhb7TXZ2kRXprStZX5TRg8x+8G07Ne3kFwIQDg=="],
"@react-navigation/routers": ["@react-navigation/routers@7.6.4", "", { "dependencies": { "nanoid": "^3.3.11" } }, "sha512-GI7eJm8/KsZUQaYcXvEExikKurRZRgEsSzyZ7faENfi65yqJBCXjDMwyN1pF6pNW1MoLH1ErDwDivFxY6BzD3w=="],
- "@react-navigation/stack": ["@react-navigation/stack@7.10.17", "", { "dependencies": { "@react-navigation/elements": "^2.9.36", "color": "^4.2.3", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "@react-navigation/native": "^7.3.14", "react": ">= 18.2.0", "react-native": "*", "react-native-gesture-handler": ">= 2.0.0", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-2b/AC8YxfPkFsN8DZGjrMiX6G86unbEonE0E8dA5LugzfKF/s28VBM7yBwZKRsp3ys5aVCvt/Z3zviQA/uGAIA=="],
+ "@react-navigation/stack": ["@react-navigation/stack@7.10.22", "", { "dependencies": { "@react-navigation/elements": "^2.9.38", "color": "^4.2.3", "use-latest-callback": "^0.2.4" }, "peerDependencies": { "@react-navigation/native": "^7.3.16", "react": ">= 18.2.0", "react-native": "*", "react-native-gesture-handler": ">= 2.0.0", "react-native-safe-area-context": ">= 4.0.0", "react-native-screens": ">= 4.0.0" } }, "sha512-R6I4nEWnm2M78FvyKRBApvi3g2o8K8YfA9/GE975xiV9+LrZVsHzeZE41mNa+lQKKpytd8DBlRIWMlKnKk/KhA=="],
"@rtsao/scc": ["@rtsao/scc@1.1.0", "", {}, "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g=="],
@@ -654,7 +653,7 @@
"babel-preset-current-node-syntax": ["babel-preset-current-node-syntax@1.1.0", "", { "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw=="],
- "babel-preset-expo": ["babel-preset-expo@57.0.5", "", { "dependencies": { "@babel/generator": "^7.20.5", "@babel/helper-module-imports": "^7.25.9", "@babel/plugin-proposal-decorators": "^7.12.9", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-class-static-block": "^7.27.1", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-export-namespace-from": "^7.25.9", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.28.6", "@babel/plugin-transform-react-jsx-development": "^7.27.1", "@babel/plugin-transform-react-pure-annotations": "^7.27.1", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/preset-typescript": "^7.23.0", "@react-native/babel-plugin-codegen": "0.86.2", "babel-plugin-react-compiler": "^1.0.0", "babel-plugin-react-native-web": "~0.21.0", "babel-plugin-syntax-hermes-parser": "^0.36.0", "babel-plugin-transform-flow-enums": "^0.0.2", "debug": "^4.3.4" }, "peerDependencies": { "@babel/runtime": "^7.20.0", "expo": "*", "expo-widgets": "^57.0.7", "react-refresh": ">=0.14.0 <1.0.0" }, "optionalPeers": ["@babel/runtime", "expo", "expo-widgets"] }, "sha512-sz9ZBTiUAlu5P9VORVNKoeAaY2qKFQRC6eQV5Y8aMkqcorvXKEv97UeCkFMLmMBEPKA+QeWImREKkX9/H21WQA=="],
+ "babel-preset-expo": ["babel-preset-expo@57.0.6", "", { "dependencies": { "@babel/generator": "^7.20.5", "@babel/helper-module-imports": "^7.25.9", "@babel/plugin-proposal-decorators": "^7.12.9", "@babel/plugin-proposal-export-default-from": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-default-from": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-transform-async-generator-functions": "^7.25.4", "@babel/plugin-transform-async-to-generator": "^7.24.7", "@babel/plugin-transform-block-scoping": "^7.25.0", "@babel/plugin-transform-class-properties": "^7.25.4", "@babel/plugin-transform-class-static-block": "^7.27.1", "@babel/plugin-transform-classes": "^7.25.4", "@babel/plugin-transform-destructuring": "^7.24.8", "@babel/plugin-transform-export-namespace-from": "^7.25.9", "@babel/plugin-transform-flow-strip-types": "^7.25.2", "@babel/plugin-transform-for-of": "^7.24.7", "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", "@babel/plugin-transform-modules-commonjs": "^7.24.8", "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", "@babel/plugin-transform-object-rest-spread": "^7.24.7", "@babel/plugin-transform-optional-catch-binding": "^7.24.7", "@babel/plugin-transform-optional-chaining": "^7.24.8", "@babel/plugin-transform-parameters": "^7.24.7", "@babel/plugin-transform-private-methods": "^7.24.7", "@babel/plugin-transform-private-property-in-object": "^7.24.7", "@babel/plugin-transform-react-display-name": "^7.24.7", "@babel/plugin-transform-react-jsx": "^7.28.6", "@babel/plugin-transform-react-jsx-development": "^7.27.1", "@babel/plugin-transform-react-pure-annotations": "^7.27.1", "@babel/plugin-transform-runtime": "^7.24.7", "@babel/plugin-transform-typescript": "^7.25.2", "@babel/plugin-transform-unicode-regex": "^7.24.7", "@babel/preset-typescript": "^7.23.0", "@react-native/babel-plugin-codegen": "0.86.2", "babel-plugin-react-compiler": "^1.0.0", "babel-plugin-react-native-web": "~0.21.0", "babel-plugin-syntax-hermes-parser": "^0.36.0", "babel-plugin-transform-flow-enums": "^0.0.2", "debug": "^4.3.4" }, "peerDependencies": { "@babel/runtime": "^7.20.0", "expo": "*", "expo-widgets": "^57.0.8", "react-refresh": ">=0.14.0 <1.0.0" }, "optionalPeers": ["@babel/runtime", "expo", "expo-widgets"] }, "sha512-ASyy0iP7yPQtq2QaMEnZG4O7YQ/x4sTMguk7PZcow2OHFnWsBpX6v+UCmh/uwCzGfD1q93jDQEuACWwWx4kSrw=="],
"babel-preset-jest": ["babel-preset-jest@29.6.3", "", { "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA=="],
@@ -890,17 +889,17 @@
"expect": ["expect@29.7.0", "", { "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw=="],
- "expo": ["expo@57.0.9", "", { "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "^57.0.11", "@expo/config": "~57.0.6", "@expo/config-plugins": "~57.0.6", "@expo/devtools": "~57.0.1", "@expo/dom-webview": "~57.0.1", "@expo/fingerprint": "^0.20.6", "@expo/local-build-cache-provider": "^57.0.5", "@expo/log-box": "^57.0.2", "@expo/metro": "~56.0.0", "@expo/metro-config": "~57.0.7", "@ungap/structured-clone": "^1.3.0", "babel-preset-expo": "~57.0.5", "expo-asset": "~57.0.8", "expo-constants": "~57.0.8", "expo-file-system": "~57.0.1", "expo-font": "~57.0.1", "expo-keep-awake": "~57.0.1", "expo-modules-autolinking": "~57.0.9", "expo-modules-core": "~57.0.8", "pretty-format": "^29.7.0", "react-refresh": "^0.14.2", "whatwg-url-minimum": "^0.1.2" }, "peerDependencies": { "@expo/metro-runtime": "*", "react": "*", "react-dom": "*", "react-native": "*", "react-native-web": "*", "react-native-webview": "*" }, "optionalPeers": ["@expo/metro-runtime", "react-dom", "react-native-web", "react-native-webview"], "bin": { "expo": "bin/cli", "fingerprint": "bin/fingerprint", "expo-modules-autolinking": "bin/autolinking" } }, "sha512-NDEvnU+vjRdMbtOyDC25OSok9/E97al97pyx+bMphQgNRGAED0D7oF8ha7oGYsLE+7jFzpPADVM9S60dGbpHIA=="],
+ "expo": ["expo@57.0.11", "", { "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "^57.0.13", "@expo/config": "~57.0.6", "@expo/config-plugins": "~57.0.7", "@expo/devtools": "~57.0.1", "@expo/dom-webview": "~57.0.1", "@expo/fingerprint": "^0.20.6", "@expo/local-build-cache-provider": "^57.0.5", "@expo/log-box": "^57.0.2", "@expo/metro": "~56.0.0", "@expo/metro-config": "~57.0.7", "@ungap/structured-clone": "^1.3.0", "babel-preset-expo": "~57.0.6", "expo-asset": "~57.0.9", "expo-constants": "~57.0.9", "expo-file-system": "~57.0.2", "expo-font": "~57.0.1", "expo-keep-awake": "~57.0.1", "expo-modules-autolinking": "~57.0.9", "expo-modules-core": "~57.0.10", "pretty-format": "^29.7.0", "react-refresh": "^0.14.2", "whatwg-url-minimum": "^0.1.2" }, "peerDependencies": { "@expo/metro-runtime": "*", "react": "*", "react-dom": "*", "react-native": "*", "react-native-web": "*", "react-native-webview": "*" }, "optionalPeers": ["@expo/metro-runtime", "react-dom", "react-native-web", "react-native-webview"], "bin": { "expo": "bin/cli", "fingerprint": "bin/fingerprint", "expo-modules-autolinking": "bin/autolinking" } }, "sha512-R97257N39Dw0kQFuI4/RvYx95GQ+dmePdo8hxcMOjDxAT4VcCckjILJeAWCE19Jxjb92hZ5NDXAfDPkkV1RB9w=="],
"expo-application": ["expo-application@57.0.2", "", { "peerDependencies": { "expo": "*" } }, "sha512-q31YwcXyymviAmdrtDfAg3Dld4VMxLCNAfgMHip7vZpPX4lzF/AfwsywqBJUAjQnJknzaNAkzqvMVjO5XmKYDA=="],
- "expo-asset": ["expo-asset@57.0.8", "", { "dependencies": { "@expo/image-utils": "^0.11.4", "expo-constants": "~57.0.8" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-sGocG+Wd2WcZ1KjKyB2LfUZXzrBM0VHEATGcpJKF9T3HM56M/sMbH73vv+n85u5Zr0FkJjEbV1DWhGPimCR1QQ=="],
+ "expo-asset": ["expo-asset@57.0.9", "", { "dependencies": { "@expo/image-utils": "^0.11.4", "expo-constants": "~57.0.9" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-FXlwwW5ThJ2kwXqVX0VcYcDrbmzPDUGPYJOuQZYfsdVB+TLA8LmOgU0Y5ykzLmLs5ONiqs/YjT6Uubv1NUQFzg=="],
- "expo-build-properties": ["expo-build-properties@57.0.8", "", { "dependencies": { "@expo/schema-utils": "^57.0.2", "resolve-from": "^5.0.0", "semver": "^7.6.0" }, "peerDependencies": { "expo": "*" } }, "sha512-6/SDeqKLKaF8MRfLZ3JhiiDN0y5uXtyefhWRZ42ZX03QVZ8Fw4dnbdTSHpsBvbKNIluYk5bP/zosdwkbeeNd9A=="],
+ "expo-build-properties": ["expo-build-properties@57.0.9", "", { "dependencies": { "@expo/schema-utils": "^57.0.2", "resolve-from": "^5.0.0", "semver": "^7.6.0" }, "peerDependencies": { "expo": "*" } }, "sha512-IX8Nz85yNDZyqBK7zbLEfn4ijMGaF6TmLjCY2KE0UKltUoV78DhgN6ksPZbDbPDvqLpU2fMY/5OD4CbYB7hHwg=="],
"expo-clipboard": ["expo-clipboard@57.0.1", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-HWICri4+1ao7S6QEfcorxVumXDiDnx1guGGewjZgGJWLGxFYs0RgH8ujBs+lkTzBkMmlwADaWSlaesR+nDJt5Q=="],
- "expo-constants": ["expo-constants@57.0.8", "", { "dependencies": { "@expo/env": "~2.4.2" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-ts+B7E5076BtkblrKGy7+Sm/R2obHfTwqhmYQVYbWRtilv3+C/xNwHZhyNEnAvzM/JjKqx7UdaITUBYaeFreZw=="],
+ "expo-constants": ["expo-constants@57.0.9", "", { "dependencies": { "@expo/env": "~2.4.2" }, "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-Y47sGiF+U8fwicUSPdJPjB27PuU+FgLK4Mpai0ksZF5hv8jNN3HBqKuDNxsiu70uHBKf80TaR4gwMPh0pmETiQ=="],
"expo-crypto": ["expo-crypto@57.0.1", "", { "peerDependencies": { "expo": "*" } }, "sha512-xwegXQw3ATgeL1ZuqbSNrGzOeG+zNeh6Z6DSJk825Qpa3TEQQ1kG3ioE1p3g/SNF373BAVz2iBKUTSytlIbBRA=="],
@@ -908,13 +907,13 @@
"expo-eas-client": ["expo-eas-client@57.0.1", "", {}, "sha512-4w51+zsl/ziUHQMJgLgUdgsNhRPAwHBfySpPB1hpWU21X74QS9T4SqDftaRnrDagn/DfcrXUMJvHbDQUxLPJNA=="],
- "expo-file-system": ["expo-file-system@57.0.1", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-w7/ERvQFrGP2apTO9lDtZ+O6JQIhfakL7+Xqzh+rfMO9B4LB4qwrz+YvLgir8KFRVX64JHBnRuYBVLY1oQZcqw=="],
+ "expo-file-system": ["expo-file-system@57.0.2", "", { "peerDependencies": { "expo": "*", "react-native": "*" } }, "sha512-bPgzpaOJ3NWHZxV++Osj/1QoFzFe/QOo1jBF4EODJdiZgrrxyi2dv+7PLhKuut5QqPBuihUOITRh3s5jhRtA5A=="],
"expo-font": ["expo-font@57.0.1", "", { "dependencies": { "fontfaceobserver": "^2.1.0" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-QyS9L1Kh9sKJg4gfU6rdbpxpmH+DyzBX8z6jVvXMUDoqLr1GqmkO/Wu379KCXjL///kWbhpNlbi7AgBuj4VdIQ=="],
"expo-image-loader": ["expo-image-loader@57.0.1", "", { "peerDependencies": { "expo": "*" } }, "sha512-uhrZKLT/cTl2mXyR28kPpVkS5O+PK9N1QA/07IFM4f5T4g0lTW1JHT3NEWwEEsGFldPmVX4j7LwUVVZxE+woug=="],
- "expo-image-picker": ["expo-image-picker@57.0.7", "", { "dependencies": { "expo-image-loader": "~57.0.1" }, "peerDependencies": { "expo": "*" } }, "sha512-aPZis1VeeAOeWrM/VID/As7/hF/WM6egtN2AUY+ODV97oBKfAZcGHMw7MR/5BoEy3HXzR5n1ZSYMEwnHBhICvg=="],
+ "expo-image-picker": ["expo-image-picker@57.0.8", "", { "dependencies": { "expo-image-loader": "~57.0.1" }, "peerDependencies": { "expo": "*" } }, "sha512-LXJCGxunnQ4beqk+TW3+llRyss/XITOKWdIjqgF1H9mL3CgKN+hvABWAqLVWapGz7RoJBf+r53FO9i48yicbbw=="],
"expo-json-utils": ["expo-json-utils@57.0.1", "", {}, "sha512-cgTe1NqzQdYs/WN+3nIY5IZg8s0pb0xaTUbhYvxQDn137GbwRfHoGM2se3m3Vsl4Qu+B9G4RPEK5WJDEU2Do7g=="],
@@ -928,13 +927,13 @@
"expo-modules-autolinking": ["expo-modules-autolinking@57.0.9", "", { "dependencies": { "@expo/require-utils": "^57.0.4", "@expo/spawn-async": "^1.8.0", "chalk": "^4.1.0", "commander": "^7.2.0" }, "bin": { "expo-modules-autolinking": "bin/expo-modules-autolinking.js" } }, "sha512-lj2nsAKMMRLXSFnGgaaQrWJ2fdSpLPc/bca6Rkiw4g8zYPn7qX4MRUJgavvzm/hBrzvMnlhXVgJtidOGuwBh+w=="],
- "expo-modules-core": ["expo-modules-core@57.0.8", "", { "dependencies": { "@expo/expo-modules-macros-plugin": "0.6.1", "expo-modules-jsi": "~57.0.4", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-worklets": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0" }, "optionalPeers": ["react-native-worklets"] }, "sha512-ZUU943Oso3B8jFggC+2+LOk8oa+im66IQlleYay6WP5iexbdF2fqpgC4BcYsN4xhCkDKh7KDINSt1FlcxyRRjQ=="],
+ "expo-modules-core": ["expo-modules-core@57.0.10", "", { "dependencies": { "@expo/expo-modules-macros-plugin": "0.6.1", "expo-modules-jsi": "~57.0.4", "invariant": "^2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*", "react-native-worklets": "^0.7.4 || ^0.8.0 || ^0.9.0 || ^0.10.0" }, "optionalPeers": ["react-native-worklets"] }, "sha512-aRi3G8OctoyZl8x9CLTVpbPljClfKm2eqKRgBzhcGsrH3gS1t5Y2ye7+PIZK4XK2VVP5iGqygN2b1vtqRo3xVQ=="],
"expo-modules-jsi": ["expo-modules-jsi@57.0.4", "", { "peerDependencies": { "react-native": "*" } }, "sha512-vt7FyqUqqFXiRVnBqYD7y+GSPTgeua5Ocoy0+SYt+RSHkZEA2Fyop7If3g1TYDzQObYybPRo7TG2Rle1XLaWFw=="],
"expo-navigation-bar": ["expo-navigation-bar@57.0.2", "", { "dependencies": { "debug": "^4.3.2" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-Ufe49dsTZjarA7gMMwwwnNJWpXo+oTyl5Ud0g3lsGVrj2HXjyw9cb9098N8+CoWpjfIa5PghuRYYXrcCrGQIHg=="],
- "expo-notifications": ["expo-notifications@57.0.8", "", { "dependencies": { "@expo/image-utils": "^0.11.4", "abort-controller": "^3.0.0", "badgin": "^1.1.5", "expo-application": "~57.0.2", "expo-constants": "~57.0.8" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-9pTNFMB9vk7MmULRSq7SD2nBRXhIh7IYAOSAFzFIYna6csJKdzeuKikjuUwP5KZYwxejoaN2BYBBqdLKaUpT/w=="],
+ "expo-notifications": ["expo-notifications@57.0.9", "", { "dependencies": { "@expo/image-utils": "^0.11.4", "abort-controller": "^3.0.0", "badgin": "^1.1.5", "expo-application": "~57.0.2", "expo-constants": "~57.0.9" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-AeA4sdgvfeyw5/O+JPekCIex8JfoBO21fABfZCKAeXF19pWaVtXva08dqwF41JW/LWAmvrshCAH36gP+jdVD4Q=="],
"expo-screen-capture": ["expo-screen-capture@57.0.1", "", { "peerDependencies": { "expo": "*", "react": "*" } }, "sha512-toam1doTc0auN0SzQA2ziamXaDhevvCBxhrGyYdVMpfbmj0+0brm+u/pAUkJJMe/f+l3LvWRwltV0dLaMdiY9A=="],
@@ -942,7 +941,7 @@
"expo-server": ["expo-server@57.0.1", "", {}, "sha512-sBfVDH6dmKVHZxqUxbfkzS00PZELMZt1IpnHKxcOTMZtR/t7CtRAFrbXcisG+EyzeqHSVDacZT+1tbYfZt5D8w=="],
- "expo-sharing": ["expo-sharing@57.0.8", "", { "dependencies": { "@expo/config-plugins": "^57.0.6", "@expo/config-types": "^57.0.2", "@expo/plist": "^0.8.1" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-ZjiA09iJjzveachyBlbIijSH++fiacdvuenxb/gsdW21V3SUSxMQd+krPmafnhnMus5aBqpP0TXCBfBJN+0Bfg=="],
+ "expo-sharing": ["expo-sharing@57.0.10", "", { "dependencies": { "@expo/config-plugins": "^57.0.7", "@expo/config-types": "^57.0.2", "@expo/plist": "^0.8.1" }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-WvZSgY96NIR3ZHEud5T4K5Q+OK+WW7PGN8CZ1zIYIhxlwKlMOf9qGNCYVDY3pMPU8FK7n+gbiguGxulWEXot6w=="],
"expo-status-bar": ["expo-status-bar@57.0.1", "", { "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, "sha512-Xwaq1gAoVRWx5dPG5VhT5RSbnI9OilhZnO5qoPBnUaBAa5VzRzfdS8q0/bsPt0jR2DKLtGuP0bQ6efMJ4RIMDg=="],
@@ -950,7 +949,7 @@
"expo-system-ui": ["expo-system-ui@57.0.2", "", { "dependencies": { "@react-native/normalize-colors": "0.86.2", "debug": "^4.3.2" }, "peerDependencies": { "expo": "*", "react-native": "*", "react-native-web": "*" }, "optionalPeers": ["react-native-web"] }, "sha512-zABCRqFSioDBAo/RtmS0dQiGgDtDPZUVk01Y3Ti4ducobNM4HTM2sNAtq+YPpEUhaVW3hccPVsEUH4LK/ADVhA=="],
- "expo-updates": ["expo-updates@57.0.11", "", { "dependencies": { "@expo/code-signing-certificates": "^0.0.6", "@expo/plist": "^0.8.1", "@expo/spawn-async": "^1.8.0", "arg": "^4.1.0", "chalk": "^4.1.2", "debug": "^4.3.4", "expo-eas-client": "~57.0.1", "expo-manifests": "~57.0.1", "expo-structured-headers": "~57.0.0", "expo-updates-interface": "~57.0.1", "getenv": "^2.0.0", "glob": "^13.0.0", "ignore": "^5.3.1", "nullthrows": "^1.1.1", "resolve-from": "^5.0.0" }, "peerDependencies": { "expo": "*", "expo-dev-client": "*", "react": "*", "react-native": "*" }, "optionalPeers": ["expo-dev-client"], "bin": { "expo-updates": "bin/cli.js" } }, "sha512-V8m+bVHvyUXggjmhxt/t4yyj6oDfdAN0kdx0nAt9p2i7xGci6lkzfWzcwsNUE0ZmStHR4ZyzQVFR1U0vP7C/7g=="],
+ "expo-updates": ["expo-updates@57.0.12", "", { "dependencies": { "@expo/code-signing-certificates": "^0.0.6", "@expo/plist": "^0.8.1", "@expo/spawn-async": "^1.8.0", "arg": "^4.1.0", "chalk": "^4.1.2", "debug": "^4.3.4", "expo-eas-client": "~57.0.1", "expo-manifests": "~57.0.1", "expo-structured-headers": "~57.0.0", "expo-updates-interface": "~57.0.1", "getenv": "^2.0.0", "glob": "^13.0.0", "ignore": "^5.3.1", "nullthrows": "^1.1.1", "resolve-from": "^5.0.0" }, "peerDependencies": { "expo": "*", "expo-dev-client": "*", "react": "*", "react-native": "*" }, "optionalPeers": ["expo-dev-client"], "bin": { "expo-updates": "bin/cli.js" } }, "sha512-ZFsW8Mi9qFrrYPSXF++1FXejjflRdgbVPzaSJJATuHelVmIIb3D98gCO9sIsaLPHO1RCQVj1ltkj51VnwVL+4g=="],
"expo-updates-interface": ["expo-updates-interface@57.0.1", "", { "peerDependencies": { "expo": "*" } }, "sha512-+LUWwJ0gf/TEKMVdQAw/Gjih4dvrk+URgy24X9qEGKuuMDZqjBRm9T4yQyBVALGL5TTdPUaB6ILxx3lshm3pwQ=="],
@@ -1080,7 +1079,7 @@
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
- "ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
+ "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
"image-size": ["image-size@1.2.1", "", { "dependencies": { "queue": "6.0.2" }, "bin": "bin/image-size.js" }, "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw=="],
@@ -1556,8 +1555,6 @@
"react-native-web": ["react-native-web@0.21.2", "", { "dependencies": { "@babel/runtime": "^7.18.6", "@react-native/normalize-colors": "^0.74.1", "fbjs": "^3.0.4", "inline-style-prefixer": "^7.0.1", "memoize-one": "^6.0.0", "nullthrows": "^1.1.1", "postcss-value-parser": "^4.2.0", "styleq": "^0.1.3" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" } }, "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg=="],
- "react-native-webview": ["react-native-webview@13.16.1", "", { "dependencies": { "escape-string-regexp": "^4.0.0", "invariant": "2.2.4" }, "peerDependencies": { "react": "*", "react-native": "*" } }, "sha512-If0eHhoEdOYDcHsX+xBFwHMbWBGK1BvGDQDQdVkwtSIXiq1uiqjkpWVP2uQ1as94J0CzvFE9PUNDuhiX0Z6ubw=="],
-
"react-native-worklets": ["react-native-worklets@0.10.1", "", { "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.27.1", "@babel/plugin-transform-class-properties": "^7.28.6", "@babel/plugin-transform-classes": "^7.28.6", "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", "@babel/plugin-transform-optional-chaining": "^7.28.6", "@babel/plugin-transform-shorthand-properties": "^7.27.1", "@babel/plugin-transform-template-literals": "^7.27.1", "@babel/plugin-transform-unicode-regex": "^7.27.1", "@babel/preset-typescript": "^7.28.5", "@babel/types": "^7.27.1", "convert-source-map": "^2.0.0", "semver": "^7.7.4" }, "peerDependencies": { "@babel/core": "*", "@react-native/metro-config": "*", "react": "*", "react-native": "0.83 - 0.86" } }, "sha512-62mRM19bDpfpdI8HLkEErcdOsrAPDtE9lA/sw+5lLRpzBHNhxaoj9QyY2KjXqUmirelxkX4zuPGTC3VdA0feJA=="],
"react-refresh": ["react-refresh@0.14.2", "", {}, "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA=="],
@@ -1898,6 +1895,8 @@
"@babel/helper-define-polyfill-provider/@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="],
+ "@babel/helper-define-polyfill-provider/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
"@babel/helper-define-polyfill-provider/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
"@babel/helper-member-expression-to-functions/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
@@ -1922,17 +1921,63 @@
"@babel/helper-wrap-function/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
- "@babel/plugin-syntax-jsx/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
+ "@babel/plugin-proposal-decorators/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-proposal-export-default-from/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-syntax-async-generators/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-syntax-bigint/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-syntax-class-properties/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-syntax-class-static-block/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-syntax-decorators/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-syntax-dynamic-import/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-syntax-export-default-from/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-syntax-flow/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-syntax-import-attributes/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-syntax-import-meta/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-syntax-json-strings/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-syntax-logical-assignment-operators/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-syntax-nullish-coalescing-operator/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-syntax-numeric-separator/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-syntax-object-rest-spread/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
- "@babel/plugin-syntax-typescript/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
+ "@babel/plugin-syntax-optional-catch-binding/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-syntax-optional-chaining/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-syntax-private-property-in-object/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-syntax-top-level-await/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-transform-arrow-functions/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-transform-async-generator-functions/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
"@babel/plugin-transform-async-generator-functions/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
"@babel/plugin-transform-async-to-generator/@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
+ "@babel/plugin-transform-async-to-generator/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-transform-block-scoping/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
"@babel/plugin-transform-class-properties/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/traverse": "^7.29.7", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg=="],
- "@babel/plugin-transform-class-properties/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
+ "@babel/plugin-transform-class-static-block/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
"@babel/plugin-transform-classes/@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.28.6", "", { "dependencies": { "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA=="],
@@ -1942,43 +1987,57 @@
"@babel/plugin-transform-classes/@babel/traverse": ["@babel/traverse@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.29.0", "@babel/template": "^7.28.6", "@babel/types": "^7.29.0", "debug": "^4.3.1" } }, "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA=="],
+ "@babel/plugin-transform-destructuring/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
"@babel/plugin-transform-destructuring/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
- "@babel/plugin-transform-modules-commonjs/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
+ "@babel/plugin-transform-flow-strip-types/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-transform-for-of/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
- "@babel/plugin-transform-nullish-coalescing-operator/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
+ "@babel/plugin-transform-logical-assignment-operators/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-transform-named-capturing-groups-regex/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
"@babel/plugin-transform-object-rest-spread/@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.2", "", { "dependencies": { "@babel/compat-data": "^7.27.2", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ=="],
+ "@babel/plugin-transform-object-rest-spread/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
"@babel/plugin-transform-object-rest-spread/@babel/traverse": ["@babel/traverse@7.28.5", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", "@babel/helper-globals": "^7.28.0", "@babel/parser": "^7.28.5", "@babel/template": "^7.27.2", "@babel/types": "^7.28.5", "debug": "^4.3.1" } }, "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ=="],
- "@babel/plugin-transform-optional-chaining/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
+ "@babel/plugin-transform-optional-catch-binding/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
"@babel/plugin-transform-optional-chaining/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ=="],
- "@babel/plugin-transform-react-jsx/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw=="],
+ "@babel/plugin-transform-parameters/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
- "@babel/plugin-transform-react-jsx/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
+ "@babel/plugin-transform-private-methods/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
- "@babel/plugin-transform-react-jsx-development/@babel/plugin-transform-react-jsx": ["@babel/plugin-transform-react-jsx@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/types": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw=="],
+ "@babel/plugin-transform-private-property-in-object/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
- "@babel/plugin-transform-react-jsx-self/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
+ "@babel/plugin-transform-react-display-name/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
- "@babel/plugin-transform-react-jsx-source/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
+ "@babel/plugin-transform-react-jsx/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw=="],
+
+ "@babel/plugin-transform-react-jsx-development/@babel/plugin-transform-react-jsx": ["@babel/plugin-transform-react-jsx@7.27.1", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.1", "@babel/helper-module-imports": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/types": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-2KH4LWGSrJIkVf5tSiBFYuXDAoWRq2MMwgivCf+93dd0GQi8RXLjKA/0EvRnVV5G0hrHczsquXuD01L8s6dmBw=="],
- "@babel/plugin-transform-regenerator/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
+ "@babel/plugin-transform-react-pure-annotations/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
"@babel/plugin-transform-runtime/@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
+ "@babel/plugin-transform-runtime/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-transform-shorthand-properties/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@babel/plugin-transform-template-literals/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
"@babel/plugin-transform-typescript/@babel/helper-annotate-as-pure": ["@babel/helper-annotate-as-pure@7.29.7", "", { "dependencies": { "@babel/types": "^7.29.7" } }, "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw=="],
"@babel/plugin-transform-typescript/@babel/helper-create-class-features-plugin": ["@babel/helper-create-class-features-plugin@7.29.7", "", { "dependencies": { "@babel/helper-annotate-as-pure": "^7.29.7", "@babel/helper-member-expression-to-functions": "^7.29.7", "@babel/helper-optimise-call-expression": "^7.29.7", "@babel/helper-replace-supers": "^7.29.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", "@babel/traverse": "^7.29.7", "semver": "^6.3.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg=="],
- "@babel/plugin-transform-typescript/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
-
"@babel/plugin-transform-typescript/@babel/helper-skip-transparent-expression-wrappers": ["@babel/helper-skip-transparent-expression-wrappers@7.29.7", "", { "dependencies": { "@babel/traverse": "^7.29.7", "@babel/types": "^7.29.7" } }, "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ=="],
- "@babel/preset-typescript/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.29.7", "", {}, "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw=="],
+ "@babel/plugin-transform-unicode-regex/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
"@babel/traverse/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
@@ -1986,8 +2045,6 @@
"@eslint/eslintrc/globals": ["globals@14.0.0", "", {}, "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ=="],
- "@eslint/eslintrc/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
-
"@expo/cli/arg": ["arg@5.0.2", "", {}, "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg=="],
"@expo/cli/glob": ["glob@13.0.0", "", { "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA=="],
@@ -2000,6 +2057,8 @@
"@expo/cli/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
+ "@expo/config/@expo/config-plugins": ["@expo/config-plugins@57.0.6", "", { "dependencies": { "@expo/config-types": "^57.0.2", "@expo/json-file": "~11.0.1", "@expo/plist": "^0.8.1", "@expo/require-utils": "^57.0.4", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", "debug": "^4.3.5", "getenv": "^2.0.0", "glob": "^13.0.0", "semver": "^7.5.4", "slugify": "^1.6.6", "xcode": "^3.0.1", "xml2js": "0.6.0" } }, "sha512-7CmKrS5Rnu8aSZyNlxH2qzA7Ls1HEa4EQvEVOAkHDKPr1e4Cg/nz7I7dUl09QDTVjMvkKYDH4Th0DuAsgqASaw=="],
+
"@expo/config/glob": ["glob@13.0.0", "", { "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA=="],
"@expo/config/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
@@ -2014,20 +2073,22 @@
"@expo/fingerprint/glob": ["glob@13.0.0", "", { "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA=="],
- "@expo/fingerprint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
-
"@expo/fingerprint/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
"@expo/fingerprint/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"@expo/image-utils/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
+ "@expo/inline-modules/@expo/config-plugins": ["@expo/config-plugins@57.0.6", "", { "dependencies": { "@expo/config-types": "^57.0.2", "@expo/json-file": "~11.0.1", "@expo/plist": "^0.8.1", "@expo/require-utils": "^57.0.4", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", "debug": "^4.3.5", "getenv": "^2.0.0", "glob": "^13.0.0", "semver": "^7.5.4", "slugify": "^1.6.6", "xcode": "^3.0.1", "xml2js": "0.6.0" } }, "sha512-7CmKrS5Rnu8aSZyNlxH2qzA7Ls1HEa4EQvEVOAkHDKPr1e4Cg/nz7I7dUl09QDTVjMvkKYDH4Th0DuAsgqASaw=="],
+
"@expo/metro-config/@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@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.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
"@expo/metro-config/glob": ["glob@13.0.0", "", { "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA=="],
"@expo/metro-runtime/pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="],
+ "@expo/prebuild-config/@expo/config-plugins": ["@expo/config-plugins@57.0.6", "", { "dependencies": { "@expo/config-types": "^57.0.2", "@expo/json-file": "~11.0.1", "@expo/plist": "^0.8.1", "@expo/require-utils": "^57.0.4", "@expo/sdk-runtime-versions": "^1.0.0", "chalk": "^4.1.2", "debug": "^4.3.5", "getenv": "^2.0.0", "glob": "^13.0.0", "semver": "^7.5.4", "slugify": "^1.6.6", "xcode": "^3.0.1", "xml2js": "0.6.0" } }, "sha512-7CmKrS5Rnu8aSZyNlxH2qzA7Ls1HEa4EQvEVOAkHDKPr1e4Cg/nz7I7dUl09QDTVjMvkKYDH4Th0DuAsgqASaw=="],
+
"@expo/prebuild-config/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"@expo/require-utils/@babel/core": ["@babel/core@7.29.0", "", { "dependencies": { "@babel/code-frame": "^7.29.0", "@babel/generator": "^7.29.0", "@babel/helper-compilation-targets": "^7.28.6", "@babel/helper-module-transforms": "^7.28.6", "@babel/helpers": "^7.28.6", "@babel/parser": "^7.29.0", "@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.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA=="],
@@ -2096,6 +2157,8 @@
"@types/react-test-renderer/@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
+ "@typescript-eslint/eslint-plugin/ignore": ["ignore@7.0.5", "", {}, "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg=="],
+
"@typescript-eslint/typescript-estree/minimatch": ["minimatch@10.2.4", "", { "dependencies": { "brace-expansion": "^5.0.2" } }, "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg=="],
"@typescript-eslint/typescript-estree/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
@@ -2108,6 +2171,8 @@
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
+ "babel-plugin-istanbul/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
"babel-plugin-jest-hoist/@babel/template": ["@babel/template@7.27.2", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.2", "@babel/types": "^7.27.1" } }, "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw=="],
"babel-plugin-jest-hoist/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
@@ -2116,6 +2181,8 @@
"babel-plugin-react-compiler/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
+ "babel-preset-expo/@babel/plugin-transform-export-namespace-from": ["@babel/plugin-transform-export-namespace-from@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ=="],
+
"chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"compressible/mime-db": ["mime-db@1.54.0", "", {}, "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ=="],
@@ -2132,8 +2199,6 @@
"escodegen/source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
- "eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
-
"eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
"eslint-import-resolver-typescript/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
@@ -2164,8 +2229,6 @@
"expo-updates/glob": ["glob@13.0.0", "", { "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA=="],
- "expo-updates/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
-
"fbjs/promise": ["promise@7.3.1", "", { "dependencies": { "asap": "~2.0.3" } }, "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg=="],
"finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="],
@@ -2566,6 +2629,8 @@
"@babel/plugin-transform-react-jsx-development/@babel/plugin-transform-react-jsx/@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
+ "@babel/plugin-transform-react-jsx-development/@babel/plugin-transform-react-jsx/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
"@babel/plugin-transform-react-jsx-development/@babel/plugin-transform-react-jsx/@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w=="],
"@babel/plugin-transform-react-jsx-development/@babel/plugin-transform-react-jsx/@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="],
@@ -2610,6 +2675,10 @@
"@expo/fingerprint/minimatch/brace-expansion": ["brace-expansion@5.0.5", "", { "dependencies": { "balanced-match": "^4.0.2" } }, "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ=="],
+ "@expo/inline-modules/@expo/config-plugins/glob": ["glob@13.0.0", "", { "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA=="],
+
+ "@expo/inline-modules/@expo/config-plugins/semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
+
"@expo/metro-config/@babel/core/@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
"@expo/metro-config/@babel/core/@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
@@ -2640,6 +2709,8 @@
"@expo/metro-runtime/pretty-format/react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
+ "@expo/prebuild-config/@expo/config-plugins/glob": ["glob@13.0.0", "", { "dependencies": { "minimatch": "^10.1.1", "minipass": "^7.1.2", "path-scurry": "^2.0.0" } }, "sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA=="],
+
"@expo/require-utils/@babel/core/@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
"@expo/require-utils/@babel/core/@babel/generator": ["@babel/generator@7.29.1", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" } }, "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw=="],
@@ -2720,8 +2791,18 @@
"@react-native/babel-preset/@babel/core/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
+ "@react-native/babel-preset/@babel/plugin-transform-class-properties/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
"@react-native/babel-preset/@babel/plugin-transform-modules-commonjs/@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.28.3", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.28.3" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw=="],
+ "@react-native/babel-preset/@babel/plugin-transform-modules-commonjs/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@react-native/babel-preset/@babel/plugin-transform-nullish-coalescing-operator/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@react-native/babel-preset/@babel/plugin-transform-optional-chaining/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "@react-native/babel-preset/@babel/plugin-transform-typescript/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
"@react-native/babel-preset/@babel/plugin-transform-typescript/@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ=="],
"@react-native/babel-preset/@react-native/babel-plugin-codegen/@react-native/codegen": ["@react-native/codegen@0.86.0", "", { "dependencies": { "@babel/core": "^7.25.2", "@babel/parser": "^7.29.0", "hermes-parser": "0.36.0", "invariant": "^2.2.4", "nullthrows": "^1.1.1", "tinyglobby": "^0.2.15", "yargs": "^17.6.2" } }, "sha512-uTs9DBo3+/lUqinsGZK0FKJRBVClrwMXoZToaDxE1Q2SL2e55vs2GwyZfIKzPl5uJnbu4PfFMIp0/mLXLWUMuA=="],
@@ -2800,6 +2881,8 @@
"babel-plugin-react-compiler/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
+ "babel-preset-expo/@babel/plugin-transform-export-namespace-from/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
"compression/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
"connect/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="],
@@ -2816,8 +2899,6 @@
"eslint-plugin-expo/eslint/debug": ["debug@4.4.1", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
- "eslint-plugin-expo/eslint/ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
-
"eslint-plugin-expo/eslint/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
"eslint-plugin-react-hooks/@babel/core/@babel/code-frame": ["@babel/code-frame@7.29.0", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw=="],
@@ -2948,6 +3029,10 @@
"jest-snapshot/@babel/generator/@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": { "parser": "bin/babel-parser.js" } }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="],
+ "jest-snapshot/@babel/plugin-syntax-jsx/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+ "jest-snapshot/@babel/plugin-syntax-typescript/@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
"jest-snapshot/@babel/types/@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
"jest-snapshot/@babel/types/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
@@ -3226,6 +3311,12 @@
"@expo/fingerprint/minimatch/brace-expansion/balanced-match": ["balanced-match@4.0.4", "", {}, "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA=="],
+ "@expo/inline-modules/@expo/config-plugins/glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
+
+ "@expo/inline-modules/@expo/config-plugins/glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
+
+ "@expo/inline-modules/@expo/config-plugins/glob/path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="],
+
"@expo/metro-config/@babel/core/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@expo/metro-config/@babel/core/@babel/helper-compilation-targets/@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
@@ -3246,6 +3337,12 @@
"@expo/metro-runtime/pretty-format/@jest/schemas/@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="],
+ "@expo/prebuild-config/@expo/config-plugins/glob/minimatch": ["minimatch@10.1.1", "", { "dependencies": { "@isaacs/brace-expansion": "^5.0.0" } }, "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ=="],
+
+ "@expo/prebuild-config/@expo/config-plugins/glob/minipass": ["minipass@7.1.2", "", {}, "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw=="],
+
+ "@expo/prebuild-config/@expo/config-plugins/glob/path-scurry": ["path-scurry@2.0.1", "", { "dependencies": { "lru-cache": "^11.0.0", "minipass": "^7.1.2" } }, "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA=="],
+
"@expo/require-utils/@babel/core/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@expo/require-utils/@babel/core/@babel/helper-compilation-targets/@babel/compat-data": ["@babel/compat-data@7.29.0", "", {}, "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg=="],
@@ -3558,6 +3655,10 @@
"@babel/plugin-transform-runtime/@babel/helper-module-imports/@babel/traverse/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
+ "@expo/inline-modules/@expo/config-plugins/glob/path-scurry/lru-cache": ["lru-cache@11.2.4", "", {}, "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg=="],
+
+ "@expo/prebuild-config/@expo/config-plugins/glob/path-scurry/lru-cache": ["lru-cache@11.2.4", "", {}, "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg=="],
+
"@istanbuljs/load-nyc-config/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
"@jest/reporters/istanbul-lib-instrument/@babel/core/@babel/code-frame/@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
diff --git a/docs/architecture.md b/docs/architecture.md
index 28e0031..ef1c6ba 100644
--- a/docs/architecture.md
+++ b/docs/architecture.md
@@ -109,8 +109,12 @@ A top-level error boundary (`src/components/ErrorBoundary.tsx`) wraps the naviga
3. Biometric login asks SecureStore for the stored biometric vault key with device authentication, verifies it against the same master-key verifier, and then loads it into memory.
4. `StorageService` initializes the encrypted vault cache with the active key.
5. `AuthContext` completes the shared post-login pipeline: authentication state, login notification, post-auth migration, notification settings, clipboard timeout, and auto-lock timeout.
-6. Password and note writes are serialized, encrypted with KS1, persisted to AsyncStorage, and only then committed to the decrypted in-memory cache.
-7. Logout and auto-lock clear authentication state and the in-memory key.
+6. PIN setup, PIN rotation, and transparent KDF upgrades treat ciphertext and verifier
+ metadata as one authenticated transaction. If rollback also fails, the session and
+ in-memory key are cleared instead of continuing against uncertain storage.
+7. Password and note writes are serialized, encrypted with KS1, persisted to AsyncStorage,
+ and only then committed to the decrypted in-memory cache.
+8. Logout and auto-lock clear authentication state and the in-memory key.
## Storage Boundaries
@@ -137,6 +141,8 @@ Exports are user-initiated. Encrypted exports use `KS1-PW1`, which wraps:
- Version metadata.
Imports are parsed as unknown data and validated before any object reaches `StorageService`.
+Validated password and note records are merged by ID, encrypted completely, and committed
+through one batched storage mutation so an import cannot persist only one collection.
Imported files and collection sizes are bounded before parsing and persistence. Export passwords and encrypted import payloads are cleared from hook state when their dialogs close, and temporary export files are removed after the platform share flow completes.
diff --git a/docs/assets/screenshots/add-password.png b/docs/assets/screenshots/add-password.png
new file mode 100644
index 0000000..d753190
Binary files /dev/null and b/docs/assets/screenshots/add-password.png differ
diff --git a/docs/assets/screenshots/encrypted-backup.png b/docs/assets/screenshots/encrypted-backup.png
new file mode 100644
index 0000000..62a036e
Binary files /dev/null and b/docs/assets/screenshots/encrypted-backup.png differ
diff --git a/docs/assets/screenshots/password-generator.png b/docs/assets/screenshots/password-generator.png
new file mode 100644
index 0000000..b313cab
Binary files /dev/null and b/docs/assets/screenshots/password-generator.png differ
diff --git a/docs/assets/screenshots/secure-notes.png b/docs/assets/screenshots/secure-notes.png
new file mode 100644
index 0000000..f19633b
Binary files /dev/null and b/docs/assets/screenshots/secure-notes.png differ
diff --git a/docs/development.md b/docs/development.md
index 5ba11cb..a872dd5 100644
--- a/docs/development.md
+++ b/docs/development.md
@@ -143,6 +143,10 @@ bun install --frozen-lockfile
bun run verify
```
+Dependabot uses the Bun ecosystem on a weekly schedule and must update both
+`package.json` and `bun.lock`. CI never relaxes the lockfile check for automation:
+all actors run `bun install --frozen-lockfile`.
+
Keep Expo-native packages on the versions selected by `expo install`. Major upgrades of Expo, React Native, React, Jest, ESLint, or TypeScript require a coordinated migration and must not be forced through transitive overrides. Remove a direct dependency only after confirming it has no source, configuration, script, or native-plugin consumer and completing the full verification plus Android export.
If `expo-doctor` reports duplicate native modules after a valid dependency update, regenerate `node_modules` and reinstall from the lockfile.
@@ -151,6 +155,11 @@ If `expo-doctor` reports duplicate native modules after a valid dependency updat
For significant behavior or release changes, update `README.md`, `CHANGELOG.md`, and the relevant file under `docs/`. Historical release notes under `docs/releases/` remain snapshots and should not be rewritten to describe later behavior.
+Each release adds a new `docs/releases/.md` snapshot and synchronizes
+`package.json`, `app.config.js`, `CITATION.cff`, `THIRD_PARTY_NOTICES.md`, the README current
+release section, and `docs/release.md`. Distinguish the source/GitHub release, EAS build,
+Google Play submission, and Play Store publication; none of those states implies the next.
+
When a change affects permissions, networking, updates, backups, biometrics, clipboard handling, profile images, key derivation, data retention, support contacts, or supported platforms, also review:
- the Italian and English privacy keys in `src/locales/`;
@@ -173,6 +182,12 @@ bun run build:android:production
The repository is also linked to EAS Build, and the EAS Workflow `.eas/workflows/build-android-production.yml` can build the Android production app-bundle from GitHub. It is triggered only by a version tag push (`v*`) or manual dispatch to limit build-credit usage; it does not run on every push. iOS remains available only through the explicitly approved cloud-simulator workflow; App Store publication is outside the project workflow.
+When publishing an approved release, push and verify `main` first, wait for the GitHub
+`Validate` and CodeQL checks, then create the version tag on that exact commit. The tag push
+already starts the EAS production workflow; do not also run `bun run
+build:android:production` unless the tag-triggered workflow failed to start and a deliberate
+replacement build is approved.
+
Expo Go cannot load custom native modules. Keysoft therefore uses the PBKDF2 KDF fallback in Expo Go and keeps Argon2 for EAS/native builds where `react-native-argon2` is available.
Use Expo Go for development and smoke testing only. Create release-test vaults in an EAS/native build when validating Argon2 behavior. Vault metadata that requires Argon2 (`memory > 0`) must fail with the native-KDF diagnostic if the native module is unavailable; do not reintroduce PBKDF2 fallback for that path.
diff --git a/docs/pre-build-security-ui-review.md b/docs/pre-build-security-ui-review.md
index 94ea959..a0166ac 100644
--- a/docs/pre-build-security-ui-review.md
+++ b/docs/pre-build-security-ui-review.md
@@ -1,9 +1,11 @@
# Pre-Build Security And UI Review
Original review date: 2026-06-17
-Latest update: 2026-08-02
+Latest update: 2026-08-09
-Published production release: Keysoft 3.3.0, Android versionCode 129, with iOS simulator readiness retained outside the App Store release scope.
+Current source release target: Keysoft 3.3.1, Android versionCode 130. Google Play remains on
+Keysoft 3.3.0, versionCode 129, until the new production bundle is submitted manually. iOS
+simulator readiness remains outside the App Store release scope.
## Executive Summary
@@ -11,6 +13,21 @@ The codebase passes local static and test verification, and the Android JS bundl
The release-blocking privacy/config mismatch and the main accessibility issues were accepted for remediation after this review. Keep this file as the audit trail for the pre-build review.
+### 2026-08-09 Release Review
+
+The 3.3.1 release packages the 2026-08-08 security/code audit and GitHub presentation work.
+Authentication setup, PIN rotation, KDF upgrades, encrypted writes, and batched imports now
+fail closed across persistence errors. Backup size/KDF/ciphertext bounds, active-key guards,
+generated permission blocks, frozen CI installs, and focused regressions remain in place.
+The release preserves KS1/KS1-PW1 compatibility, storage keys, default vault KDF parameters,
+permissions, icons, and the offline-only vault boundary.
+
+Public documentation now leads with the product promise, Google Play availability, security
+model, and production screenshots. The repository description, homepage, structured issue
+intake, and first-party social preview were verified live. Local verification passes 30 Jest
+suites / 212 tests, Expo Doctor 20/20, the critical dependency gate, Android bundle export,
+and generated-native R8, permission, and Argon2 keep-rule checks.
+
### 2026-08-02 Follow-Up Review
The repository was reviewed again after the 3.3.0 release without changing its design,
@@ -133,7 +150,7 @@ The 2.2 release is documentation- and content-only on top of 2.1: the settings s
- `bun run format:check`
- `bun run typecheck`
- `bun run lint`
-- `bun run test:ci`: 30 suites, 199 tests
+- `bun run test:ci`: 30 suites, 212 tests
- `bunx expo-doctor`: 20/20 checks
- `bun audit --audit-level=critical`
- `bunx expo export --platform android --output-dir C:\tmp\keysoft-android-export`
diff --git a/docs/publication.md b/docs/publication.md
index da9189c..e4a1917 100644
--- a/docs/publication.md
+++ b/docs/publication.md
@@ -21,6 +21,12 @@ publicly reviewable.
provenance and license terms.
- Review `README.md`, `LICENSE`, `COPYRIGHT.md`, `TRADEMARKS.md`,
`SECURITY.md`, and `CONTRIBUTING.md`.
+- Confirm the README leads with a clear product promise, a working store call to action,
+ current production screenshots, and links to privacy, security, and the latest release.
+- Keep exact test/dependency snapshots in dated audits or releases instead of duplicating
+ fast-stale counters in the product overview.
+- Configure a repository social preview using a first-party image of at least 640x320 and
+ point the repository homepage to the most useful current product destination.
- Run release verification:
```bash
bun run verify
@@ -69,6 +75,10 @@ Recommended settings:
- Disable force pushes on protected branches after any required history cleanup.
- Keep GitHub Actions permissions at read-only by default unless a workflow
requires write access.
+- Disable blank issues when structured bug, feature, and private vulnerability-reporting
+ paths are available.
+- Verify the repository description, homepage, topics, and social preview after any product
+ positioning or store-link change.
## Current Public Configuration
@@ -87,3 +97,8 @@ classic branch protection API. The repository is therefore configured with
administrator bypass disabled for non-admin collaborators by permission model:
`TheStreamCode` is the only collaborator with push/admin access, and all other
contributors must use pull requests.
+
+The repository description states the offline-first privacy promise, the homepage points to
+Google Play, structured issue templates are enabled with blank issues disabled, and the
+first-party Keysoft banner is configured as the social preview. Recheck these live settings
+before each public launch announcement.
diff --git a/docs/release.md b/docs/release.md
index 6b1522e..94e4a2f 100644
--- a/docs/release.md
+++ b/docs/release.md
@@ -2,7 +2,11 @@
## Release Readiness Checklist
-Current production release: Keysoft 3.3.0, Android versionCode 129, published on Google Play. EAS remote versioning remains authoritative for future Android builds. iOS remains a cloud-simulator compatibility target and is not an App Store release target.
+Current source release and Android build target: Keysoft 3.3.1, versionCode 130. Google Play
+currently distributes Keysoft 3.3.0, versionCode 129, until the new app bundle completes
+review and is submitted manually. EAS remote versioning remains authoritative for Android
+builds. iOS remains a cloud-simulator compatibility target and is not an App Store release
+target.
Before preparing a release:
@@ -37,9 +41,15 @@ Application version data is maintained in:
- `app.config.js`
- Android native configuration where applicable
-For the current 3.3.0 production release, `app.config.js` uses `version: "3.3.0"`, `android.versionCode: 129`, and an iOS simulator baseline of `ios.buildNumber: "1"`. The published Android release uses build 129. The local value mirrors the release for manifest visibility but does not control future EAS production builds. Because EAS Update uses the `appVersion` runtime policy, this release uses native runtime 3.3.0.
+For the 3.3.1 source release, `app.config.js` uses `version: "3.3.1"`,
+`android.versionCode: 130`, and an iOS simulator baseline of `ios.buildNumber: "1"`. Before
+the build, the EAS remote Android value is 129; the production profile auto-increments it to 130. The local value mirrors the target for manifest visibility but does not control the EAS
+counter. Because EAS Update uses the `appVersion` runtime policy, this release uses native
+runtime 3.3.1. Google Play remains on 3.3.0/129 until manual submission and publication.
-Read the current remote value with `bunx eas-cli build:version:get --platform android` instead of assuming the baseline.
+Read the current remote value with
+`bunx eas-cli build:version:get --platform android --profile production --non-interactive`
+instead of assuming the baseline.
When changing Android permissions or update behavior, keep `app.config.js`, EAS profiles, and generated native configuration in sync.
@@ -68,7 +78,10 @@ These commands upload the project to expo.dev. Do not start them until the relea
### Build from GitHub
-The Android workflow runs for a matching version tag or manual dispatch. It uploads the project to Expo and therefore requires explicit approval before it is run.
+The Android workflow runs for a matching version tag or manual dispatch. It uploads the
+project to Expo and therefore requires explicit approval before it is run. A release tag
+already starts the production build; do not launch the local production-build command as
+well unless intentionally replacing a missing or failed workflow run.
## iOS Cloud Simulator Testing
@@ -130,6 +143,7 @@ Blocked:
- `READ_MEDIA_IMAGES`
- `READ_MEDIA_VIDEO`
- `READ_MEDIA_AUDIO`
+- `SYSTEM_ALERT_WINDOW`
Prefer Android Photo Picker or explicit user-selected file access instead of broad media permissions.
@@ -142,6 +156,8 @@ Encrypted backups use `KS1-PW1`. Release testing should cover:
- Reject encrypted backup with the wrong password.
- Reject malformed JSON backup.
- Reject backup objects with invalid password or note shape.
+- Reject backup files whose size is missing or exceeds 10 MiB before reading them.
+- Verify that a failed batched import leaves both password and note collections unchanged.
## Security Regression Checks
@@ -151,6 +167,8 @@ Before release, confirm these invariants:
- Argon2 vault metadata (`memory > 0`) fails with an explicit native-KDF error if the native module is unavailable.
- Argon2 timeout surfaces as a KDF timeout diagnostic, not as a generic invalid PIN.
- PIN setup and PIN change reuse the key derived while creating the master-key verifier instead of running an extra KDF pass for the same new PIN.
+- PIN setup refuses to overwrite an existing verifier and removes a newly created verifier if initial database setup fails.
+- A committed PIN change remains committed if biometric refresh or cleanup fails; failures before verifier persistence roll encrypted data back to the previous key.
- No `Math.random` usage exists under `src`.
- Backup export does not call `Crypto.encrypt(jsonData, exportPassword)`.
- Backup import does not decrypt with `Crypto.decrypt(parsedData.data, importPassword)`.
diff --git a/docs/releases/3.3.1.md b/docs/releases/3.3.1.md
new file mode 100644
index 0000000..ffac9ff
--- /dev/null
+++ b/docs/releases/3.3.1.md
@@ -0,0 +1,59 @@
+# Keysoft 3.3.1
+
+Source release: 2026-08-09.
+
+Android production target: versionCode 130.
+Google Play status at release creation: Keysoft 3.3.0 / versionCode 129 remains published;
+3.3.1 submission is a separate manual step.
+
+Keysoft 3.3.1 is a security, reliability, dependency, and repository-quality release. It
+does not change the visible application design, canonical assets, Android permissions,
+offline-first behavior, storage keys, or KS1/KS1-PW1 payload layouts.
+
+## Security And Reliability
+
+- Makes first-time PIN setup, PIN rotation, and transparent KDF upgrades transactional.
+- Closes authentication and clears the active key when verifier/ciphertext rollback cannot
+ restore a known-good storage state.
+- Persists encrypted mutations before updating decrypted caches.
+- Validates backup size, KDF costs, field/collection limits, and KS1 CBC framing before
+ persistence.
+- Merges imported records by ID and commits password/note changes through one encrypted
+ batch, preventing partial imports and repeated quadratic writes.
+- Requires an active vault key for secure collection clears and blocks
+ `SYSTEM_ALERT_WINDOW` in generated Android manifests.
+- Keeps debug logging message-only and avoids structured vault or preference data.
+
+## Fixed
+
+- Applies excluded-similar-character filtering to every password-generator character set,
+ including the character guaranteed from each enabled class.
+- Keeps generated passwords at the requested length even for direct API calls shorter than
+ the number of enabled classes.
+- Uses canonical-alphabet membership in the development web crypto mock's special-character
+ regression coverage.
+
+## Tooling And Repository
+
+- Aligns Expo SDK 57 and compatible JavaScript patch releases while preserving the supported
+ React Native 0.86 toolchain.
+- Adds weekly Bun Dependabot updates and frozen lockfile installation to every CI path.
+- Publishes a product-first README, official production screenshots, a Google Play homepage,
+ structured issue intake, a first-party social preview, and a dated full audit.
+
+## Verification
+
+- `bun install --frozen-lockfile`
+- `bun run verify`: formatting, strict TypeScript, ESLint, 30 suites / 212 tests, and Expo
+ Doctor 20/20
+- `bun audit --audit-level=critical`: zero critical advisories
+- Android Expo bundle export
+- Generated Android inspection for permissions, optimized R8/resource shrinking, and Argon2
+ keep rules
+- GitHub `Validate` and CodeQL checks
+
+## Deployment Boundary
+
+The `v3.3.1` tag identifies the exact source used to start the EAS Android production build.
+The EAS profile produces an Android App Bundle and auto-increments the remote version from
+129 to 130. It does not submit to Google Play; store review and publication remain manual.
diff --git a/docs/security-audit-2026-08-08.md b/docs/security-audit-2026-08-08.md
new file mode 100644
index 0000000..87417b5
--- /dev/null
+++ b/docs/security-audit-2026-08-08.md
@@ -0,0 +1,217 @@
+# Security and Code Audit — 2026-08-08
+
+## Executive Summary
+
+The repository was reviewed across application code, cryptography and storage boundaries,
+tests, dependencies, Expo/Android configuration, CI, GitHub security signals,
+documentation, and release readiness. Nine actionable findings were remediated without
+changing the KS1/KS1-PW1 formats, storage keys, existing-vault KDF parameters, UI, icons,
+or visual assets.
+
+No unresolved critical vulnerability was found in application code or the dependency
+audit. The remaining package advisories are transitive findings in the Expo, React Native,
+Jest, Babel, and ESLint toolchain and are tracked as accepted release debt until compatible
+upstream versions are available. The documented KS1 key-sharing and KS1-PW1 PBKDF2-SHA1
+trade-offs remain intentionally unchanged because an in-place change would make existing
+vaults or backups unreadable.
+
+## Scope and Method
+
+The audit covered:
+
+- authentication, KDF migration, biometric key lifecycle, encryption, storage, import and
+ export, clipboard/logging boundaries, and untrusted-input validation;
+- React Native hooks, contexts, screens, service mocks, and test isolation;
+- package manifests and lockfile, Expo Doctor, dependency advisories, and Android export;
+- generated Android permissions, release minification/resource shrinking, and Argon2 keep
+ rules;
+- GitHub Actions, Dependabot, branch-protection evidence, CodeQL alerts, documentation, and
+ release checklists.
+
+Static searches also checked for committed secrets, unsafe randomness, direct secret
+clipboard paths, raw console logging, dynamic code execution, and broad AsyncStorage
+clears. No high-confidence secret or dynamic-code-execution finding was identified.
+
+## Remediated Findings
+
+### KSA-001 — High — PIN/KDF transaction could split ciphertext and verifier state
+
+**Location:** `src/services/auth/authService.ts`
+
+After encrypted vault data and the new verifier were persisted, a later preference or
+biometric failure could enter the general rollback path. That rollback restored ciphertext
+to the old key without restoring the already persisted verifier, risking a vault that no
+PIN could open.
+
+**Resolution:** preference state is read before vault mutation. Ciphertext re-encryption
+and verifier persistence now form the core transaction; in-memory authentication state is
+committed immediately afterward. Biometric refresh and cleanup are isolated best-effort
+work and cannot initiate a vault rollback. Pre-commit verifier failures still roll back
+encrypted data, and a failed rollback clears authentication and the in-memory key.
+
+### KSA-002 — Medium — Backup import allowed partial persistence and quadratic work
+
+**Location:** `src/hooks/settings/useExportImport.ts`,
+`src/services/storage/storageService.ts`
+
+The import hook previously saved every password and note separately. A failure midway
+left a partial import, and repeatedly encrypting a growing collection made large valid
+backups unnecessarily expensive.
+
+**Resolution:** validated records are merged by ID, limits are evaluated against the final
+password set, both collections are encrypted before persistence, and one `multiSet` batch
+is issued. Caches update only after that batch succeeds. The development web mock implements
+the same merge and batch contract.
+
+### KSA-003 — Medium — Unknown backup size bypassed the pre-read bound
+
+**Location:** `src/services/import-export/backupValidation.ts`,
+`src/hooks/settings/useExportImport.ts`
+
+An absent picker size was previously accepted, allowing the complete selected file to be
+read before the 10 MiB content check.
+
+**Resolution:** native imports recover size from file metadata when necessary; unknown,
+unsafe, negative, non-integer, or oversized values are rejected before file contents are
+loaded.
+
+### KSA-004 — Low — KS1 framing accepted impossible CBC payload lengths
+
+**Location:** `src/services/crypto/cryptoService.ts`
+
+The previous word-count check allowed a payload containing only IV and MAC, or ciphertext
+whose byte length was not aligned to an AES block.
+
+**Resolution:** KS1 decryption now requires IV + at least one 16-byte ciphertext block +
+32-byte MAC and requires the ciphertext length to be divisible by 16 before MAC or AES
+processing.
+
+### KSA-005 — Medium — Collection clears did not require an authenticated vault key
+
+**Location:** `src/services/storage/storageService.ts`
+
+Individual password/note clear methods checked decryption errors but not whether the vault
+key was active.
+
+**Resolution:** both methods now use the same active-key/decryption-safety guard as other
+secure mutations. The deliberate full-reset flow remains separately available and removes
+only Keysoft-owned keys.
+
+### KSA-006 — Medium — CI weakened lockfile enforcement for dependency automation
+
+**Location:** `.github/workflows/ci.yml`, `.github/dependabot.yml`
+
+CI used a non-frozen install for Dependabot, so dependency PR validation could test a
+resolver-generated graph that was not represented by the committed lockfile.
+
+**Resolution:** every CI actor now uses `bun install --frozen-lockfile`, and weekly Bun
+Dependabot updates are configured to maintain the Bun manifest/lockfile pair.
+
+### KSA-007 — Low — SDK patch drift and duplicate ESLint plugin registration
+
+**Location:** `package.json`, `bun.lock`, `eslint.config.js`
+
+Nine Expo packages were behind the versions selected for SDK 57. A separately installed
+TypeScript ESLint plugin duplicated the plugin already registered by the Expo flat preset
+after compatible updates.
+
+**Resolution:** Expo-aligned patches and compatible navigation/tooling patches were
+installed, the redundant plugin dependency/configuration was removed, and the frozen
+install plus Expo Doctor checks pass.
+
+### KSA-008 — Low — Unneeded overlay permission appeared in generated Android metadata
+
+**Location:** `app.config.js`
+
+The generated manifest included `SYSTEM_ALERT_WINDOW`, although Keysoft has no overlay
+feature.
+
+**Resolution:** the permission is explicitly blocked. A fresh prebuild emits a
+`tools:node="remove"` directive alongside the existing blocked media/audio permissions.
+
+### KSA-009 — Medium — Failed first-time setup left partial authentication state
+
+**Location:** `src/services/auth/authService.ts`,
+`src/services/storage/storageService.ts`
+
+If initial database setup failed after the new verifier had been saved, setup returned
+failure while the vault key remained in memory and the partial verifier remained
+configured. A repeated setup call could also replace an existing verifier.
+
+**Resolution:** setup now refuses to overwrite existing verifier metadata. If failure
+occurs after saving a genuinely new verifier, it removes that verifier, clears the
+in-memory vault key and authentication state, and reports failure. SecureStore deletion
+still follows the persist-before-cache rule.
+
+## Accepted and Monitored Risk
+
+### Dependency advisories
+
+`bun audit` reports 58 transitive advisories: 42 high, 14 moderate, and 2 low; none are
+critical, so `bun audit --audit-level=critical` passes. Affected transitive packages include
+`ajv`, `@xmldom/xmldom`, `brace-expansion`, `lodash`, `uuid`, `minimatch`, `form-data`,
+`yaml`, `flatted`, `js-yaml`, `nanoid`, `node-forge`, `postcss`, `@babel/core`, `ws`,
+`image-size`, and `picomatch`.
+
+Most paths are build, lint, test, Metro, code-generation, or Expo CLI dependencies rather
+than code shipped as a direct Keysoft runtime API. Forcing incompatible major overrides
+would conflict with the supported Expo SDK 57 graph. The release gate therefore blocks
+critical advisories and requires human review of lower severities on each update.
+
+### CodeQL alerts
+
+Three open `js/insufficient-password-hash` alerts were reviewed:
+
+- `cryptoServiceMock.ts`: SHA-256 receives an already PBKDF2-derived key to create a
+ compatibility verifier; it does not hash a raw password.
+- `cryptoService.ts`: AES encrypts password-manager records with a KDF-derived vault key;
+ this is data encryption, not password hashing.
+- `CryptoService.test.ts`: the test invokes the production verifier creation path and
+ contains no independent password-storage implementation.
+
+These are scanner classification false positives. They were not remotely dismissed as
+part of this local implementation.
+
+### Format compatibility and device assumptions
+
+The accepted limitations in `docs/security.md` remain in force: one key is shared between
+KS1 AES and HMAC operations, KS1-PW1 uses PBKDF2-SHA1, the current master secret is six
+digits, unlock has no attempt throttling, screenshot protection is opt-in, auto-lock is
+transition-based, Android clipboard previews cannot be fully suppressed, and the OTA
+update channel is trusted. Format changes require a new version and authenticated
+migration.
+
+## Verification Evidence
+
+| Check | Result |
+| ------------------------------ | --------------------------------------------------------------------------------------------- |
+| Frozen Bun install | Passed; no lockfile changes |
+| Formatting, TypeScript, ESLint | Passed |
+| Jest with coverage | 30 suites, 212 tests passed |
+| Expo Doctor | 20/20 checks passed |
+| Critical dependency audit | Passed; zero critical advisories |
+| Full dependency audit | 58 transitive advisories: 42 high, 14 moderate, 2 low |
+| Android JS bundle export | Passed; 1,652 modules bundled |
+| Generated Android permissions | Expected permissions present; blocked media/audio/overlay directives present |
+| Release shrinking | `minifyEnabled`, `shrinkResources`, `proguard-android-optimize.txt`, and optimized R8 present |
+| Argon2 native rules | `com.poowf.argon2` and `com.reactnativeargon2` keep rules present |
+
+The initial baseline run had one isolated timeout in the authentication integration suite;
+the focused suite passed immediately afterward and the final full coverage run passed all
+212 tests. No timeout suppression or increased global timeout was introduced.
+
+## Release Boundary
+
+This audit performed local source changes, dependency resolution, tests, an Android bundle
+export, and a temporary native prebuild. It did not start an EAS build, submit to Google
+Play, create a tag or release, push commits, change secrets, dismiss GitHub alerts, or
+deploy anything.
+
+## Post-Audit Publication Follow-Up — 2026-08-09
+
+The audited changes were subsequently published for review in GitHub PR #35 with green CI
+and CodeQL analysis. The existing `js/insufficient-password-hash` alert on the KS1 encryption
+call was dismissed as a false positive with a repository-visible technical justification:
+the operation reversibly encrypts vault records under an already KDF-derived key and is not
+password-authentication hashing. Keysoft 3.3.1 packages the audited changes without altering
+the KS1/KS1-PW1 formats or accepted compatibility boundaries.
diff --git a/docs/security.md b/docs/security.md
index 9c04c0f..dd3e480 100644
--- a/docs/security.md
+++ b/docs/security.md
@@ -106,7 +106,7 @@ so this is the weakest link in the current design.
The KDF identifier is part of the `KS1-PW1` payload, so a stronger KDF can only be
introduced as a new payload version that still reads `KS1-PW1` files.
-Backup imports are limited to 10 MiB and validate collection sizes and field lengths before any object reaches storage. Export passwords and imported ciphertext are removed from UI state on close, while temporary export files are deleted after sharing completes.
+Backup imports are limited to 10 MiB and validate collection sizes and field lengths before any object reaches storage. An import whose size cannot be established is rejected before the file is read. Valid records are merged by ID, encrypted in memory, and persisted in one batched storage operation; decrypted caches change only after persistence succeeds. Export passwords and imported ciphertext are removed from UI state on close, while temporary export files are deleted after sharing completes.
## Biometric Authentication
@@ -117,7 +117,7 @@ Expected behavior:
- The stored biometric key uses `requireAuthentication: true` and `WHEN_PASSCODE_SET_THIS_DEVICE_ONLY`.
- Biometric login reads that SecureStore item, verifies it against the master-key verifier, loads vault metadata, initializes storage, and authenticates the session.
- If the SecureStore item is missing, invalidated by biometric enrollment changes, or no longer verifies, biometrics are disabled and the user must log in with PIN and re-enable biometrics.
-- When the PIN changes, the biometric key is updated to the new vault key. If that update fails, biometrics are disabled and the stale key is deleted.
+- When the PIN changes, ciphertext and verifier metadata commit before biometric maintenance. The biometric key is then updated to the new vault key. If that update fails, biometrics are disabled and the stale key is deleted on a best-effort basis; biometric cleanup can never roll back an already committed PIN change.
## Storage Rules
@@ -126,7 +126,12 @@ Expected behavior:
- Legacy plaintext arrays are immediately re-encrypted when loaded with an active encryption key.
- Writes are blocked after decryption errors until the unsafe state is resolved.
- Mutations persist encrypted data before changing the decrypted cache, so a failed write cannot expose an uncommitted value through later reads.
+- Backup imports prepare every changed encrypted collection before one batched write and update neither cache if that write fails.
+- Imported records are merged by ID and checked against post-merge collection limits before
+ encryption, preventing both partial persistence and repeated quadratic writes.
+- Clearing a password or note collection requires an active vault key and is blocked after a decryption error.
- Master-key verifier metadata is written to SecureStore before its in-memory cache is updated. A failed SecureStore write leaves the previously persisted and cached verifier unchanged.
+- Initial PIN setup refuses to replace existing verifier metadata. If first-time database initialization fails after saving a new verifier, that verifier is removed and authentication/key state is cleared before setup reports failure.
- Reset removes only Keysoft-owned AsyncStorage keys; it must not call the process-wide `AsyncStorage.clear()` API.
## Randomness
@@ -210,3 +215,7 @@ bunx expo export --platform android --output-dir C:\tmp\keysoft-android-export
```
Security-sensitive changes should add or update tests in `src/__tests__/services`.
+
+The latest repository-wide review is recorded in
+[`security-audit-2026-08-08.md`](security-audit-2026-08-08.md).
+Its release follow-up is documented in the Keysoft 3.3.1 release notes.
diff --git a/eslint.config.js b/eslint.config.js
index e604ce7..657c8d3 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -1,7 +1,6 @@
// ESLint 9 flat config. Mirrors the previous .eslintrc.js: the Expo preset plus
// the project's unused-vars rule, with Jest globals scoped to test files.
const expoFlat = require('eslint-config-expo/flat');
-const typescriptEslint = require('@typescript-eslint/eslint-plugin');
const globals = require('globals');
module.exports = [
@@ -30,9 +29,6 @@ module.exports = [
},
{
files: ['**/*.ts', '**/*.tsx', '**/*.d.ts'],
- plugins: {
- '@typescript-eslint': typescriptEslint,
- },
rules: {
'@typescript-eslint/no-unused-vars': [
'warn',
diff --git a/package.json b/package.json
index 0059f61..cc5e43c 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "keysoft",
"author": "Michael Gasperini (Mikesoft)",
- "version": "3.3.0",
+ "version": "3.3.1",
"packageManager": "bun@1.3.14",
"repository": {
"type": "git",
@@ -36,31 +36,31 @@
"@expo/vector-icons": "^15.1.1",
"@react-native-async-storage/async-storage": "^2.2.0",
"@react-native-community/slider": "5.2.0",
- "@react-navigation/bottom-tabs": "^7.18.14",
- "@react-navigation/native": "^7.3.14",
- "@react-navigation/native-stack": "^7.18.6",
- "@react-navigation/stack": "^7.10.17",
+ "@react-navigation/bottom-tabs": "^7.18.16",
+ "@react-navigation/native": "^7.3.16",
+ "@react-navigation/native-stack": "^7.18.8",
+ "@react-navigation/stack": "^7.10.22",
"crypto-js": "^4.2.0",
- "expo": "~57.0.9",
- "expo-asset": "~57.0.8",
- "expo-build-properties": "~57.0.8",
+ "expo": "~57.0.11",
+ "expo-asset": "~57.0.9",
+ "expo-build-properties": "~57.0.9",
"expo-clipboard": "~57.0.1",
- "expo-constants": "~57.0.8",
+ "expo-constants": "~57.0.9",
"expo-crypto": "~57.0.1",
"expo-document-picker": "~57.0.1",
- "expo-file-system": "~57.0.1",
+ "expo-file-system": "~57.0.2",
"expo-font": "~57.0.1",
- "expo-image-picker": "~57.0.7",
+ "expo-image-picker": "~57.0.8",
"expo-local-authentication": "~57.0.2",
"expo-localization": "~57.0.1",
"expo-navigation-bar": "~57.0.2",
- "expo-notifications": "~57.0.8",
+ "expo-notifications": "~57.0.9",
"expo-screen-capture": "~57.0.1",
"expo-secure-store": "~57.0.1",
- "expo-sharing": "~57.0.8",
+ "expo-sharing": "~57.0.10",
"expo-status-bar": "~57.0.1",
"expo-system-ui": "~57.0.2",
- "expo-updates": "~57.0.11",
+ "expo-updates": "~57.0.12",
"react": "19.2.3",
"react-dom": "19.2.3",
"react-native": "0.86.2",
@@ -77,14 +77,13 @@
},
"devDependencies": {
"@babel/core": "^7.29.7",
- "@babel/plugin-transform-export-namespace-from": "^7.27.1",
+ "@babel/plugin-transform-export-namespace-from": "^7.29.7",
"@react-native/jest-preset": "0.86.2",
"@testing-library/react-native": "^13.3.3",
- "@typescript-eslint/eslint-plugin": "^8.63.0",
"@types/jest": "29.5.14",
"@types/react": "~19.2.18",
"babel-plugin-module-resolver": "^5.0.3",
- "babel-preset-expo": "~57.0.5",
+ "babel-preset-expo": "~57.0.6",
"eslint": "^9.39.5",
"eslint-config-expo": "~57.0.1",
"globals": "^16.5.0",
diff --git a/src/__tests__/services/AuthService.test.ts b/src/__tests__/services/AuthService.test.ts
index 0478cf8..9421c43 100644
--- a/src/__tests__/services/AuthService.test.ts
+++ b/src/__tests__/services/AuthService.test.ts
@@ -59,12 +59,18 @@ describe('AuthService', () => {
(CryptoService.isLegacyKdf as jest.Mock).mockReturnValue(false);
(StorageService.saveMasterKeyInfo as jest.Mock).mockResolvedValue(undefined);
+ (StorageService.deleteMasterKeyInfo as jest.Mock).mockResolvedValue(undefined);
(StorageService.setEncryptionKey as jest.Mock).mockImplementation(() => {});
(StorageService.initDatabase as jest.Mock).mockResolvedValue(undefined);
(StorageService.getMasterKeyInfo as jest.Mock).mockResolvedValue(mockMasterKeyInfo);
(StorageService.getEncryptionKey as jest.Mock).mockReturnValue(null); // Default: no key
(StorageService.getBiometricKey as jest.Mock).mockResolvedValue(mockEncryptionKey);
(StorageService.saveBiometricKey as jest.Mock).mockResolvedValue(undefined);
+ (StorageService.deleteBiometricKey as jest.Mock).mockResolvedValue(undefined);
+ (StorageService.getUserPreferences as jest.Mock).mockResolvedValue({
+ biometricsEnabled: false,
+ });
+ (StorageService.saveUserPreferences as jest.Mock).mockResolvedValue(undefined);
// Biometrics default: available and enrolled
(LocalAuthentication.hasHardwareAsync as jest.Mock).mockResolvedValue(true);
@@ -77,6 +83,7 @@ describe('AuthService', () => {
describe('Master Password Setup & Login', () => {
it('should setup master password correctly', async () => {
+ (StorageService.getMasterKeyInfo as jest.Mock).mockResolvedValue(null);
const result = await AuthService.setupMasterPassword(mockMasterPassword);
expect(result).toBe(true);
@@ -89,6 +96,35 @@ describe('AuthService', () => {
expect(AuthService.getIsAuthenticated()).toBe(true);
});
+ it('does not overwrite an existing master password configuration', async () => {
+ (StorageService.getMasterKeyInfo as jest.Mock).mockResolvedValue(mockMasterKeyInfo);
+
+ const result = await AuthService.setupMasterPassword(mockMasterPassword);
+
+ expect(result).toBe(false);
+ expect(CryptoService.createMasterKeyInfoWithDerivedKey).not.toHaveBeenCalled();
+ expect(StorageService.saveMasterKeyInfo).not.toHaveBeenCalled();
+ expect(AuthService.getLastAuthFailure()).toEqual(
+ expect.objectContaining({ reason: 'master_key_already_configured' }),
+ );
+ });
+
+ it('rolls back a new verifier and clears the key when initial database setup fails', async () => {
+ (StorageService.getMasterKeyInfo as jest.Mock).mockResolvedValue(null);
+ (StorageService.initDatabase as jest.Mock).mockRejectedValue(
+ new Error('storage unavailable'),
+ );
+
+ const result = await AuthService.setupMasterPassword(mockMasterPassword);
+
+ expect(result).toBe(false);
+ expect(StorageService.saveMasterKeyInfo).toHaveBeenCalledWith(mockMasterKeyInfo);
+ expect(StorageService.deleteMasterKeyInfo).toHaveBeenCalled();
+ expect(StorageService.setEncryptionKey).toHaveBeenLastCalledWith('');
+ expect(AuthService.getIsAuthenticated()).toBe(false);
+ expect(AuthService.getMasterKeyInfo()).toBeNull();
+ });
+
it('should login with correct master password', async () => {
const result = await AuthService.loginWithMasterPassword(mockMasterPassword);
@@ -221,6 +257,36 @@ describe('AuthService', () => {
expect.objectContaining({ reason: 'kdf_upgrade_rollback_failed' }),
);
});
+
+ it('keeps a committed KDF upgrade when biometric refresh and cleanup fail', async () => {
+ (StorageService.getMasterKeyInfo as jest.Mock).mockResolvedValue(mockMasterKeyInfo);
+ (CryptoService.isNativeKdfAvailable as jest.Mock).mockReturnValue(true);
+ (CryptoService.isLegacyKdf as jest.Mock).mockReturnValue(true);
+ (StorageService.getEncryptionKey as jest.Mock).mockReturnValue(mockEncryptionKey);
+ (CryptoService.createMasterKeyInfoWithDerivedKey as jest.Mock).mockResolvedValue({
+ masterKeyInfo: newInfo,
+ derivedKey: newKey,
+ });
+ (StorageService.getUserPreferences as jest.Mock).mockResolvedValue({
+ biometricsEnabled: true,
+ });
+ (StorageService.reEncryptAllData as jest.Mock).mockResolvedValue(undefined);
+ (StorageService.saveBiometricKey as jest.Mock).mockRejectedValue(
+ new Error('biometric write failed'),
+ );
+ (StorageService.deleteBiometricKey as jest.Mock).mockRejectedValue(
+ new Error('biometric cleanup failed'),
+ );
+
+ const result = await AuthService.loginWithMasterPassword(mockMasterPassword);
+
+ expect(result).toBe(true);
+ expect(StorageService.reEncryptAllData).toHaveBeenCalledTimes(1);
+ expect(StorageService.saveMasterKeyInfo).toHaveBeenCalledWith(newInfo);
+ expect(StorageService.setEncryptionKey).toHaveBeenLastCalledWith(newKey);
+ expect(AuthService.getMasterKeyInfo()).toEqual(newInfo);
+ expect(AuthService.getIsAuthenticated()).toBe(true);
+ });
});
describe('Biometrics', () => {
@@ -413,6 +479,50 @@ describe('AuthService', () => {
);
});
+ it('does not mutate encrypted storage when preferences cannot be read', async () => {
+ await AuthService.loginWithMasterPassword(mockMasterPassword);
+ (StorageService.getEncryptionKey as jest.Mock).mockReturnValue(mockEncryptionKey);
+ (StorageService.getUserPreferences as jest.Mock).mockRejectedValue(
+ new Error('preferences unavailable'),
+ );
+
+ const result = await AuthService.updateMasterPassword('new-password');
+
+ expect(result).toBe(false);
+ expect(StorageService.reEncryptAllData).not.toHaveBeenCalled();
+ expect(StorageService.saveMasterKeyInfo).not.toHaveBeenCalled();
+ });
+
+ it('keeps the committed password change when biometric cleanup also fails', async () => {
+ const newKey = '1112131415161718191a1b1c1d1e1f201112131415161718191a1b1c1d1e1f20';
+ const newInfo = { ...mockMasterKeyInfo, verifier: 'new-verifier' };
+
+ await AuthService.loginWithMasterPassword(mockMasterPassword);
+ (StorageService.getEncryptionKey as jest.Mock).mockReturnValue(mockEncryptionKey);
+ (StorageService.getUserPreferences as jest.Mock).mockResolvedValue({
+ biometricsEnabled: true,
+ });
+ (CryptoService.createMasterKeyInfoWithDerivedKey as jest.Mock).mockResolvedValue({
+ masterKeyInfo: newInfo,
+ derivedKey: newKey,
+ });
+ (StorageService.saveBiometricKey as jest.Mock).mockRejectedValue(
+ new Error('biometric write failed'),
+ );
+ (StorageService.deleteBiometricKey as jest.Mock).mockRejectedValue(
+ new Error('biometric cleanup failed'),
+ );
+
+ const result = await AuthService.updateMasterPassword('new-password');
+
+ expect(result).toBe(true);
+ expect(StorageService.reEncryptAllData).toHaveBeenCalledTimes(1);
+ expect(StorageService.reEncryptAllData).toHaveBeenCalledWith(newKey);
+ expect(StorageService.saveMasterKeyInfo).toHaveBeenCalledWith(newInfo);
+ expect(StorageService.setEncryptionKey).toHaveBeenLastCalledWith(newKey);
+ expect(AuthService.getMasterKeyInfo()).toEqual(newInfo);
+ });
+
it('should rollback re-encryption when saveMasterKeyInfo fails', async () => {
const newPassword = 'new-password';
const newKey = 'new-derived-key';
diff --git a/src/__tests__/services/BackupValidation.test.ts b/src/__tests__/services/BackupValidation.test.ts
index 0e1591b..4700890 100644
--- a/src/__tests__/services/BackupValidation.test.ts
+++ b/src/__tests__/services/BackupValidation.test.ts
@@ -83,8 +83,9 @@ describe('Backup import validation', () => {
});
it('bounds backup file sizes', () => {
- expect(isBackupFileSizeAllowed(undefined)).toBe(true);
+ expect(isBackupFileSizeAllowed(undefined)).toBe(false);
expect(isBackupFileSizeAllowed(MAX_BACKUP_FILE_SIZE_BYTES)).toBe(true);
expect(isBackupFileSizeAllowed(MAX_BACKUP_FILE_SIZE_BYTES + 1)).toBe(false);
+ expect(isBackupFileSizeAllowed(Number.NaN)).toBe(false);
});
});
diff --git a/src/__tests__/services/CryptoService.test.ts b/src/__tests__/services/CryptoService.test.ts
index 82ecdc8..ecdc6e2 100644
--- a/src/__tests__/services/CryptoService.test.ts
+++ b/src/__tests__/services/CryptoService.test.ts
@@ -101,6 +101,24 @@ describe('CryptoService', () => {
);
});
+ it('rejects KS1 payloads without a complete ciphertext block', async () => {
+ const truncatedPayload = CryptoJS.lib.WordArray.create(new Uint8Array(48) as any);
+ const malformedPayload = `KS1:${CryptoJS.enc.Base64.stringify(truncatedPayload)}`;
+
+ await expect(CryptoService.decrypt(malformedPayload, masterKey)).rejects.toThrow(
+ 'Invalid payload length',
+ );
+ });
+
+ it('rejects KS1 payloads whose ciphertext is not block aligned', async () => {
+ const nonAlignedPayload = CryptoJS.lib.WordArray.create(new Uint8Array(65) as any);
+ const malformedPayload = `KS1:${CryptoJS.enc.Base64.stringify(nonAlignedPayload)}`;
+
+ await expect(CryptoService.decrypt(malformedPayload, masterKey)).rejects.toThrow(
+ 'Invalid payload length',
+ );
+ });
+
it('should return empty string for empty input (legitimate empty case)', async () => {
const result = await CryptoService.decrypt('', masterKey);
expect(result).toBe('');
diff --git a/src/__tests__/services/StorageService.test.ts b/src/__tests__/services/StorageService.test.ts
index 503a668..1585399 100644
--- a/src/__tests__/services/StorageService.test.ts
+++ b/src/__tests__/services/StorageService.test.ts
@@ -108,6 +108,21 @@ describe('StorageService', () => {
);
await expect(StorageService.getMasterKeyInfo()).resolves.toEqual(currentInfo);
});
+
+ it('deletes verifier metadata before clearing its cache', async () => {
+ const currentInfo: UserMasterKey = {
+ salt: 'current-salt',
+ iterations: 2,
+ memory: 19456,
+ verifier: 'current-verifier',
+ };
+ await StorageService.saveMasterKeyInfo(currentInfo);
+
+ await StorageService.deleteMasterKeyInfo();
+
+ expect(SecureStore.deleteItemAsync).toHaveBeenCalledWith('keysoft_master_key_info');
+ await expect(StorageService.getMasterKeyInfo()).resolves.toBeNull();
+ });
});
describe('User Preferences', () => {
@@ -169,6 +184,16 @@ describe('StorageService', () => {
await expect(StorageService.getAllPasswords()).resolves.toEqual([]);
});
+ it('does not clear passwords without an active vault key', async () => {
+ await StorageService.savePassword(mockPassword);
+ StorageService.setEncryptionKey('');
+ jest.clearAllMocks();
+
+ await expect(StorageService.clearAllPasswords()).rejects.toThrow('Encryption key not set');
+ expect(AsyncStorage.removeItem).not.toHaveBeenCalled();
+ await expect(StorageService.getAllPasswords()).resolves.toEqual([mockPassword]);
+ });
+
it('should retrieve decrypted passwords', async () => {
// Mock stored encrypted data
const storedData = `encrypted-${JSON.stringify([mockPassword])}`;
@@ -341,6 +366,79 @@ describe('StorageService', () => {
});
});
+ describe('Backup import', () => {
+ const existingPassword: Password = {
+ id: 'existing-password',
+ title: 'Existing',
+ username: 'user',
+ password: 'old-secret',
+ createdAt: mockTimestamp,
+ updatedAt: mockTimestamp,
+ };
+ const importedNote: Note = {
+ id: 'imported-note',
+ title: 'Imported',
+ content: 'Secret note',
+ createdAt: mockTimestamp,
+ updatedAt: mockTimestamp,
+ };
+
+ it('encrypts password and note collections before one storage write', async () => {
+ await StorageService.savePassword(existingPassword);
+ jest.clearAllMocks();
+
+ const updatedPassword = { ...existingPassword, password: 'new-secret' };
+ const result = await StorageService.importBackupData({
+ passwords: [updatedPassword],
+ notes: [importedNote],
+ });
+
+ expect(result).toEqual({ passwords: 1, notes: 1 });
+ expect(AsyncStorage.multiSet).toHaveBeenCalledTimes(1);
+ expect(AsyncStorage.multiSet).toHaveBeenCalledWith(
+ expect.arrayContaining([
+ [expect.stringContaining('passwords'), expect.stringContaining('encrypted-')],
+ [expect.stringContaining('notes'), expect.stringContaining('encrypted-')],
+ ]),
+ );
+ await expect(StorageService.getAllPasswords()).resolves.toEqual([updatedPassword]);
+ await expect(StorageService.getNotes()).resolves.toEqual([importedNote]);
+ });
+
+ it('keeps both caches unchanged when the combined persistence fails', async () => {
+ await StorageService.savePassword(existingPassword);
+ (AsyncStorage.multiSet as jest.Mock).mockRejectedValueOnce(new Error('disk full'));
+
+ await expect(
+ StorageService.importBackupData({
+ passwords: [{ ...existingPassword, password: 'new-secret' }],
+ notes: [importedNote],
+ }),
+ ).rejects.toThrow('disk full');
+
+ await expect(StorageService.getAllPasswords()).resolves.toEqual([existingPassword]);
+ await expect(StorageService.getNotes()).resolves.toEqual([]);
+ });
+
+ it('rejects an import whose merged password set exceeds the vault limit', async () => {
+ await StorageService.savePassword(existingPassword);
+ const importedPasswords = Array.from(
+ { length: StorageService.MAX_PASSWORDS_LIMIT },
+ (_, index): Password => ({
+ ...existingPassword,
+ id: `imported-${index}`,
+ }),
+ );
+ jest.clearAllMocks();
+
+ await expect(
+ StorageService.importBackupData({ passwords: importedPasswords }),
+ ).rejects.toThrow('Password limit');
+ expect(CryptoService.encrypt).not.toHaveBeenCalled();
+ expect(AsyncStorage.multiSet).not.toHaveBeenCalled();
+ });
+ });
+
describe('Notes', () => {
const mockNote: Note = {
id: '1',
@@ -362,6 +460,16 @@ describe('StorageService', () => {
mockEncryptionKey,
);
});
+
+ it('does not clear notes without an active vault key', async () => {
+ await StorageService.saveNote(mockNote);
+ StorageService.setEncryptionKey('');
+ jest.clearAllMocks();
+
+ await expect(StorageService.clearAllNotes()).rejects.toThrow('Encryption key not set');
+ expect(AsyncStorage.removeItem).not.toHaveBeenCalled();
+ await expect(StorageService.getNotes()).resolves.toEqual([mockNote]);
+ });
});
describe('Data reset', () => {
diff --git a/src/hooks/settings/useExportImport.ts b/src/hooks/settings/useExportImport.ts
index 6b20723..e5a5a4b 100644
--- a/src/hooks/settings/useExportImport.ts
+++ b/src/hooks/settings/useExportImport.ts
@@ -71,43 +71,24 @@ export const useExportImport = ({
return;
}
- const hasPasswords = Array.isArray(validatedData.passwords);
- const hasNotes = Array.isArray(validatedData.notes);
-
- let importedPasswordsCount = 0;
- let importedNotesCount = 0;
-
- if (hasPasswords && validatedData.passwords) {
- const currentPasswordCount = await Storage.getPasswordCount();
- const canAddMore = await Storage.canAddPassword();
- const passwordsToImport = validatedData.passwords.length;
-
- if (!canAddMore) {
- alert(
- t('limit_reached_import'),
- t('limit_reached_import_message').replace('{limit}', MAX_PASSWORDS_LIMIT.toString()),
- );
- return;
- }
+ if (validatedData.passwords) {
+ const currentPasswords = await Storage.getAllPasswords();
+ const currentIds = new Set(currentPasswords.map((password) => password.id));
+ const newIds = new Set(
+ validatedData.passwords
+ .map((password) => password.id)
+ .filter((id) => !currentIds.has(id)),
+ );
- const remainingSlots = MAX_PASSWORDS_LIMIT - currentPasswordCount;
- if (passwordsToImport > remainingSlots) {
+ if (currentPasswords.length + newIds.size > MAX_PASSWORDS_LIMIT) {
alert(t('insufficient_space'), t('insufficient_space_message'));
return;
}
-
- for (const pwd of validatedData.passwords) {
- await Storage.savePassword(pwd);
- importedPasswordsCount++;
- }
}
- if (hasNotes && validatedData.notes) {
- for (const note of validatedData.notes) {
- await Storage.saveNote(note);
- importedNotesCount++;
- }
- }
+ const importResult = await Storage.importBackupData(validatedData);
+ const importedPasswordsCount = importResult.passwords;
+ const importedNotesCount = importResult.notes;
await NotificationService.sendBackupSuccess();
@@ -320,7 +301,14 @@ export const useExportImport = ({
return;
}
- if (!isBackupFileSizeAllowed(selectedFile.size)) {
+ let fileSize = selectedFile.size;
+ if (fileSize === undefined && Platform.OS !== 'web') {
+ const fileInfo = await FileSystem.getInfoAsync(fileUri);
+ if (fileInfo.exists) fileSize = fileInfo.size;
+ }
+
+ // Reject unknown sizes instead of reading an unbounded file into memory.
+ if (!isBackupFileSizeAllowed(fileSize)) {
alert(t('error'), t('import_invalid_file'));
return;
}
diff --git a/src/services/auth/authService.ts b/src/services/auth/authService.ts
index 55daa43..64caac7 100644
--- a/src/services/auth/authService.ts
+++ b/src/services/auth/authService.ts
@@ -25,7 +25,8 @@ export type AuthFailureReason =
| 'biometrics_disabled'
| 'biometric_key_unavailable'
| 'biometric_auth_failed'
- | 'not_authenticated';
+ | 'not_authenticated'
+ | 'master_key_already_configured';
export interface AuthFailure {
reason: AuthFailureReason;
@@ -127,6 +128,10 @@ async function upgradeVaultKdfIfLegacy(
try {
Logger.info('AuthService: upgrading vault KDF to current Argon2id parameters');
+ // Read preferences before changing either side of the vault transaction. A
+ // preference read failure must not leave ciphertext and verifier metadata out of sync.
+ const prefs = await StorageService.getUserPreferences();
+
const { masterKeyInfo: newMkInfo, derivedKey: newKey } =
await CryptoService.createMasterKeyInfoWithDerivedKey(masterPassword);
@@ -153,8 +158,14 @@ async function upgradeVaultKdfIfLegacy(
throw error;
}
- // Keep biometric unlock working by storing the new vault key.
- const prefs = await StorageService.getUserPreferences();
+ // Ciphertext and verifier metadata are now committed as one logical unit.
+ // Update authentication state before optional biometric maintenance so a
+ // SecureStore cleanup failure can never trigger a vault rollback.
+ masterKeyInfo = newMkInfo;
+ StorageService.setEncryptionKey(newKey);
+
+ // Keep biometric unlock working by storing the new vault key. This is
+ // deliberately best-effort after the core vault transaction has committed.
if (prefs.biometricsEnabled) {
try {
await StorageService.saveBiometricKey(newKey);
@@ -163,12 +174,14 @@ async function upgradeVaultKdfIfLegacy(
'AuthService: failed to refresh biometric key after KDF upgrade',
biometricError,
);
- await disableBiometrics(prefs);
+ try {
+ await disableBiometrics(prefs);
+ } catch (cleanupError) {
+ Logger.warn('AuthService: failed to disable biometrics after KDF upgrade', cleanupError);
+ }
}
}
- masterKeyInfo = newMkInfo;
- StorageService.setEncryptionKey(newKey);
Logger.info('AuthService: vault KDF upgrade completed');
return true;
} catch (error) {
@@ -236,14 +249,22 @@ export const initDatabase = async (): Promise => {
* Configures the master password for the user.
*/
export const setupMasterPassword = async (masterPassword: string): Promise => {
+ let hasPersistedNewMasterKeyInfo = false;
try {
clearAuthFailure();
+ const existingMasterKeyInfo = await StorageService.getMasterKeyInfo();
+ if (existingMasterKeyInfo) {
+ setAuthFailure('master_key_already_configured', 'Master password is already configured');
+ return false;
+ }
+
// Create master key info (salt, iterations, etc.)
const { masterKeyInfo: mkInfo, derivedKey: encryptionKey } =
await CryptoService.createMasterKeyInfoWithDerivedKey(masterPassword);
// Save info to SecureStore
await StorageService.saveMasterKeyInfo(mkInfo);
+ hasPersistedNewMasterKeyInfo = true;
// Set encryption key in memory
StorageService.setEncryptionKey(encryptionKey);
@@ -258,6 +279,20 @@ export const setupMasterPassword = async (masterPassword: string): Promise =
const oldMkInfo = masterKeyInfo;
let dataReEncryptedWithNewKey = false;
let newEncryptionKey: string | null = null;
+ let prefs: Awaited>;
try {
+ // Resolve optional side-effect state before changing encrypted storage. If
+ // this read fails, the vault remains untouched.
+ prefs = await StorageService.getUserPreferences();
+
// 2. Create new Master Key Info
const { masterKeyInfo: newMkInfo, derivedKey } =
await CryptoService.createMasterKeyInfoWithDerivedKey(password);
@@ -558,23 +598,11 @@ export const updateMasterPassword = async (password: string): Promise =
// CRITICAL: If this fails, the rollback below restores the old encrypted state.
await StorageService.saveMasterKeyInfo(newMkInfo);
- // 6. If Biometrics enabled, replace the stored biometric key with the new vault key.
- const prefs = await StorageService.getUserPreferences();
- if (prefs.biometricsEnabled) {
- try {
- await StorageService.saveBiometricKey(newEncryptionKey);
- } catch (biometricError) {
- Logger.warn('AuthService: Failed to update biometric key after PIN change', biometricError);
- await disableBiometrics(prefs);
- }
- }
-
- // 7. Update memory state
+ // 6. Ciphertext and verifier metadata are committed. Update in-memory state
+ // before optional biometric maintenance so later cleanup failures cannot
+ // roll the vault back while leaving the new verifier in SecureStore.
masterKeyInfo = newMkInfo;
StorageService.setEncryptionKey(newEncryptionKey);
-
- Logger.info('AuthService: Master password updated successfully');
- return true;
} catch (error) {
Logger.error('AuthService: Error updating master password', error);
setAuthFailure('derive_key_failed', 'Master password update failed');
@@ -604,6 +632,25 @@ export const updateMasterPassword = async (password: string): Promise =
return false;
}
+
+ // 7. Biometric persistence is a separate, best-effort transaction. The
+ // password change is already durable and must remain successful even if the
+ // biometric key or its cleanup cannot be written.
+ if (prefs.biometricsEnabled && newEncryptionKey) {
+ try {
+ await StorageService.saveBiometricKey(newEncryptionKey);
+ } catch (biometricError) {
+ Logger.warn('AuthService: Failed to update biometric key after PIN change', biometricError);
+ try {
+ await disableBiometrics(prefs);
+ } catch (cleanupError) {
+ Logger.warn('AuthService: Failed to disable biometrics after PIN change', cleanupError);
+ }
+ }
+ }
+
+ Logger.info('AuthService: Master password updated successfully');
+ return true;
};
// Add other methods as needed based on original file...
diff --git a/src/services/auth/authServiceMock.ts b/src/services/auth/authServiceMock.ts
index f5a73be..be31e98 100644
--- a/src/services/auth/authServiceMock.ts
+++ b/src/services/auth/authServiceMock.ts
@@ -23,10 +23,14 @@ const AuthServiceMock = {
},
setupMasterPassword: async (masterPassword: string): Promise => {
+ let hasPersistedNewMasterKeyInfo = false;
try {
+ if (await StorageServiceMock.getMasterKeyInfo()) return false;
+
const { masterKeyInfo: mkInfo, derivedKey: encryptionKey } =
await CryptoServiceMock.createMasterKeyInfoWithDerivedKey(masterPassword);
await StorageServiceMock.saveMasterKeyInfo(mkInfo);
+ hasPersistedNewMasterKeyInfo = true;
StorageServiceMock.setEncryptionKey(encryptionKey);
await StorageServiceMock.initDatabase();
@@ -36,6 +40,16 @@ const AuthServiceMock = {
return true;
} catch (error) {
Logger.error('AuthServiceMock: Error setting up master password', error);
+ isAuthenticated = false;
+ masterKeyInfo = null;
+ StorageServiceMock.setEncryptionKey('');
+ if (hasPersistedNewMasterKeyInfo) {
+ try {
+ await StorageServiceMock.deleteMasterKeyInfo();
+ } catch (cleanupError) {
+ Logger.error('AuthServiceMock: Failed to roll back incomplete setup', cleanupError);
+ }
+ }
return false;
}
},
diff --git a/src/services/crypto/cryptoService.ts b/src/services/crypto/cryptoService.ts
index c825b9a..578bec3 100644
--- a/src/services/crypto/cryptoService.ts
+++ b/src/services/crypto/cryptoService.ts
@@ -366,8 +366,10 @@ export const decrypt = async (encryptedText: string, key: string): Promise= 0 && size <= MAX_BACKUP_FILE_SIZE_BYTES)
+ size !== undefined &&
+ Number.isSafeInteger(size) &&
+ size >= 0 &&
+ size <= MAX_BACKUP_FILE_SIZE_BYTES
);
}
diff --git a/src/services/storage/storageService.ts b/src/services/storage/storageService.ts
index 1371979..197ef90 100644
--- a/src/services/storage/storageService.ts
+++ b/src/services/storage/storageService.ts
@@ -335,6 +335,11 @@ export const getMasterKeyInfo = async (): Promise => {
return cache.masterKeyInfo;
};
+export const deleteMasterKeyInfo = async (): Promise => {
+ await SecureStore.deleteItemAsync(STORAGE_KEYS.MASTER_KEY_INFO_SECURE);
+ cache.masterKeyInfo = null;
+};
+
export const setEncryptionKey = (key: string): void => {
if (!key) {
encryptionKey = null;
@@ -364,6 +369,81 @@ export const canAddPassword = async (): Promise => {
return cache.passwords.length < MAX_PASSWORDS_LIMIT;
};
+export interface BackupImportData {
+ passwords?: Password[];
+ notes?: Note[];
+}
+
+export interface BackupImportResult {
+ passwords: number;
+ notes: number;
+}
+
+function mergeRecordsById(current: T[], imported: T[]): T[] {
+ const merged = [...current];
+ const indexesById = new Map(merged.map((record, index) => [record.id, index]));
+
+ for (const record of imported) {
+ const existingIndex = indexesById.get(record.id);
+ if (existingIndex === undefined) {
+ indexesById.set(record.id, merged.length);
+ merged.push(record);
+ } else {
+ merged[existingIndex] = record;
+ }
+ }
+
+ return merged;
+}
+
+/**
+ * Encrypts and persists a validated backup in one storage operation. All
+ * ciphertext is prepared before writing and caches are updated only after the
+ * write succeeds, preventing a failed import from exposing a partial vault in
+ * the active session.
+ */
+export const importBackupData = async (imported: BackupImportData): Promise => {
+ const activeKey = getEncryptionKeyOrThrow('import backup');
+ if (decryptionErrors.passwords || decryptionErrors.notes) {
+ throw new Error('Decryption error detected. Cannot import backup safely.');
+ }
+
+ const importedPasswords = imported.passwords ?? [];
+ const importedNotes = imported.notes ?? [];
+ const nextPasswords = mergeRecordsById(cache.passwords, importedPasswords);
+ const nextNotes = mergeRecordsById(cache.notes, importedNotes);
+
+ if (nextPasswords.length > MAX_PASSWORDS_LIMIT) {
+ throw new Error(`Password limit of ${MAX_PASSWORDS_LIMIT} would be exceeded.`);
+ }
+
+ const writes: [string, string][] = [];
+ if (imported.passwords) {
+ writes.push([
+ STORAGE_KEYS.PASSWORDS,
+ await CryptoService.encrypt(JSON.stringify(nextPasswords), activeKey),
+ ]);
+ }
+ if (imported.notes) {
+ writes.push([
+ STORAGE_KEYS.NOTES,
+ await CryptoService.encrypt(JSON.stringify(nextNotes), activeKey),
+ ]);
+ }
+
+ if (writes.length > 0) {
+ await AsyncStorage.multiSet(writes);
+ }
+
+ if (imported.passwords) cache.passwords = nextPasswords;
+ if (imported.notes) cache.notes = nextNotes;
+
+ return {
+ passwords: importedPasswords.length,
+ notes: importedNotes.length,
+ };
+};
+
function getEncryptionKeyOrThrow(action: string, scope?: 'passwords' | 'notes'): string {
if (scope && decryptionErrors[scope]) {
throw new Error(`Decryption error detected. Cannot ${action} safely.`);
@@ -471,9 +551,7 @@ export const deletePassword = async (id: string): Promise => {
export const clearAllPasswords = async (): Promise => {
try {
- if (decryptionErrors.passwords) {
- throw new Error('Decryption error detected. Cannot clear passwords safely.');
- }
+ getEncryptionKeyOrThrow('clear passwords', 'passwords');
await AsyncStorage.removeItem(STORAGE_KEYS.PASSWORDS);
cache.passwords = [];
Logger.info('StorageService: All passwords cleared');
@@ -606,9 +684,7 @@ export const deleteNote = async (noteId: string): Promise => {
export const clearAllNotes = async (): Promise => {
try {
- if (decryptionErrors.notes) {
- throw new Error('Decryption error detected. Cannot clear notes safely.');
- }
+ getEncryptionKeyOrThrow('clear notes', 'notes');
await AsyncStorage.removeItem(STORAGE_KEYS.NOTES);
cache.notes = [];
Logger.info('StorageService: All notes cleared');
diff --git a/src/services/storage/storageServiceMock.ts b/src/services/storage/storageServiceMock.ts
index b9f04bf..c63393f 100644
--- a/src/services/storage/storageServiceMock.ts
+++ b/src/services/storage/storageServiceMock.ts
@@ -86,6 +86,11 @@ const StorageServiceMock = {
}
},
+ deleteMasterKeyInfo: async (): Promise => {
+ await AsyncStorage.removeItem(STORAGE_KEYS.MASTER_KEY_INFO);
+ cachedMasterKeyInfo = null;
+ },
+
setEncryptionKey: (key: string): void => {
if (!key) {
encryptionKey = null;
@@ -185,6 +190,48 @@ const StorageServiceMock = {
return count < MAX_PASSWORDS_LIMIT;
},
+ importBackupData: async (imported: {
+ passwords?: Password[];
+ notes?: Note[];
+ }): Promise<{ passwords: number; notes: number }> => {
+ const passwords = await StorageServiceMock.getAllPasswords();
+ const notes = await StorageServiceMock.getNotes();
+
+ const mergeById = (current: T[], incoming: T[]): T[] => {
+ const merged = [...current];
+ const indexesById = new Map(merged.map((record, index) => [record.id, index]));
+ for (const record of incoming) {
+ const existingIndex = indexesById.get(record.id);
+ if (existingIndex === undefined) {
+ indexesById.set(record.id, merged.length);
+ merged.push(record);
+ } else {
+ merged[existingIndex] = record;
+ }
+ }
+ return merged;
+ };
+
+ const nextPasswords = mergeById(passwords, imported.passwords ?? []);
+ if (nextPasswords.length > MAX_PASSWORDS_LIMIT) {
+ throw new Error(`Password limit of ${MAX_PASSWORDS_LIMIT} would be exceeded.`);
+ }
+
+ const writes: [string, string][] = [];
+ if (imported.passwords) {
+ writes.push([STORAGE_KEYS.PASSWORDS, JSON.stringify(nextPasswords)]);
+ }
+ if (imported.notes) {
+ writes.push([STORAGE_KEYS.NOTES, JSON.stringify(mergeById(notes, imported.notes))]);
+ }
+ if (writes.length > 0) await AsyncStorage.multiSet(writes);
+
+ return {
+ passwords: imported.passwords?.length ?? 0,
+ notes: imported.notes?.length ?? 0,
+ };
+ },
+
// Notes methods
getNotes: async (): Promise => {
try {