mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-04-14 18:16:12 +00:00
10 KiB
10 KiB
Connection Flow
Related spec: spec/api.md | spec/architecture.md
Overview
Establishing contact between two SimpleX Chat users. SimpleX uses no user identifiers; connections are formed through one-time invitation links or permanent SimpleX addresses. Each connection creates unique unidirectional SMP queues, ensuring no server can correlate sender and receiver. Supports incognito mode for per-contact random profile generation.
Prerequisites
- User profile created and chat engine running
- Network connectivity to SMP relay servers
- For QR code scanning: camera permission granted
Step-by-Step Processes
1. Create Invitation Link
- User taps "+" button in
ChatListView->NewChatMenuButton-> "Add contact". NewChatViewis presented.- Calls
apiAddContact(incognito:):func apiAddContact(incognito: Bool) async -> ((CreatedConnLink, PendingContactConnection)?, Alert?) - Internally sends
ChatCommand.apiAddContact(userId:incognito:)to core. - Core creates SMP queues and returns
ChatResponse1.invitation(user, connLinkInv, connection). - Returns
(CreatedConnLink, PendingContactConnection). CreatedConnLinkcontains the invitation URI (both full and short link forms).- UI displays:
- QR code rendered by
QRCodeview (scannable by peer) - Share button to send link via system share sheet
- Copy button for clipboard
- QR code rendered by
- A
PendingContactConnectionappears in the chat list while awaiting peer.
2. Connect via Link
- User receives a SimpleX link (pasted, scanned, or opened via URL scheme).
- If opened via deep link:
SimpleXApp.onOpenURLsetschatModel.appOpenUrl. - For manual entry: User pastes link in
NewChatView. - First,
apiConnectPlan(connLink:inProgress:)is called to validate:func apiConnectPlan(connLink: String, inProgress: BoxedValue<Bool>) async -> ((CreatedConnLink, ConnectionPlan)?, Alert?) - Returns
ConnectionPlanindicating whether it is an invitation, contact address, or group link, and whether connection is already established. - If valid, calls
apiConnect(incognito:connLink:):func apiConnect(incognito: Bool, connLink: CreatedConnLink) async -> (ConnReqType, PendingContactConnection)? - Core creates the connection and returns one of:
ChatResponse1.sentConfirmation(user, connection)-- for invitation links (type:.invitation)ChatResponse1.sentInvitation(user, connection)-- for contact address links (type:.contact)ChatResponse1.contactAlreadyExists(user, contact)-- duplicate
PendingContactConnectionappears in chat list while awaiting peer confirmation.
3. Prepared Contact/Group Flow (Short Links)
- For short links with embedded profile data, the app uses a two-phase flow.
apiPrepareContact(connLink:contactShortLinkData:)orapiPrepareGroup(connLink:directLink:groupShortLinkData:)creates a local prepared chat.directLinkistruefor standard group links,falsefor channel relay links.- Returns
ChatDatawith the prepared contact/group shown in UI before connecting. - User can switch profiles or set incognito before committing.
apiConnectPreparedContact(contactId:incognito:msg:)finalizes the connection.- Returns
ChatResponse1.startedConnectionToContact(user, contact).
4. Accept Contact Request
- When a peer connects via the user's SimpleX address, core generates a
ChatEvent.receivedContactRequest. processReceivedMsghandles the event, adding aUserContactRequesttoChatModel.- Contact request appears in
ChatListViewas a specialContactRequestViewrow. - User taps "Accept":
func apiAcceptContactRequest(incognito: Bool, contactReqId: Int64) async -> Contact? - Sends
ChatCommand.apiAcceptContact(incognito:contactReqId:). - Core returns
ChatResponse1.acceptingContactRequest(user, contact). - Connection handshake proceeds asynchronously.
- User can also reject:
apiRejectContactRequest(contactReqId:)->ChatResponse1.contactRequestRejected.
5. Connection Established
- Both sides complete the SMP handshake asynchronously.
- Core sends
ChatEvent.contactConnected(user, contact, userCustomProfile). processReceivedMsgupdatesChatModel:- Contact status transitions from pending to active.
- Chat becomes available for messaging.
NtfManagermay post a notification: "Contact connected".- The
PendingContactConnectionin the chat list is replaced by the full contact chat.
6. Create SimpleX Address
- User navigates to Settings or taps "Create SimpleX address" during onboarding.
- Calls
apiCreateUserAddress():func apiCreateUserAddress() async throws -> CreatedConnLink? - Core creates a permanent address (unlike one-time invitations).
- Address is stored in
ChatModel.shared.userAddress. - Can be shared publicly; multiple contacts can connect via the same address.
- User must accept each incoming contact request individually.
- To delete:
apiDeleteUserAddress()removes the address and associated SMP queues.
7a. Relay Link Rejection
- User scans, pastes, or opens a relay address link (URL path
/rorSimplexLinkType.relay). - In
ContentView.connectViaUrl_(): early return with alert "Relay address" / "This is a chat relay address, it cannot be used to connect." - In
NewChatView.planAndConnect():.simplexLink(_, .relay, _, _)pattern triggers the same alert. - The link is NOT processed further. No connection is attempted.
7b. Channel Prepared Group Flow
- When connecting to a channel link (
GroupShortLinkInfo.direct == false): apiPrepareGroup(connLink:directLink:groupShortLinkData:)is called withdirectLink: false, preparing the channel locally.groupShortLinkInfo.groupRelays(hostnames) stored inChatModel.shared.channelRelayHostnames[groupId].- Pre-join UI shows channel icon and "Open new channel" (not "Open new group").
apiConnectPreparedGroup(groupId:incognito:msg:)returns(GroupInfo, [RelayConnectionResult]).RelayConnectionResultcontainsrelayMember: GroupMemberand optionalrelayError: ChatError?per relay.- Relay members are upserted to
chatModel.groupMembers;channelRelayHostnamesentry is cleared.
7. Incognito Connection
- Before connecting, user toggles "Incognito" in the connection UI.
incognito: trueis passed toapiAddContact,apiConnect, orapiAcceptContactRequest.- Core generates a random display name for this connection only.
- The random profile is stored per-connection; the user's real profile is never shared.
- Incognito status is shown with a mask icon in the chat.
- Can also be toggled for pending connections via
apiSetConnectionIncognito(connId:incognito:).
Data Structures
| Type | Location | Description |
|---|---|---|
CreatedConnLink |
SimpleXChat/APITypes.swift |
Contains connFullLink (URI) and optional connShortLink |
PendingContactConnection |
SimpleXChat/ChatTypes.swift |
Represents an in-progress connection before contact is established |
ConnectionPlan |
Shared/Model/AppAPITypes.swift |
Enum describing what a link will do: connect contact, join group, already connected, etc. |
ConnReqType |
Shared/Views/NewChat/NewChatView.swift |
.invitation, .contact, or .groupLink -- type of connection request |
Contact |
SimpleXChat/ChatTypes.swift |
Full contact model with profile, connection status, preferences |
UserContactRequest |
SimpleXChat/ChatTypes.swift |
Incoming contact request awaiting acceptance |
ChatType |
SimpleXChat/ChatTypes.swift |
.direct, .group, .local, .contactRequest, .contactConnection |
GroupShortLinkInfo |
Shared/Model/AppAPITypes.swift |
Contains direct: Bool, groupRelays: [String], publicGroupId: String?; transient data returned by prepare |
RelayConnectionResult |
Shared/Model/AppAPITypes.swift |
Contains relayMember: GroupMember, relayError: ChatError?; per-relay join outcome |
Error Cases
| Error | Cause | Handling |
|---|---|---|
ChatError.invalidConnReq |
Malformed or expired link | Alert: "Invalid connection link" |
ChatError.unsupportedConnReq |
Link requires newer app version | Alert: "Unsupported connection link" |
ChatError.errorAgent(.SMP(_, .AUTH)) |
Link already used or deleted | Alert: "Connection error (AUTH)" |
ChatError.errorAgent(.SMP(_, .BLOCKED(info))) |
Server operator blocked connection | Alert: "Connection blocked" with reason |
ChatError.errorAgent(.SMP(_, .QUOTA)) |
Too many undelivered messages | Alert: "Undelivered messages" |
ChatError.errorAgent(.INTERNAL("SEUniqueID")) |
Duplicate connection attempt | Alert: "Already connected?" |
ChatError.errorAgent(.BROKER(_, .TIMEOUT)) |
Server timeout | Retryable via chatApiSendCmdWithRetry |
ChatError.errorAgent(.BROKER(_, .NETWORK)) |
Network failure | Retryable via chatApiSendCmdWithRetry |
contactAlreadyExists |
Connecting to existing contact | Alert: "Contact already exists" with contact name |
errorAgent(.SMP(_, .AUTH)) on accept |
Sender deleted request | Alert: "Sender may have deleted the connection request" |
Key Files
| File | Purpose |
|---|---|
Shared/Views/NewChat/NewChatView.swift |
Main connection UI: create link, paste link, QR scan |
Shared/Views/NewChat/NewChatMenuButton.swift |
"+" button menu in chat list |
Shared/Views/NewChat/QRCode.swift |
QR code rendering for invitation links |
Shared/Views/NewChat/AddContactLearnMore.swift |
Help text explaining connection process |
Shared/Views/ChatList/ContactRequestView.swift |
Incoming contact request display |
Shared/Views/ChatList/ContactConnectionView.swift |
Pending connection display |
Shared/Views/ChatList/ContactConnectionInfo.swift |
Connection details sheet |
Shared/Model/SimpleXAPI.swift |
API functions: apiAddContact, apiConnect, apiConnectPlan, apiAcceptContactRequest, apiCreateUserAddress |
Shared/Model/AppAPITypes.swift |
ConnectionPlan enum, GroupLink struct |
SimpleXChat/APITypes.swift |
CreatedConnLink, ComposedMessage, command/response types |
SimpleXChat/ChatTypes.swift |
Contact, PendingContactConnection, UserContactRequest |
Related Specifications
apps/ios/product/README.md-- Product overview: Contacts capability mapapps/ios/product/flows/messaging.md-- Messaging after connection is established