Keysoft is an offline-first React Native application built with Expo. The architecture keeps user data local, separates screen concerns from service logic, and treats encryption and storage as first-class boundaries.
| Layer | Technology |
|---|---|
| App framework | Expo SDK 57 |
| UI runtime | React Native 0.86.0 |
| Language | TypeScript, strict mode |
| Navigation | React Navigation v7 |
| Package manager | Bun |
| Test framework | Jest, React Native Testing Library |
src/
components/ Shared UI and reusable controls
contexts/ Auth, language, theme, and alert state
hooks/ Complex screen behavior and orchestration
settings/ Settings workflows split out of the screen component
locales/ Italian and English translation dictionaries
models/ Domain interfaces
navigation/ Stack and tab navigation
screens/ User-facing screens
services/ Auth, crypto, storage, import/export, utilities
utils/ Cross-cutting helpers
Screens own presentation state and user interactions. They should not implement cryptography, persistence rules, or business validation directly. Complex screen workflows should move into hooks or services.
The presentation layer follows the Nocturne system: semantic light/dark theme tokens, compact outlined controls, bounded responsive content, a flat five-slot tab bar, and copy-first read views separated from edit forms. Shared dialog, bottom-sheet, toast, motion, and PIN-keypad primitives live under src/components/ui; motion honors the operating-system reduced-motion preference and is limited to opacity and transform effects.
ProfileAvatar owns photo rendering and the initial-based fallback across unlock, vault, and settings surfaces. KeysoftMark renders the canonical shield-and-eye asset in onboarding, matching the launcher, adaptive, splash, and web artwork configured in app.config.js.
The visible compact Settings layout is the canonical settings surface. It keeps email support, the in-app privacy notice, open-source notices, and GitHub Sponsors directly reachable. The hidden legacy layout is retained only as a temporary migration reference and must not be treated as proof that an action is user-accessible; regression tests inspect the visible layout before that boundary.
Contact details live in src/constants/contact.ts and are the only definition of the support address and the Mikesoft site. Support opens a mailto: intent and falls back to copying the address, so no screen or dictionary repeats either value.
Dialoghandles app alerts, blocking loading states, and confirmations, including destructive actions.BottomSheethandles selection lists and contextual workflows that benefit from a mobile sheet.Toasthandles transient success or informational feedback that must not interrupt the current task.AlertContextorchestrates global dialogs and toasts so screens do not fall back to platform-specificAlertorToastAndroidUI.- When an action inside a bottom sheet requires a confirmation dialog, the sheet closes first. Canceling may reopen it; this avoids competing native modal layers.
All overlays expose modal semantics where appropriate, keep decorative backdrops out of the accessibility tree, and use localized labels for dismiss and action controls.
The profile editor keeps a selected image as temporary form state. Saving copies a native picked file into application-owned storage, or stores a data URI on web, and then writes the stable reference to user preferences. Canceling leaves the saved avatar unchanged. This prevents temporary picker URIs from disappearing after reload or login.
The original Keysoft shield-and-eye artwork is the single app identity:
| Surface | Source |
|---|---|
| App launcher | assets/icon.png |
| Android adaptive | assets/adaptive-icon.png |
| Splash | assets/splash-icon.png |
| Web favicon | assets/favicon.png |
| Onboarding | KeysoftMark using the splash asset |
Keep these variants visually aligned when changing branding. The Android adaptive icon uses a white background while the splash screen provides separate light and dark backgrounds.
Contexts expose app-wide state and side effects:
AuthContext: authentication status and session lifecycle.LanguageContext: localization and language selection.ThemeContext: visual appearance.AlertContext: app-level dialog and toast orchestration.
Localization dictionaries live in src/locales and are loaded by LanguageContext. User-visible strings must be added to both Italian and English dictionaries.
Hooks contain complex screen workflows that would otherwise make screen components hard to scan. Settings-specific orchestration lives in src/hooks/settings, while SettingsScreen keeps presentation and modal composition.
Services own core behavior:
authService: authentication and master password lifecycle.cryptoService: key derivation, encryption, decryption, backup payload helpers, password generation.storageService: encrypted persistence, cache management, migration, preferences.import-export/backupValidation: validation and normalization of imported backup data.utils/*Service: notifications, clipboard, auto-lock, screen capture.
A top-level error boundary (src/components/ErrorBoundary.tsx) wraps the navigator inside the context providers. A render error in any screen shows a recoverable, themed and localized fallback instead of a blank screen, and the failure is logged through the sanitized Logger.
- The user authenticates with the master password/PIN, or with biometrics if biometric unlock was previously enabled.
- PIN login uses
CryptoServiceto derive a 64-character hex vault key and verify it against the stored master-key verifier. Argon2id uses the OWASP minimum parameters (19 MiB / t = 2). Legacy vaults (PBKDF2 or the old heavy Argon2 parameters) are transparently and non-destructively upgraded to the current parameters after a successful password login. - 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.
StorageServiceinitializes the encrypted vault cache with the active key.AuthContextcompletes the shared post-login pipeline: authentication state, login notification, post-auth migration, notification settings, clipboard timeout, and auto-lock timeout.- Password and note writes are serialized, encrypted with KS1, and stored in AsyncStorage.
- Logout and auto-lock clear authentication state and the in-memory key.
| Storage | Contents |
|---|---|
| AsyncStorage | Encrypted passwords, encrypted notes, categories, preferences, and the saved profile-image reference |
| SecureStore | Master key verifier metadata; optional biometric vault key protected by device authentication |
| Memory only | Active vault key and decrypted vault cache |
The active vault key must never be written to AsyncStorage or logs. The only persistence exception is biometric unlock: when the user enables biometrics from an authenticated session, StorageService stores the current vault key in SecureStore with requireAuthentication and device-only keychain accessibility. If that SecureStore item is missing, invalidated, or stale, biometric login disables biometrics and the user must log in with PIN before re-enabling it.
authService records a sanitized last-failure reason for UI messaging and debug diagnostics. Reasons distinguish cases such as missing master-key metadata, native KDF unavailability, KDF timeout, verifier mismatch, database initialization failure, and missing or invalid biometric key.
Failure reasons must not contain PINs, vault keys, password values, note content, or encrypted payloads.
Exports are user-initiated. Encrypted exports use KS1-PW1, which wraps:
- KDF salt and parameters.
- KS1 ciphertext.
- Version metadata.
Imports are parsed as unknown data and validated before any object reaches StorageService.
The test suite covers:
- Core services.
- Integration user flows.
- Source structure rules for lowercase directories and shared settings hooks.
- Compatibility and responsive behavior.
- Internationalization dictionary parity, placeholder parity, and hardcoded fallback regressions.
- Accessibility regressions for key icon-only controls and touch behavior.
- Large-list performance.
- Security regressions for biometric key handling, backup encryption, plaintext migration, and import validation.