+
+Invest in SimpleX Chat. [Register now](https://simplexchat.typeform.com/crowdfunding).
# SimpleX - the first messaging platform that has no user identifiers of any kind - 100% private by design!
diff --git a/apps/ios/Shared/Model/AppAPITypes.swift b/apps/ios/Shared/Model/AppAPITypes.swift
index a5a56174b1..1a66156a49 100644
--- a/apps/ios/Shared/Model/AppAPITypes.swift
+++ b/apps/ios/Shared/Model/AppAPITypes.swift
@@ -84,6 +84,7 @@ enum ChatCommand: ChatCmdProtocol {
case apiLeaveGroup(groupId: Int64)
case apiListMembers(groupId: Int64)
case apiUpdateGroupProfile(groupId: Int64, groupProfile: GroupProfile)
+ case apiSetPublicGroupAccess(groupId: Int64, access: PublicGroupAccess)
case apiCreateGroupLink(groupId: Int64, memberRole: GroupMemberRole)
case apiGroupLinkMemberRole(groupId: Int64, memberRole: GroupMemberRole)
case apiDeleteGroupLink(groupId: Int64)
@@ -130,9 +131,9 @@ enum ChatCommand: ChatCmdProtocol {
case apiAddContact(userId: Int64, incognito: Bool)
case apiSetConnectionIncognito(connId: Int64, incognito: Bool)
case apiChangeConnectionUser(connId: Int64, userId: Int64)
- case apiConnectPlan(userId: Int64, connLink: String, linkOwnerSig: LinkOwnerSig?)
- case apiPrepareContact(userId: Int64, connLink: CreatedConnLink, contactShortLinkData: ContactShortLinkData)
- case apiPrepareGroup(userId: Int64, connLink: CreatedConnLink, directLink: Bool, groupShortLinkData: GroupShortLinkData)
+ case apiConnectPlan(userId: Int64, connLink: String, resolveMode: PlanResolveMode, linkOwnerSig: LinkOwnerSig?)
+ case apiPrepareContact(userId: Int64, connLink: CreatedConnLink, contactShortLinkData: ContactShortLinkData, verifiedDomain: SimplexDomain?)
+ case apiPrepareGroup(userId: Int64, connLink: CreatedConnLink, directLink: Bool, groupShortLinkData: GroupShortLinkData, verifiedDomain: SimplexDomain?)
case apiChangePreparedContactUser(contactId: Int64, newUserId: Int64)
case apiChangePreparedGroupUser(groupId: Int64, newUserId: Int64)
case apiConnectPreparedContact(contactId: Int64, incognito: Bool, msg: MsgContent?)
@@ -155,6 +156,9 @@ enum ChatCommand: ChatCmdProtocol {
case apiAddMyAddressShortLink(userId: Int64)
case apiSetProfileAddress(userId: Int64, on: Bool)
case apiSetAddressSettings(userId: Int64, addressSettings: AddressSettings)
+ case apiSetUserDomain(userId: Int64, simplexDomain: String?)
+ case apiVerifyContactDomain(contactId: Int64)
+ case apiVerifyGroupDomain(groupId: Int64)
case apiAcceptContact(incognito: Bool, contactReqId: Int64)
case apiRejectContact(contactReqId: Int64)
// WebRTC calls
@@ -343,11 +347,12 @@ enum ChatCommand: ChatCmdProtocol {
case let .apiAddContact(userId, incognito): return "/_connect \(userId) incognito=\(onOff(incognito))"
case let .apiSetConnectionIncognito(connId, incognito): return "/_set incognito :\(connId) \(onOff(incognito))"
case let .apiChangeConnectionUser(connId, userId): return "/_set conn user :\(connId) \(userId)"
- case let .apiConnectPlan(userId, connLink, linkOwnerSig):
+ case let .apiConnectPlan(userId, connLink, resolveMode, linkOwnerSig):
+ let resolveStr = resolveMode == .unknown ? "" : " resolve=\(resolveMode.rawValue)"
let sigStr = if let linkOwnerSig { " sig=\(encodeJSON(linkOwnerSig))" } else { "" }
- return "/_connect plan \(userId) \(connLink)\(sigStr)"
- case let .apiPrepareContact(userId, connLink, contactShortLinkData): return "/_prepare contact \(userId) \(connLink.connFullLink) \(connLink.connShortLink ?? "") \(encodeJSON(contactShortLinkData))"
- case let .apiPrepareGroup(userId, connLink, directLink, groupShortLinkData): return "/_prepare group \(userId) \(connLink.connFullLink) \(connLink.connShortLink ?? "") direct=\(onOff(directLink)) \(encodeJSON(groupShortLinkData))"
+ return "/_connect plan \(userId) \(connLink)\(resolveStr)\(sigStr)"
+ case let .apiPrepareContact(userId, connLink, contactShortLinkData, verifiedDomain): return "/_prepare contact \(userId) \(connLink.cmdString)\(verifiedDomain.map{ " \($0.cmdString)" } ?? "") \(encodeJSON(contactShortLinkData))"
+ case let .apiPrepareGroup(userId, connLink, directLink, groupShortLinkData, verifiedDomain): return "/_prepare group \(userId) \(connLink.cmdString) direct=\(onOff(directLink))\(verifiedDomain.map{ " \($0.cmdString)" } ?? "") \(encodeJSON(groupShortLinkData))"
case let .apiChangePreparedContactUser(contactId, newUserId): return "/_set contact user @\(contactId) \(newUserId)"
case let .apiChangePreparedGroupUser(groupId, newUserId): return "/_set group user #\(groupId) \(newUserId)"
case let .apiConnectPreparedContact(contactId, incognito, mc): return "/_connect contact @\(contactId) incognito=\(onOff(incognito))\(maybeContent(mc))"
@@ -370,6 +375,10 @@ enum ChatCommand: ChatCmdProtocol {
case let .apiAddMyAddressShortLink(userId): return "/_short_link_address \(userId)"
case let .apiSetProfileAddress(userId, on): return "/_profile_address \(userId) \(onOff(on))"
case let .apiSetAddressSettings(userId, addressSettings): return "/_address_settings \(userId) \(encodeJSON(addressSettings))"
+ case let .apiSetUserDomain(userId, simplexDomain): return "/_set domain \(userId)" + (simplexDomain.map { " " + $0 } ?? "")
+ case let .apiSetPublicGroupAccess(groupId, access): return "/_public group access #\(groupId) \(encodeJSON(access))"
+ case let .apiVerifyContactDomain(contactId): return "/_verify domain @\(contactId)"
+ case let .apiVerifyGroupDomain(groupId): return "/_verify domain #\(groupId)"
case let .apiAcceptContact(incognito, contactReqId): return "/_accept incognito=\(onOff(incognito)) \(contactReqId)"
case let .apiRejectContact(contactReqId): return "/_reject \(contactReqId)"
case let .apiSendCallInvitation(contact, callType): return "/_call invite @\(contact.apiId) \(encodeJSON(callType))"
@@ -481,6 +490,7 @@ enum ChatCommand: ChatCmdProtocol {
case .apiLeaveGroup: return "apiLeaveGroup"
case .apiListMembers: return "apiListMembers"
case .apiUpdateGroupProfile: return "apiUpdateGroupProfile"
+ case .apiSetPublicGroupAccess: return "apiSetPublicGroupAccess"
case .apiCreateGroupLink: return "apiCreateGroupLink"
case .apiGroupLinkMemberRole: return "apiGroupLinkMemberRole"
case .apiDeleteGroupLink: return "apiDeleteGroupLink"
@@ -551,6 +561,9 @@ enum ChatCommand: ChatCmdProtocol {
case .apiAddMyAddressShortLink: return "apiAddMyAddressShortLink"
case .apiSetProfileAddress: return "apiSetProfileAddress"
case .apiSetAddressSettings: return "apiSetAddressSettings"
+ case .apiSetUserDomain: return "apiSetUserDomain"
+ case .apiVerifyContactDomain: return "apiVerifyContactDomain"
+ case .apiVerifyGroupDomain: return "apiVerifyGroupDomain"
case .apiAcceptContact: return "apiAcceptContact"
case .apiRejectContact: return "apiRejectContact"
case .apiSendCallInvitation: return "apiSendCallInvitation"
@@ -802,7 +815,7 @@ enum ChatResponse1: Decodable, ChatAPIResult {
case invitation(user: UserRef, connLinkInvitation: CreatedConnLink, connection: PendingContactConnection)
case connectionIncognitoUpdated(user: UserRef, toConnection: PendingContactConnection)
case connectionUserChanged(user: UserRef, fromConnection: PendingContactConnection, toConnection: PendingContactConnection, newUser: UserRef)
- case connectionPlan(user: UserRef, connLink: CreatedConnLink, connectionPlan: ConnectionPlan)
+ case connectionPlan(user: UserRef, connLink: CreatedConnLink, planSimplexName: SimplexNameInfo?, otherSimplexName: SimplexNameInfo?, connectionPlan: ConnectionPlan)
case newPreparedChat(user: UserRef, chat: ChatData)
case contactUserChanged(user: UserRef, fromContact: Contact, newUser: UserRef, toContact: Contact)
case groupUserChanged(user: UserRef, fromGroup: GroupInfo, newUser: UserRef, toGroup: GroupInfo)
@@ -926,7 +939,7 @@ enum ChatResponse1: Decodable, ChatAPIResult {
case let .invitation(u, connLinkInvitation, connection): return withUser(u, "connLinkInvitation: \(connLinkInvitation)\nconnection: \(connection)")
case let .connectionIncognitoUpdated(u, toConnection): return withUser(u, String(describing: toConnection))
case let .connectionUserChanged(u, fromConnection, toConnection, newUser): return withUser(u, "fromConnection: \(String(describing: fromConnection))\ntoConnection: \(String(describing: toConnection))\nnewUserId: \(String(describing: newUser.userId))")
- case let .connectionPlan(u, connLink, connectionPlan): return withUser(u, "connLink: \(String(describing: connLink))\nconnectionPlan: \(String(describing: connectionPlan))")
+ case let .connectionPlan(u, connLink, _, _, connectionPlan): return withUser(u, "connLink: \(String(describing: connLink))\nconnectionPlan: \(String(describing: connectionPlan))")
case let .newPreparedChat(u, chat): return withUser(u, String(describing: chat))
case let .contactUserChanged(u, fromContact, newUser, toContact): return withUser(u, "fromContact: \(String(describing: fromContact))\nnewUserId: \(String(describing: newUser.userId))\ntoContact: \(String(describing: toContact))")
case let .groupUserChanged(u, fromGroup, newUser, toGroup): return withUser(u, "fromGroup: \(String(describing: fromGroup))\nnewUserId: \(String(describing: newUser.userId))\ntoGroup: \(String(describing: toGroup))")
@@ -960,6 +973,8 @@ enum ChatResponse2: Decodable, ChatAPIResult {
case membersRoleUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], toRole: GroupMemberRole)
case membersBlockedForAllUser(user: UserRef, groupInfo: GroupInfo, members: [GroupMember], blocked: Bool)
case groupUpdated(user: UserRef, toGroup: GroupInfo)
+ case contactDomainVerified(user: UserRef, contact: Contact, verificationFailure: String?)
+ case groupDomainVerified(user: UserRef, groupInfo: GroupInfo, verificationFailure: String?)
case groupLinkCreated(user: UserRef, groupInfo: GroupInfo, groupLink: GroupLink)
case groupLink(user: UserRef, groupInfo: GroupInfo, groupLink: GroupLink)
case groupLinkDeleted(user: UserRef, groupInfo: GroupInfo)
@@ -1015,6 +1030,8 @@ enum ChatResponse2: Decodable, ChatAPIResult {
case .membersRoleUser: "membersRoleUser"
case .membersBlockedForAllUser: "membersBlockedForAllUser"
case .groupUpdated: "groupUpdated"
+ case .contactDomainVerified: "contactDomainVerified"
+ case .groupDomainVerified: "groupDomainVerified"
case .groupLinkCreated: "groupLinkCreated"
case .groupLink: "groupLink"
case .groupLinkDeleted: "groupLinkDeleted"
@@ -1066,6 +1083,8 @@ enum ChatResponse2: Decodable, ChatAPIResult {
case let .membersRoleUser(u, groupInfo, members, toRole): return withUser(u, "groupInfo: \(groupInfo)\nmembers: \(members)\ntoRole: \(toRole)")
case let .membersBlockedForAllUser(u, groupInfo, members, blocked): return withUser(u, "groupInfo: \(groupInfo)\nmember: \(members)\nblocked: \(blocked)")
case let .groupUpdated(u, toGroup): return withUser(u, String(describing: toGroup))
+ case let .contactDomainVerified(u, contact, verificationFailure): return withUser(u, "contact: \(contact)\nverificationFailure: \(verificationFailure ?? "ok")")
+ case let .groupDomainVerified(u, groupInfo, verificationFailure): return withUser(u, "groupInfo: \(groupInfo)\nverificationFailure: \(verificationFailure ?? "ok")")
case let .groupLinkCreated(u, groupInfo, groupLink): return withUser(u, "groupInfo: \(groupInfo)\ngroupLink: \(groupLink)")
case let .groupLink(u, groupInfo, groupLink): return withUser(u, "groupInfo: \(groupInfo)\ngroupLink: \(groupLink)")
case let .groupLinkDeleted(u, groupInfo): return withUser(u, String(describing: groupInfo))
@@ -1368,6 +1387,20 @@ enum OwnerVerification: Decodable, Hashable {
case failed(reason: String)
}
+struct ConnectionPlanResult {
+ var connLink: CreatedConnLink
+ var planSimplexName: SimplexNameInfo?
+ var otherSimplexName: SimplexNameInfo?
+ var connectionPlan: ConnectionPlan
+}
+
+// APIConnectPlan resolution scope; .never is local-store-only (no network), used for per-keystroke name search
+enum PlanResolveMode: String {
+ case allGroups
+ case unknown
+ case never
+}
+
enum ConnectionPlan: Decodable, Hashable {
case invitationLink(invitationLinkPlan: InvitationLinkPlan)
case contactAddress(contactAddressPlan: ContactAddressPlan)
@@ -1766,14 +1799,15 @@ struct ServerOperator: Identifiable, Equatable, Codable {
serverDomains: ["simplex.im"],
conditionsAcceptance: .accepted(acceptedAt: nil, autoAccepted: false),
enabled: true,
- smpRoles: ServerRoles(storage: true, proxy: true),
- xftpRoles: ServerRoles(storage: true, proxy: true)
+ smpRoles: ServerRoles(storage: true, proxy: true, names: true),
+ xftpRoles: ServerRoles(storage: true, proxy: true, names: false)
)
}
struct ServerRoles: Equatable, Codable {
var storage: Bool
var proxy: Bool
+ var names: Bool
}
struct UserOperatorServers: Identifiable, Equatable, Codable {
@@ -1800,8 +1834,8 @@ struct UserOperatorServers: Identifiable, Equatable, Codable {
serverDomains: [],
conditionsAcceptance: .accepted(acceptedAt: nil, autoAccepted: false),
enabled: false,
- smpRoles: ServerRoles(storage: true, proxy: true),
- xftpRoles: ServerRoles(storage: true, proxy: true)
+ smpRoles: ServerRoles(storage: true, proxy: true, names: true),
+ xftpRoles: ServerRoles(storage: true, proxy: true, names: false)
)
}
set { `operator` = newValue }
@@ -1824,6 +1858,7 @@ struct UserOperatorServers: Identifiable, Equatable, Codable {
public enum UserServersWarning: Decodable {
case noChatRelays(user: UserRef?)
+ case noNamesServers(user: UserRef?)
}
enum UserServersError: Decodable {
diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift
index ea2de31569..5e957d68dc 100644
--- a/apps/ios/Shared/Model/SimpleXAPI.swift
+++ b/apps/ios/Shared/Model/SimpleXAPI.swift
@@ -569,7 +569,7 @@ private func processSendMessageCmd(toChatType: ChatType, cmd: ChatCommand) async
return cItems
}
if let networkErrorAlert = networkErrorAlert(r) {
- AlertManager.shared.showAlert(networkErrorAlert)
+ await MainActor.run { showAlert(networkErrorAlert) }
} else {
sendMessageErrorAlert(r.unexpected)
}
@@ -1003,15 +1003,15 @@ func apiVerifyGroupMember(_ groupId: Int64, _ groupMemberId: Int64, connectionCo
return nil
}
-func apiAddContact(incognito: Bool) async -> ((CreatedConnLink, PendingContactConnection)?, Alert?) {
+func apiAddContact(incognito: Bool) async -> (CreatedConnLink, PendingContactConnection)? {
guard let userId = ChatModel.shared.currentUser?.userId else {
logger.error("apiAddContact: no current user")
- return (nil, nil)
+ return nil
}
let r: APIResult