Files
simplex-chat/apps/ios/product/flows/onboarding.md
T

240 lines
13 KiB
Markdown

# Onboarding Flow
> **Related spec:** [spec/architecture.md](../../spec/architecture.md) | [spec/database.md](../../spec/database.md)
## Overview
First-time setup and migration flows for SimpleX Chat iOS. Covers app initialization, profile creation, server operator selection, notification configuration, and database import/export for device migration. The app uses a Haskell runtime for its core chat engine, with SQLite databases shared between the main app and the Notification Service Extension (NSE).
## Prerequisites
- Fresh install of SimpleX Chat from the App Store, or
- Existing install with database archive for import/migration
- iOS 15+ with App Group entitlement configured
## Step-by-Step Processes
### 1. App Initialization Sequence
On every app launch, `SimpleXApp.init()` executes the following in order:
```
1. haskell_init() -- Start Haskell runtime system (GHC RTS)
2. UserDefaults.standard.register(defaults:) -- Set default preferences (appDefaults)
3. setGroupDefaults() -- Configure app group shared defaults
4. registerGroupDefaults() -- Register group container defaults
5. setDbContainer() -- Configure database paths in app group container
6. BGManager.shared.register() -- Register background task handlers
7. NtfManager.shared.registerCategories() -- Register notification action categories
```
Then in `ContentView.onAppear`:
- If no migration is in progress and authentication is set up, `initChatAndMigrate()` is called.
- This triggers `chatMigrateInit()` to initialize/migrate databases.
- Then `startChat()` is called to start the chat engine.
### 2. Fresh Install -- Onboarding Steps
Onboarding is managed by `OnboardingStage` enum and `OnboardingView`:
**Step 1: SimpleX Info** (`step1_SimpleXInfo`)
1. `SimpleXInfo` view is presented.
2. Explains SimpleX's architecture: no user identifiers, E2E encryption, decentralized servers.
3. User taps "Create your profile" to proceed.
**Step 2: Create Profile** (`step2_CreateProfile` -- now inline in step 1)
1. `CreateFirstProfile` view (embedded in the onboarding flow).
2. User enters display name (required). Full name is set to empty string.
3. Display name is validated via `mkValidName()` and `canCreateProfile()`.
4. On "Create":
```swift
AppChatState.shared.set(.active)
m.currentUser = try apiCreateActiveUser(profile)
try startChat()
```
5. `apiCreateActiveUser(Profile(displayName:fullName:shortDescr:))` creates the user in the Haskell core.
6. `startChat()` initializes the chat engine.
7. Onboarding advances to `step3_ChooseServerOperators`.
**Step 3: Choose Server Operators** (`step3_ChooseServerOperators`)
1. `OnboardingConditionsView` is presented (simplified conditions acceptance).
2. User reviews and accepts server operator conditions.
3. This configures which SMP/XFTP server operators to use.
4. Advances to `step4_SetNotificationsMode`.
**Step 4: Set Notifications** (`step4_SetNotificationsMode`)
1. `SetNotificationsMode` view is presented.
2. Three options:
- **Instant**: Requires Apple Push Notification service. Registers device token via `apiRegisterToken(token:notificationMode:)`.
- **Periodic**: Uses iOS background app refresh. No push token needed.
- **Off**: No notifications.
3. For instant mode: `apiRegisterToken` sends `ChatCommand.apiRegisterToken(token:notificationMode:)` and receives `ChatResponse2.ntfTokenStatus(status)`.
4. On completion: `onboardingStageDefault.set(.onboardingComplete)`.
**Onboarding Complete** (`onboardingComplete`)
1. `ChatListView` is shown.
2. Empty state displays "Add contact" prompt via `ChatHelp`.
3. If delivery receipts haven't been configured: `chatModel.setDeliveryReceipts = true` triggers a prompt.
### 3. startChat() -- Chat Engine Startup
Called after profile creation or on subsequent app launches:
```swift
func startChat(refreshInvitations: Bool = true, onboarding: Bool = false) throws {
1. setNetworkConfig(getNetCfg()) -- Apply network configuration
2. apiCheckChatRunning() -- Check if already running
3. listUsers() -- Load all user profiles
4. getUserChatData() -- Load chats, tags, address, TTL
5. NtfManager.shared.setNtfBadgeCount(...) -- Set badge count
6. refreshCallInvitations() -- Check pending call invitations
7. apiGetNtfToken() -- Get notification token status
8. apiStartChat() -- Start the Haskell chat engine
9. registerToken(token:) -- Register push token if available
10. ChatReceiver.shared.start() -- Start message receive loop
}
```
### 4. Database Setup
**Location:**
- App group container (shared with NSE): determined by `dbContainerGroupDefault`
- Path prefix: `simplex_v1` (`DB_FILE_PREFIX`)
- Chat database: `simplex_v1_chat.db` (messages, contacts, groups, settings)
- Agent database: `simplex_v1_agent.db` (SMP connections, encryption keys, queues)
**Initialization:**
- `chatMigrateInit(useKey:confirmMigrations:backgroundMode:)` in `SimpleXChat/API.swift`.
- Creates databases if they do not exist.
- Runs pending migrations with confirmation mode.
- Handles database encryption:
- If keychain storage enabled: generates random DB key on first run (`randomDatabasePassword()`).
- Stores key in keychain via `kcDatabasePassword`.
- `initialRandomDBPassphraseGroupDefault` tracks whether using auto-generated key.
**Encryption:**
- Optional database encryption passphrase via `DatabaseEncryptionView`.
- `apiStorageEncryption(currentKey:newKey:)` changes encryption key.
- `testStorageEncryption(key:)` validates a key against the database.
### 5. Database Export (Source Device)
1. User navigates to Settings -> Database -> "Export database".
2. Chat must be stopped first for data consistency.
3. Calls `apiExportArchive(config: ArchiveConfig)`:
```swift
func apiExportArchive(config: ArchiveConfig) async throws -> [ArchiveError]
```
4. Core creates a ZIP archive containing both databases and file attachments.
5. Returns any non-fatal `[ArchiveError]` (e.g., file access issues).
6. User transfers the archive to the new device via AirDrop, file share, etc.
### 6. Database Import (Destination Device)
1. On new device: during onboarding or Settings -> Database -> "Import database".
2. User selects the archive file.
3. Calls `apiImportArchive(config: ArchiveConfig)`:
```swift
func apiImportArchive(config: ArchiveConfig) async throws -> [ArchiveError]
```
4. Core extracts the archive, replacing local databases.
5. Returns any non-fatal `[ArchiveError]`.
6. Chat engine is restarted with the imported data.
7. All contacts, groups, messages, and settings are restored.
### 7. In-App Device Migration
An alternative to manual export/import using direct device-to-device transfer.
**Source device** (`MigrateFromDevice` view):
1. User navigates to Settings -> Database -> "Migrate to another device".
2. App creates a temporary database and uploads archive via XFTP standalone file.
3. Generates a migration link containing the file URL and encryption key.
4. Displays QR code / share link for the destination device.
**Destination device** (`MigrateToDevice` view):
1. On new device: onboarding detects migration state or user selects "Migrate".
2. Scans/pastes the migration link.
3. `downloadStandaloneFile(user:url:file:ctrl:)` downloads the archive from XFTP.
4. `standaloneFileInfo(url:ctrl:)` validates the file metadata.
5. Archive is imported, databases are restored.
6. `chatInitTemporaryDatabase(url:key:confirmation:)` may be used for temporary DB operations during migration.
7. Chat engine starts with the migrated data.
If migration is interrupted:
- `chatModel.migrationState` preserves state across app restarts.
- On next launch, `ContentView.onAppear` detects pending migration and resumes.
### 8. Additional Profile Creation (Multi-Account)
1. From `UserPicker` (profile switcher) -> "Add profile".
2. `CreateProfile` view is presented (distinct from `CreateFirstProfile`).
3. User enters display name and optional bio (max 160 bytes JSON-encoded, `MAX_BIO_LENGTH_BYTES`).
4. `apiCreateActiveUser(profile)` creates additional user.
5. `listUsers()` and `getUserChatData()` refresh the model.
6. No onboarding steps -- goes directly to chat list.
## Data Structures
| Type | Location | Description |
|------|----------|-------------|
| `OnboardingStage` | `Shared/Views/Onboarding/OnboardingView.swift` | Enum: `step1_SimpleXInfo`, `step2_CreateProfile`, `step3_ChooseServerOperators`, `step4_SetNotificationsMode`, `onboardingComplete` |
| `Profile` | `SimpleXChat/ChatTypes.swift` | `displayName`, `fullName`, `image`, `shortDescr` |
| `User` | `SimpleXChat/ChatTypes.swift` | Full user model with profile, userId, and settings |
| `ArchiveConfig` | `SimpleXChat/APITypes.swift` | Configuration for database export/import |
| `DBMigrationResult` | `SimpleXChat/API.swift` | Result of database migration: `.ok`, `.errorNotADatabase`, `.errorKeychain`, etc. |
| `MigrationConfirmation` | `SimpleXChat/API.swift` | Migration confirmation mode: `.error`, `.yesUp`, `.yesUpDown` |
| `DeviceToken` | `SimpleXChat/ChatTypes.swift` | Apple push notification device token |
| `NtfTknStatus` | `SimpleXChat/ChatTypes.swift` | Notification token status: registered, active, expired, etc. |
| `NotificationsMode` | `SimpleXChat/ChatTypes.swift` | `.off`, `.periodic`, `.instant` |
| `MigrationFileLinkData` | Used in standalone file transfers for device migration |
| `AppChatState` | `SimpleXChat/` | Shared state: `.active`, `.stopped`, `.suspended` |
## Error Cases
| Error | Cause | Handling |
|-------|-------|----------|
| `DBMigrationResult.errorNotADatabase` | Wrong encryption key or corrupt DB | Show `DatabaseErrorView` with options |
| `DBMigrationResult.errorKeychain` | Keychain access failed | Show error, offer to re-enter passphrase |
| `DBMigrationResult.errorMigration` | Schema migration failure | Show error with migration details |
| `duplicateUserError` | Display name already in use | `UserProfileAlert.duplicateUserError` |
| `invalidDisplayNameError` | Invalid characters in display name | `UserProfileAlert.invalidDisplayNameError` |
| `createUserError` | Core failed to create user | Alert with error details |
| `invalidNameError(validName)` | Name needs normalization | Alert suggesting the valid name |
| Archive import errors | Missing files, version mismatch | Non-fatal `[ArchiveError]` displayed |
| Migration interrupted | Network failure, app killed | State preserved in `chatModel.migrationState`, resumed on next launch |
## Key Files
| File | Purpose |
|------|---------|
| `Shared/SimpleXApp.swift` | App entry point: `haskell_init`, defaults registration, DB container setup, BG tasks |
| `Shared/AppDelegate.swift` | Push notification registration, URL handling |
| `Shared/ContentView.swift` | Root view: authentication, onboarding routing, chat initialization |
| `Shared/Views/Onboarding/OnboardingView.swift` | Onboarding step router, `OnboardingStage` enum |
| `Shared/Views/Onboarding/SimpleXInfo.swift` | Step 1: Privacy architecture explanation |
| `Shared/Views/Onboarding/CreateProfile.swift` | Profile creation: `CreateProfile` (additional) and `CreateFirstProfile` (onboarding) |
| `Shared/Views/Onboarding/ChooseServerOperators.swift` | Step 3: Server operator conditions |
| `Shared/Views/Onboarding/SetNotificationsMode.swift` | Step 4: Notification mode selection |
| `Shared/Views/Onboarding/CreateSimpleXAddress.swift` | Optional address creation during onboarding |
| `Shared/Views/Onboarding/HowItWorks.swift` | Educational content about SimpleX protocol |
| `Shared/Views/Migration/MigrateFromDevice.swift` | Source device migration UI |
| `Shared/Views/Migration/MigrateToDevice.swift` | Destination device migration UI |
| `Shared/Views/Database/DatabaseView.swift` | Database management: export, import, encryption |
| `Shared/Views/Database/DatabaseEncryptionView.swift` | Database passphrase management |
| `Shared/Views/Database/DatabaseErrorView.swift` | Database error recovery UI |
| `Shared/Views/Database/MigrateToAppGroupView.swift` | Legacy migration from Documents to App Group container |
| `Shared/Model/SimpleXAPI.swift` | `startChat`, `apiCreateActiveUser`, `apiExportArchive`, `apiImportArchive`, `apiRegisterToken` |
| `SimpleXChat/API.swift` | `chatMigrateInit`, `chatInitTemporaryDatabase`, low-level DB initialization |
| `SimpleXChat/FileUtils.swift` | DB file paths, constants (`DB_FILE_PREFIX`, `CHAT_DB`, `AGENT_DB`) |
| `SimpleXChat/AppGroup.swift` | App group container configuration |
| `SimpleXChat/KeyChain.swift` | Keychain access for DB passphrase and app passwords |
| `Shared/Model/BGManager.swift` | Background task registration and scheduling |
| `Shared/Model/NtfManager.swift` | Notification management and badge counts |
## Related Specifications
- `apps/ios/product/README.md` -- Product overview: architecture and capabilities
- `apps/ios/product/flows/connection.md` -- After onboarding, user establishes first connections
- `apps/ios/product/flows/messaging.md` -- Messaging starts after profile creation