mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-08-14 11:30:18 +00:00
* ios, desktop, android: clearer error message when connecting via removed link (AUTH) * Update apps/ios/Shared/Model/SimpleXAPI.swift Co-authored-by: Evgeny <evgeny@poberezkin.com> * android, desktop: use concise AUTH error message to match iOS * update localizations --------- Co-authored-by: Evgeny <evgeny@poberezkin.com>
179 lines
10 KiB
Markdown
179 lines
10 KiB
Markdown
# Connection Flow
|
|
|
|
> **Related spec:** [spec/api.md](../../spec/api.md) | [spec/architecture.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
|
|
|
|
1. User taps "+" button in `ChatListView` -> `NewChatMenuButton` -> "Add contact".
|
|
2. `NewChatView` is presented.
|
|
3. Calls `apiAddContact(incognito:)`:
|
|
```swift
|
|
func apiAddContact(incognito: Bool) async
|
|
-> ((CreatedConnLink, PendingContactConnection)?, Alert?)
|
|
```
|
|
4. Internally sends `ChatCommand.apiAddContact(userId:incognito:)` to core.
|
|
5. Core creates SMP queues and returns `ChatResponse1.invitation(user, connLinkInv, connection)`.
|
|
6. Returns `(CreatedConnLink, PendingContactConnection)`.
|
|
7. `CreatedConnLink` contains the invitation URI (both full and short link forms).
|
|
8. UI displays:
|
|
- QR code rendered by `QRCode` view (scannable by peer)
|
|
- Share button to send link via system share sheet
|
|
- Copy button for clipboard
|
|
9. A `PendingContactConnection` appears in the chat list while awaiting peer.
|
|
|
|
### 2. Connect via Link
|
|
|
|
1. User receives a SimpleX link (pasted, scanned, or opened via URL scheme).
|
|
2. If opened via deep link: `SimpleXApp.onOpenURL` sets `chatModel.appOpenUrl`.
|
|
3. For manual entry: User pastes link in `NewChatView`.
|
|
4. First, `apiConnectPlan(connLink:inProgress:)` is called to validate:
|
|
```swift
|
|
func apiConnectPlan(connLink: String, inProgress: BoxedValue<Bool>) async
|
|
-> ((CreatedConnLink, ConnectionPlan)?, Alert?)
|
|
```
|
|
5. Returns `ConnectionPlan` indicating whether it is an invitation, contact address, or group link, and whether connection is already established.
|
|
6. If valid, calls `apiConnect(incognito:connLink:)`:
|
|
```swift
|
|
func apiConnect(incognito: Bool, connLink: CreatedConnLink) async
|
|
-> (ConnReqType, PendingContactConnection)?
|
|
```
|
|
7. 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
|
|
8. `PendingContactConnection` appears in chat list while awaiting peer confirmation.
|
|
|
|
### 3. Prepared Contact/Group Flow (Short Links)
|
|
|
|
1. For short links with embedded profile data, the app uses a two-phase flow.
|
|
2. `apiPrepareContact(connLink:contactShortLinkData:)` or `apiPrepareGroup(connLink:directLink:groupShortLinkData:)` creates a local prepared chat. `directLink` is `true` for standard group links, `false` for channel relay links.
|
|
3. Returns `ChatData` with the prepared contact/group shown in UI before connecting.
|
|
4. User can switch profiles or set incognito before committing.
|
|
5. `apiConnectPreparedContact(contactId:incognito:msg:)` finalizes the connection.
|
|
6. Returns `ChatResponse1.startedConnectionToContact(user, contact)`.
|
|
|
|
### 4. Accept Contact Request
|
|
|
|
1. When a peer connects via the user's SimpleX address, core generates a `ChatEvent.receivedContactRequest`.
|
|
2. `processReceivedMsg` handles the event, adding a `UserContactRequest` to `ChatModel`.
|
|
3. Contact request appears in `ChatListView` as a special `ContactRequestView` row.
|
|
4. User taps "Accept":
|
|
```swift
|
|
func apiAcceptContactRequest(incognito: Bool, contactReqId: Int64) async -> Contact?
|
|
```
|
|
5. Sends `ChatCommand.apiAcceptContact(incognito:contactReqId:)`.
|
|
6. Core returns `ChatResponse1.acceptingContactRequest(user, contact)`.
|
|
7. Connection handshake proceeds asynchronously.
|
|
8. User can also reject: `apiRejectContactRequest(contactReqId:)` -> `ChatResponse1.contactRequestRejected`.
|
|
|
|
### 5. Connection Established
|
|
|
|
1. Both sides complete the SMP handshake asynchronously.
|
|
2. Core sends `ChatEvent.contactConnected(user, contact, userCustomProfile)`.
|
|
3. `processReceivedMsg` updates `ChatModel`:
|
|
- Contact status transitions from pending to active.
|
|
- Chat becomes available for messaging.
|
|
4. `NtfManager` may post a notification: "Contact connected".
|
|
5. The `PendingContactConnection` in the chat list is replaced by the full contact chat.
|
|
|
|
### 6. Create SimpleX Address
|
|
|
|
1. User navigates to Settings or taps "Create SimpleX address" during onboarding.
|
|
2. Calls `apiCreateUserAddress()`:
|
|
```swift
|
|
func apiCreateUserAddress() async throws -> CreatedConnLink?
|
|
```
|
|
3. Core creates a permanent address (unlike one-time invitations).
|
|
4. Address is stored in `ChatModel.shared.userAddress`.
|
|
5. Can be shared publicly; multiple contacts can connect via the same address.
|
|
6. User must accept each incoming contact request individually.
|
|
7. To delete: `apiDeleteUserAddress()` removes the address and associated SMP queues.
|
|
|
|
### 7a. Relay Link Rejection
|
|
|
|
1. User scans, pastes, or opens a relay address link (URL path `/r` or `SimplexLinkType.relay`).
|
|
2. In `ContentView.connectViaUrl_()`: early return with alert "Relay address" / "This is a chat relay address, it cannot be used to connect."
|
|
3. In `NewChatView.planAndConnect()`: `.simplexLink(_, .relay, _, _)` pattern triggers the same alert.
|
|
4. The link is NOT processed further. No connection is attempted.
|
|
|
|
### 7b. Channel Prepared Group Flow
|
|
|
|
1. When connecting to a channel link (`GroupShortLinkInfo.direct == false`):
|
|
2. `apiPrepareGroup(connLink:directLink:groupShortLinkData:)` is called with `directLink: false`, preparing the channel locally.
|
|
3. `groupShortLinkInfo.groupRelays` (hostnames) stored in `ChatModel.shared.channelRelayHostnames[groupId]`.
|
|
4. Pre-join UI shows channel icon and "Open new channel" (not "Open new group").
|
|
5. `apiConnectPreparedGroup(groupId:incognito:msg:)` returns `(GroupInfo, [RelayConnectionResult])`.
|
|
6. `RelayConnectionResult` contains `relayMember: GroupMember` and optional `relayError: ChatError?` per relay.
|
|
7. Relay members are upserted to `chatModel.groupMembers`; `channelRelayHostnames` entry is cleared.
|
|
|
|
### 7. Incognito Connection
|
|
|
|
1. Before connecting, user toggles "Incognito" in the connection UI.
|
|
2. `incognito: true` is passed to `apiAddContact`, `apiConnect`, or `apiAcceptContactRequest`.
|
|
3. Core generates a random display name for this connection only.
|
|
4. The random profile is stored per-connection; the user's real profile is never shared.
|
|
5. Incognito status is shown with a mask icon in the chat.
|
|
6. 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 link removed" |
|
|
| `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 map
|
|
- `apps/ios/product/flows/messaging.md` -- Messaging after connection is established
|