Files
simplex-chat/apps/ios/product/flows/onboarding.md
T
2026-02-19 10:58:16 +00:00

13 KiB

Onboarding Flow

Related spec: spec/architecture.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":
    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:

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):
    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):
    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
  • 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