- core: reject service payloads nested deeper than 32 levels, so a hostile service cannot crash the app's JSON decoder - core: one parseServiceBody (decompress, decode, depth check) at all three decode points: incoming requests, sendServiceRequestTo, redeemBadgeCode - directory: '%' and '_' in search text match literally instead of acting as LIKE wildcards - directory: search text over 100 characters is rejected over RPC - directory: a new directory creates its address with DR keys, so service requests work without a manual key rotation - directory: service requests are always processed; the --service-requests switch is no longer needed for the directory - directory: the in-flight service request cap is a DirectoryOpts field (default 8) instead of a hardcoded constant - directory: the page-fit check compresses the exact response object that is sent, not a proxy with a different key order and cursor - directory: search entries omit the full link when a short link exists, saving hundreds of bytes per entry in the envelope - ios: add DirectorySearch.swift and DirectorySearchView.swift to the Xcode project; the app did not build without them - ios: in the onboarding state the search bar is at the bottom in one-hand mode, as in the chat list - ios: in the onboarding state a failed search shows the retry row instead of returning to the cards - ios: a search with no results shows a "No results" row - ios: the "No chats found" overlay no longer covers directory results, the retry row or the no-results row - ios: directory rows are rendered by one builder for both the onboarding and chat-list branches - ios: retrying after a failed "Show more" resumes from the cursor instead of restarting the search - ios: cancelling the spinner, or tapping a result mid-search, drops the pending request without clearing results already shown - ios: typing during a search stops the spinner, and a stale response no longer clears a newer search's spinner - ios: repeated Search taps for the same text while a search is in flight are ignored - ios: channel results show the subscriber count and channel icon; group results show the group icon - ios: remove the unused searchText parameter of SearchInDirectoryRow - android: a directory search timeout no longer pops the retry alert (the UI will show a retry row) - tests: RPC paging by the echoed cursor, literal wildcards and the length bound, rejection over the cap, page fitting to the envelope, nested payload rejection
SimpleX Chat iOS app
This file provides guidance when working with code in this repository.
iOS App Overview
The iOS app is a SwiftUI application that interfaces with the Haskell core library via FFI. It shares the SimpleXChat framework with two extensions: Notification Service Extension (NSE) for push notifications and Share Extension (SE) for sharing content from other apps.
Build & Development
Open SimpleX.xcodeproj in Xcode. The project has five targets:
- SimpleX (iOS) - Main app (Bundle ID:
chat.simplex.app) - SimpleXChat - Framework containing FFI bridge and shared types
- SimpleX NSE - Notification Service Extension
- SimpleX SE - Share Extension
- Tests iOS - UI tests
Build and run via Xcode (Product > Build/Run). Tests run via Product > Test or:
xcodebuild test -scheme "SimpleX (iOS)" -destination 'platform=iOS Simulator,name=iPhone 15'
Deployment target: iOS 15.0+, Swift 5.0.
Architecture
Haskell Core Integration
The app calls the Haskell core library through C FFI defined in SimpleXChat/SimpleX.h:
chat_migrate_init_key()- Initialize/migrate databasechat_send_cmd_retry()- Send command to chat controllerchat_recv_msg_wait()- Receive messages from controller
Swift wrappers in SimpleXChat/API.swift:
chatMigrateInit()- Initialize chat controllersendSimpleXCmd<R>()- Send typed commands and parse responsesrecvSimpleXMsg<R>()- Receive typed messages
Haskell runtime initialization (SimpleXChat/hs_init.c) uses different memory configurations:
- Main app: 64MB heap
- NSE: 512KB heap (minimal footprint for background processing)
- SE: 1MB heap
Pre-compiled Haskell libraries are in Libraries/{ios,mac,sim}/.
State Management
- ChatModel (
Shared/Model/ChatModel.swift) - Main singletonObservableObjectfor app-wide state (chat list, active chat, users) - ItemsModel - Manages chat items within a selected chat (similar to Kotlin's ChatsContext)
- AppTheme - Theme management and customization
App Structure
Entry point: Shared/SimpleXApp.swift
Key directories in Shared/:
Model/- Data models and API layer (ChatModel.swift,SimpleXAPI.swift)Views/- SwiftUI views organized by feature:ChatList/- Chat list and user pickerChat/- Message display and compositionCall/- VoIP call UIUserSettings/- App settingsLocalAuth/- Passcode and biometric authenticationDatabase/- Database initialization and migration
Shared Data Between Targets
All three targets share data via App Group (group.chat.simplex.app):
SimpleXChat/AppGroup.swift- GroupDefaults wrapper for typed shared preferences- Keychain for sensitive data:
kcDatabasePassword,kcAppPassword,kcSelfDestructPassword
Key Types
Types are defined in SimpleXChat/:
ChatTypes.swift- User, Chat, Message, Group typesAPITypes.swift- API request/response types
Commands follow ChatCmdProtocol (has cmdString property), sent as JSON through FFI.
Localization
31 languages supported. Localization files in SimpleX Localizations/.
Workflow:
Product > Export Localizations- Export XLIFF filesProduct > Import Localizations- Import updated translations
SimpleX Assets
The app includes optional assets behind the SIMPLEX_ASSETS Swift compilation flag. Without setup, the app builds normally without them.
Setup
Create Local.xcconfig (gitignored) in the apps/ios/ directory:
SIMPLEX_ASSETS_DIR = /path/to/assets
SWIFT_ACTIVE_COMPILATION_CONDITIONS = $(inherited) SIMPLEX_ASSETS
The copy script (scripts/ios/copy-assets.sh) runs as a build phase on each build but exits immediately if SIMPLEX_ASSETS is not set.
Updating assets
When source images change, regenerate resized images (requires ImageMagick):
cd path/to/assets && ./resize.sh
Background Capabilities
Configured in Info.plist:
- Background modes: audio, fetch, remote-notification, voip
- URL scheme:
simplex://for deep linking - BGTaskScheduler:
chat.simplex.app.receive