mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-08-14 22:20:35 +00:00
240 lines
13 KiB
Markdown
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
|