ios: remove types used only in the app from the framework (#5866)

* ios: remove types used only in the app from the framework

* move more types

* comment
This commit is contained in:
Evgeny 2025-05-02 12:27:08 +01:00 committed by GitHub
parent e7a4611be9
commit f5c706f2dd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 2775 additions and 2467 deletions

File diff suppressed because it is too large Load diff

View file

@ -11,7 +11,7 @@ import UIKit
import Dispatch
import BackgroundTasks
import SwiftUI
import SimpleXChat
@preconcurrency import SimpleXChat
private var chatController: chat_ctrl?
@ -91,7 +91,7 @@ func chatSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? =
logger.debug("chatSendCmd \(cmd.cmdType)")
}
let start = Date.now
let resp = bgTask
let resp: ChatResponse = bgTask
? withBGTask(bgDelay: bgDelay) { sendSimpleXCmd(cmd, ctrl) }
: sendSimpleXCmd(cmd, ctrl)
if log {
@ -115,7 +115,7 @@ func chatSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil
func chatRecvMsg(_ ctrl: chat_ctrl? = nil) async -> ChatResponse? {
await withCheckedContinuation { cont in
_ = withBGTask(bgDelay: msgDelay) { () -> ChatResponse? in
let resp = recvSimpleXMsg(ctrl)
let resp: ChatResponse? = recvSimpleXMsg(ctrl)
cont.resume(returning: resp)
return resp
}
@ -123,7 +123,7 @@ func chatRecvMsg(_ ctrl: chat_ctrl? = nil) async -> ChatResponse? {
}
func apiGetActiveUser(ctrl: chat_ctrl? = nil) throws -> User? {
let r = chatSendCmdSync(.showActiveUser, ctrl)
let r: ChatResponse = chatSendCmdSync(.showActiveUser, ctrl)
switch r {
case let .activeUser(user): return user
case .chatCmdError(_, .error(.noActiveUser)): return nil
@ -132,7 +132,7 @@ func apiGetActiveUser(ctrl: chat_ctrl? = nil) throws -> User? {
}
func apiCreateActiveUser(_ p: Profile?, pastTimestamp: Bool = false, ctrl: chat_ctrl? = nil) throws -> User {
let r = chatSendCmdSync(.createActiveUser(profile: p, pastTimestamp: pastTimestamp), ctrl)
let r: ChatResponse = chatSendCmdSync(.createActiveUser(profile: p, pastTimestamp: pastTimestamp), ctrl)
if case let .activeUser(user) = r { return user }
throw r
}
@ -199,19 +199,19 @@ func apiUnmuteUser(_ userId: Int64) async throws -> User {
}
func setUserPrivacy_(_ cmd: ChatCommand) async throws -> User {
let r = await chatSendCmd(cmd)
let r: ChatResponse = await chatSendCmd(cmd)
if case let .userPrivacy(_, updatedUser) = r { return updatedUser }
throw r
}
func apiDeleteUser(_ userId: Int64, _ delSMPQueues: Bool, viewPwd: String?) async throws {
let r = await chatSendCmd(.apiDeleteUser(userId: userId, delSMPQueues: delSMPQueues, viewPwd: viewPwd))
let r: ChatResponse = await chatSendCmd(.apiDeleteUser(userId: userId, delSMPQueues: delSMPQueues, viewPwd: viewPwd))
if case .cmdOk = r { return }
throw r
}
func apiStartChat(ctrl: chat_ctrl? = nil) throws -> Bool {
let r = chatSendCmdSync(.startChat(mainApp: true, enableSndFiles: true), ctrl)
let r: ChatResponse = chatSendCmdSync(.startChat(mainApp: true, enableSndFiles: true), ctrl)
switch r {
case .chatStarted: return true
case .chatRunning: return false
@ -890,7 +890,7 @@ func apiConnect_(incognito: Bool, connLink: CreatedConnLink) async -> ((ConnReqT
logger.error("apiConnect: no current user")
return (nil, nil)
}
let r = await chatSendCmd(.apiConnect(userId: userId, incognito: incognito, connLink: connLink))
let r: ChatResponse = await chatSendCmd(.apiConnect(userId: userId, incognito: incognito, connLink: connLink))
let m = ChatModel.shared
switch r {
case let .sentConfirmation(_, connection):
@ -1281,7 +1281,7 @@ func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool
case let .rcvFileAccepted(_, chatItem):
await chatItemSimpleUpdate(user, chatItem)
default:
if let chatError = chatError(r) {
if let chatError = r.chatErrorType {
switch chatError {
case let .fileNotApproved(fileId, unknownServers):
fileIdsToApprove.append(fileId)
@ -1348,7 +1348,7 @@ func receiveFiles(user: any UserLike, fileIds: [Int64], userApprovedRelays: Bool
)
}
default:
if let chatError = chatError(errorResponse) {
if let chatError = errorResponse.chatErrorType {
switch chatError {
case .fileCancelled, .fileAlreadyReceiving:
logger.debug("receiveFiles ignoring FileCancelled or FileAlreadyReceiving error")
@ -1635,7 +1635,7 @@ func apiLeaveGroup(_ groupId: Int64) async throws -> GroupInfo {
// use ChatModel's loadGroupMembers from views
func apiListMembers(_ groupId: Int64) async -> [GroupMember] {
let r = await chatSendCmd(.apiListMembers(groupId: groupId))
let r: ChatResponse = await chatSendCmd(.apiListMembers(groupId: groupId))
if case let .groupMembers(_, group) = r { return group.members }
return []
}

View file

@ -752,7 +752,7 @@ private class MigrationChatReceiver {
func receiveMsgLoop() async {
// TODO use function that has timeout
if let msg = await chatRecvMsg(ctrl) {
if let msg: ChatResponse = await chatRecvMsg(ctrl) {
Task {
await TerminalItems.shared.add(.resp(.now, msg))
}

View file

@ -0,0 +1,173 @@
//
// APITypes.swift
// SimpleX
//
// Created by EP on 01/05/2025.
// Copyright © 2025 SimpleX Chat. All rights reserved.
//
import SimpleXChat
enum NSEChatCommand: ChatCmdProtocol {
case showActiveUser
case startChat(mainApp: Bool, enableSndFiles: Bool)
case apiActivateChat(restoreChat: Bool)
case apiSuspendChat(timeoutMicroseconds: Int)
case apiSetNetworkConfig(networkConfig: NetCfg)
case apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String)
case apiSetEncryptLocalFiles(enable: Bool)
case apiGetNtfConns(nonce: String, encNtfInfo: String)
case apiGetConnNtfMessages(connMsgReqs: [ConnMsgReq])
case receiveFile(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?, inline: Bool?)
case setFileToReceive(fileId: Int64, userApprovedRelays: Bool, encrypted: Bool?)
var cmdString: String {
switch self {
case .showActiveUser: return "/u"
case let .startChat(mainApp, enableSndFiles): return "/_start main=\(onOff(mainApp)) snd_files=\(onOff(enableSndFiles))"
case let .apiActivateChat(restore): return "/_app activate restore=\(onOff(restore))"
case let .apiSuspendChat(timeoutMicroseconds): return "/_app suspend \(timeoutMicroseconds)"
case let .apiSetNetworkConfig(networkConfig): return "/_network \(encodeJSON(networkConfig))"
case let .apiSetAppFilePaths(filesFolder, tempFolder, assetsFolder):
return "/set file paths \(encodeJSON(AppFilePaths(appFilesFolder: filesFolder, appTempFolder: tempFolder, appAssetsFolder: assetsFolder)))"
case let .apiSetEncryptLocalFiles(enable): return "/_files_encrypt \(onOff(enable))"
case let .apiGetNtfConns(nonce, encNtfInfo): return "/_ntf conns \(nonce) \(encNtfInfo)"
case let .apiGetConnNtfMessages(connMsgReqs): return "/_ntf conn messages \(connMsgReqs.map { $0.cmdString }.joined(separator: ","))"
case let .receiveFile(fileId, userApprovedRelays, encrypt, inline): return "/freceive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))\(onOffParam("inline", inline))"
case let .setFileToReceive(fileId, userApprovedRelays, encrypt): return "/_set_file_to_receive \(fileId)\(onOffParam("approved_relays", userApprovedRelays))\(onOffParam("encrypt", encrypt))"
}
}
private func onOffParam(_ param: String, _ b: Bool?) -> String {
if let b = b {
" \(param)=\(onOff(b))"
} else {
""
}
}
}
enum NSEChatResponse: Decodable, Error, ChatRespProtocol {
case response(type: String, json: String)
case activeUser(user: User)
case chatStarted
case chatRunning
case chatSuspended
case contactConnected(user: UserRef, contact: Contact, userCustomProfile: Profile?)
case receivedContactRequest(user: UserRef, contactRequest: UserContactRequest)
case newChatItems(user: UserRef, chatItems: [AChatItem])
case rcvFileAccepted(user: UserRef, chatItem: AChatItem)
case rcvFileSndCancelled(user: UserRef, chatItem: AChatItem, rcvFileTransfer: RcvFileTransfer)
case sndFileComplete(user: UserRef, chatItem: AChatItem, sndFileTransfer: SndFileTransfer)
case sndFileRcvCancelled(user: UserRef, chatItem_: AChatItem?, sndFileTransfer: SndFileTransfer)
case callInvitation(callInvitation: RcvCallInvitation)
case ntfConns(ntfConns: [NtfConn])
case connNtfMessages(receivedMsgs: [NtfMsgInfo?])
case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgAckInfo)
case cmdOk(user_: UserRef?)
case chatCmdError(user_: UserRef?, chatError: ChatError)
case chatError(user_: UserRef?, chatError: ChatError)
var responseType: String {
switch self {
case let .response(type, _): "* \(type)"
case .activeUser: "activeUser"
case .chatStarted: "chatStarted"
case .chatRunning: "chatRunning"
case .chatSuspended: "chatSuspended"
case .contactConnected: "contactConnected"
case .receivedContactRequest: "receivedContactRequest"
case .newChatItems: "newChatItems"
case .rcvFileAccepted: "rcvFileAccepted"
case .rcvFileSndCancelled: "rcvFileSndCancelled"
case .sndFileComplete: "sndFileComplete"
case .sndFileRcvCancelled: "sndFileRcvCancelled"
case .callInvitation: "callInvitation"
case .ntfConns: "ntfConns"
case .connNtfMessages: "connNtfMessages"
case .ntfMessage: "ntfMessage"
case .cmdOk: "cmdOk"
case .chatCmdError: "chatCmdError"
case .chatError: "chatError"
}
}
var details: String {
switch self {
case let .response(_, json): return json
case let .activeUser(user): return String(describing: user)
case .chatStarted: return noDetails
case .chatRunning: return noDetails
case .chatSuspended: return noDetails
case let .contactConnected(u, contact, _): return withUser(u, String(describing: contact))
case let .receivedContactRequest(u, contactRequest): return withUser(u, String(describing: contactRequest))
case let .newChatItems(u, chatItems):
let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n")
return withUser(u, itemsString)
case let .rcvFileAccepted(u, chatItem): return withUser(u, String(describing: chatItem))
case let .rcvFileSndCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem))
case let .sndFileComplete(u, chatItem, _): return withUser(u, String(describing: chatItem))
case let .sndFileRcvCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem))
case let .callInvitation(inv): return String(describing: inv)
case let .ntfConns(ntfConns): return String(describing: ntfConns)
case let .connNtfMessages(receivedMsgs): return "receivedMsgs: \(String(describing: receivedMsgs))"
case let .ntfMessage(u, connEntity, ntfMessage): return withUser(u, "connEntity: \(String(describing: connEntity))\nntfMessage: \(String(describing: ntfMessage))")
case .cmdOk: return noDetails
case let .chatCmdError(u, chatError): return withUser(u, String(describing: chatError))
case let .chatError(u, chatError): return withUser(u, String(describing: chatError))
}
}
var noDetails: String { "\(responseType): no details" }
static func chatResponse(_ s: String) -> NSEChatResponse {
let d = s.data(using: .utf8)!
// TODO is there a way to do it without copying the data? e.g:
// let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson))
// let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free)
do {
let r = try jsonDecoder.decode(APIResponse<NSEChatResponse>.self, from: d)
return r.resp
} catch {
logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)")
}
var type: String?
var json: String?
if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary {
if let jResp = j["resp"] as? NSDictionary, jResp.count == 1 || jResp.count == 2 {
type = jResp.allKeys[0] as? String
if jResp.count == 2 && type == "_owsf" {
type = jResp.allKeys[1] as? String
}
if type == "chatCmdError" {
if let jError = jResp["chatCmdError"] as? NSDictionary {
return .chatCmdError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? ""))
}
} else if type == "chatError" {
if let jError = jResp["chatError"] as? NSDictionary {
return .chatError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? ""))
}
}
}
json = serializeJSON(j, options: .prettyPrinted)
}
return NSEChatResponse.response(type: type ?? "invalid", json: json ?? s)
}
var chatError: ChatError? {
switch self {
case let .chatCmdError(_, error): error
case let .chatError(_, error): error
default: nil
}
}
var chatErrorType: ChatErrorType? {
switch self {
case let .chatCmdError(_, .error(error)): error
case let .chatError(_, .error(error)): error
default: nil
}
}
}

View file

@ -789,9 +789,9 @@ func receiveMessages() async {
}
}
func chatRecvMsg() async -> ChatResponse? {
func chatRecvMsg() async -> NSEChatResponse? {
await withCheckedContinuation { cont in
let resp = recvSimpleXMsg()
let resp: NSEChatResponse? = recvSimpleXMsg()
cont.resume(returning: resp)
}
}
@ -799,7 +799,7 @@ func chatRecvMsg() async -> ChatResponse? {
private let isInChina = SKStorefront().countryCode == "CHN"
private func useCallKit() -> Bool { !isInChina && callKitEnabledGroupDefault.get() }
func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotificationData)? {
func receivedMsgNtf(_ res: NSEChatResponse) async -> (String, NSENotificationData)? {
logger.debug("NotificationService receivedMsgNtf: \(res.responseType)")
switch res {
case let .contactConnected(user, contact, _):
@ -868,7 +868,7 @@ func updateNetCfg() {
}
func apiGetActiveUser() -> User? {
let r = sendSimpleXCmd(.showActiveUser)
let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.showActiveUser)
logger.debug("apiGetActiveUser sendSimpleXCmd response: \(r.responseType)")
switch r {
case let .activeUser(user): return user
@ -885,7 +885,7 @@ func apiGetActiveUser() -> User? {
}
func apiStartChat() throws -> Bool {
let r = sendSimpleXCmd(.startChat(mainApp: false, enableSndFiles: false))
let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.startChat(mainApp: false, enableSndFiles: false))
switch r {
case .chatStarted: return true
case .chatRunning: return false
@ -895,27 +895,27 @@ func apiStartChat() throws -> Bool {
func apiActivateChat() -> Bool {
chatReopenStore()
let r = sendSimpleXCmd(.apiActivateChat(restoreChat: false))
let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.apiActivateChat(restoreChat: false))
if case .cmdOk = r { return true }
logger.error("NotificationService apiActivateChat error: \(String(describing: r))")
return false
}
func apiSuspendChat(timeoutMicroseconds: Int) -> Bool {
let r = sendSimpleXCmd(.apiSuspendChat(timeoutMicroseconds: timeoutMicroseconds))
let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.apiSuspendChat(timeoutMicroseconds: timeoutMicroseconds))
if case .cmdOk = r { return true }
logger.error("NotificationService apiSuspendChat error: \(String(describing: r))")
return false
}
func apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String) throws {
let r = sendSimpleXCmd(.apiSetAppFilePaths(filesFolder: filesFolder, tempFolder: tempFolder, assetsFolder: assetsFolder))
let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.apiSetAppFilePaths(filesFolder: filesFolder, tempFolder: tempFolder, assetsFolder: assetsFolder))
if case .cmdOk = r { return }
throw r
}
func apiSetEncryptLocalFiles(_ enable: Bool) throws {
let r = sendSimpleXCmd(.apiSetEncryptLocalFiles(enable: enable))
let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.apiSetEncryptLocalFiles(enable: enable))
if case .cmdOk = r { return }
throw r
}
@ -925,7 +925,7 @@ func apiGetNtfConns(nonce: String, encNtfInfo: String) -> [NtfConn]? {
logger.debug("no active user")
return nil
}
let r = sendSimpleXCmd(.apiGetNtfConns(nonce: nonce, encNtfInfo: encNtfInfo))
let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.apiGetNtfConns(nonce: nonce, encNtfInfo: encNtfInfo))
if case let .ntfConns(ntfConns) = r {
logger.debug("apiGetNtfConns response ntfConns: \(ntfConns.count)")
return ntfConns
@ -942,8 +942,8 @@ func apiGetConnNtfMessages(connMsgReqs: [ConnMsgReq]) -> [NtfMsgInfo?]? {
logger.debug("no active user")
return nil
}
logger.debug("apiGetConnNtfMessages command: \(ChatCommand.apiGetConnNtfMessages(connMsgReqs: connMsgReqs).cmdString)")
let r = sendSimpleXCmd(.apiGetConnNtfMessages(connMsgReqs: connMsgReqs))
logger.debug("apiGetConnNtfMessages command: \(NSEChatCommand.apiGetConnNtfMessages(connMsgReqs: connMsgReqs).cmdString)")
let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.apiGetConnNtfMessages(connMsgReqs: connMsgReqs))
if case let .connNtfMessages(receivedMsgs) = r {
logger.debug("apiGetConnNtfMessages response receivedMsgs: total \(receivedMsgs.count), expecting messages \(receivedMsgs.count { $0 != nil })")
return receivedMsgs
@ -962,7 +962,7 @@ func getConnNtfMessage(connMsgReq: ConnMsgReq) -> NtfMsgInfo? {
func apiReceiveFile(fileId: Int64, encrypted: Bool, inline: Bool? = nil) -> AChatItem? {
let userApprovedRelays = !privacyAskToApproveRelaysGroupDefault.get()
let r = sendSimpleXCmd(.receiveFile(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted, inline: inline))
let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.receiveFile(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted, inline: inline))
if case let .rcvFileAccepted(_, chatItem) = r { return chatItem }
logger.error("receiveFile error: \(responseError(r))")
return nil
@ -970,7 +970,7 @@ func apiReceiveFile(fileId: Int64, encrypted: Bool, inline: Bool? = nil) -> ACha
func apiSetFileToReceive(fileId: Int64, encrypted: Bool) {
let userApprovedRelays = !privacyAskToApproveRelaysGroupDefault.get()
let r = sendSimpleXCmd(.setFileToReceive(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted))
let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.setFileToReceive(fileId: fileId, userApprovedRelays: userApprovedRelays, encrypted: encrypted))
if case .cmdOk = r { return }
logger.error("setFileToReceive error: \(responseError(r))")
}
@ -989,7 +989,7 @@ func autoReceiveFile(_ file: CIFile) -> ChatItem? {
}
func setNetworkConfig(_ cfg: NetCfg) throws {
let r = sendSimpleXCmd(.apiSetNetworkConfig(networkConfig: cfg))
let r: NSEChatResponse = sendSimpleXCmd(NSEChatCommand.apiSetNetworkConfig(networkConfig: cfg))
if case .cmdOk = r { return }
throw r
}

View file

@ -13,7 +13,7 @@ import SimpleXChat
let logger = Logger()
func apiGetActiveUser() throws -> User? {
let r = sendSimpleXCmd(.showActiveUser)
let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.showActiveUser)
switch r {
case let .activeUser(user): return user
case .chatCmdError(_, .error(.noActiveUser)): return nil
@ -22,7 +22,7 @@ func apiGetActiveUser() throws -> User? {
}
func apiStartChat() throws -> Bool {
let r = sendSimpleXCmd(.startChat(mainApp: false, enableSndFiles: true))
let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.startChat(mainApp: false, enableSndFiles: true))
switch r {
case .chatStarted: return true
case .chatRunning: return false
@ -31,25 +31,25 @@ func apiStartChat() throws -> Bool {
}
func apiSetNetworkConfig(_ cfg: NetCfg) throws {
let r = sendSimpleXCmd(.apiSetNetworkConfig(networkConfig: cfg))
let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.apiSetNetworkConfig(networkConfig: cfg))
if case .cmdOk = r { return }
throw r
}
func apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String) throws {
let r = sendSimpleXCmd(.apiSetAppFilePaths(filesFolder: filesFolder, tempFolder: tempFolder, assetsFolder: assetsFolder))
let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.apiSetAppFilePaths(filesFolder: filesFolder, tempFolder: tempFolder, assetsFolder: assetsFolder))
if case .cmdOk = r { return }
throw r
}
func apiSetEncryptLocalFiles(_ enable: Bool) throws {
let r = sendSimpleXCmd(.apiSetEncryptLocalFiles(enable: enable))
let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.apiSetEncryptLocalFiles(enable: enable))
if case .cmdOk = r { return }
throw r
}
func apiGetChats(userId: User.ID) throws -> Array<ChatData> {
let r = sendSimpleXCmd(.apiGetChats(userId: userId))
let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.apiGetChats(userId: userId))
if case let .apiChats(user: _, chats: chats) = r { return chats }
throw r
}
@ -58,13 +58,13 @@ func apiSendMessages(
chatInfo: ChatInfo,
composedMessages: [ComposedMessage]
) throws -> [AChatItem] {
let r = sendSimpleXCmd(
let r: SEChatResponse = sendSimpleXCmd(
chatInfo.chatType == .local
? .apiCreateChatItems(
? SEChatCommand.apiCreateChatItems(
noteFolderId: chatInfo.apiId,
composedMessages: composedMessages
)
: .apiSendMessages(
: SEChatCommand.apiSendMessages(
type: chatInfo.chatType,
id: chatInfo.apiId,
live: false,
@ -84,19 +84,20 @@ func apiSendMessages(
func apiActivateChat() throws {
chatReopenStore()
let r = sendSimpleXCmd(.apiActivateChat(restoreChat: false))
let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.apiActivateChat(restoreChat: false))
if case .cmdOk = r { return }
throw r
}
func apiSuspendChat(expired: Bool) {
let r = sendSimpleXCmd(.apiSuspendChat(timeoutMicroseconds: expired ? 0 : 3_000000))
let r: SEChatResponse = sendSimpleXCmd(SEChatCommand.apiSuspendChat(timeoutMicroseconds: expired ? 0 : 3_000000))
// Block until `chatSuspended` received or 3 seconds has passed
var suspended = false
if case .cmdOk = r, !expired {
let startTime = CFAbsoluteTimeGetCurrent()
while CFAbsoluteTimeGetCurrent() - startTime < 3 {
switch recvSimpleXMsg(messageTimeout: 3_500000) {
let msg: SEChatResponse? = recvSimpleXMsg(messageTimeout: 3_500000)
switch msg {
case .chatSuspended:
suspended = false
break
@ -105,9 +106,166 @@ func apiSuspendChat(expired: Bool) {
}
}
if !suspended {
_ = sendSimpleXCmd(.apiSuspendChat(timeoutMicroseconds: 0))
let _r1: SEChatResponse = sendSimpleXCmd(SEChatCommand.apiSuspendChat(timeoutMicroseconds: 0))
}
logger.debug("close store")
chatCloseStore()
SEChatState.shared.set(.inactive)
}
enum SEChatCommand: ChatCmdProtocol {
case showActiveUser
case startChat(mainApp: Bool, enableSndFiles: Bool)
case apiActivateChat(restoreChat: Bool)
case apiSuspendChat(timeoutMicroseconds: Int)
case apiSetNetworkConfig(networkConfig: NetCfg)
case apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String)
case apiSetEncryptLocalFiles(enable: Bool)
case apiGetChats(userId: Int64)
case apiCreateChatItems(noteFolderId: Int64, composedMessages: [ComposedMessage])
case apiSendMessages(type: ChatType, id: Int64, live: Bool, ttl: Int?, composedMessages: [ComposedMessage])
var cmdString: String {
switch self {
case .showActiveUser: return "/u"
case let .startChat(mainApp, enableSndFiles): return "/_start main=\(onOff(mainApp)) snd_files=\(onOff(enableSndFiles))"
case let .apiActivateChat(restore): return "/_app activate restore=\(onOff(restore))"
case let .apiSuspendChat(timeoutMicroseconds): return "/_app suspend \(timeoutMicroseconds)"
case let .apiSetNetworkConfig(networkConfig): return "/_network \(encodeJSON(networkConfig))"
case let .apiSetAppFilePaths(filesFolder, tempFolder, assetsFolder):
return "/set file paths \(encodeJSON(AppFilePaths(appFilesFolder: filesFolder, appTempFolder: tempFolder, appAssetsFolder: assetsFolder)))"
case let .apiSetEncryptLocalFiles(enable): return "/_files_encrypt \(onOff(enable))"
case let .apiGetChats(userId): return "/_get chats \(userId) pcc=on"
case let .apiCreateChatItems(noteFolderId, composedMessages):
let msgs = encodeJSON(composedMessages)
return "/_create *\(noteFolderId) json \(msgs)"
case let .apiSendMessages(type, id, live, ttl, composedMessages):
let msgs = encodeJSON(composedMessages)
let ttlStr = ttl != nil ? "\(ttl!)" : "default"
return "/_send \(ref(type, id)) live=\(onOff(live)) ttl=\(ttlStr) json \(msgs)"
}
}
func ref(_ type: ChatType, _ id: Int64) -> String {
"\(type.rawValue)\(id)"
}
}
enum SEChatResponse: Decodable, Error, ChatRespProtocol {
case response(type: String, json: String)
case activeUser(user: User)
case chatStarted
case chatRunning
case chatSuspended
case apiChats(user: UserRef, chats: [ChatData])
case newChatItems(user: UserRef, chatItems: [AChatItem])
case sndFileProgressXFTP(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, sentSize: Int64, totalSize: Int64)
case sndFileCompleteXFTP(user: UserRef, chatItem: AChatItem, fileTransferMeta: FileTransferMeta)
case chatItemsStatusesUpdated(user: UserRef, chatItems: [AChatItem])
case sndFileError(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String)
case sndFileWarning(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String)
case cmdOk(user_: UserRef?)
case chatCmdError(user_: UserRef?, chatError: ChatError)
case chatError(user_: UserRef?, chatError: ChatError)
var responseType: String {
switch self {
case let .response(type, _): "* \(type)"
case .activeUser: "activeUser"
case .chatStarted: "chatStarted"
case .chatRunning: "chatRunning"
case .chatSuspended: "chatSuspended"
case .apiChats: "apiChats"
case .newChatItems: "newChatItems"
case .sndFileProgressXFTP: "sndFileProgressXFTP"
case .sndFileCompleteXFTP: "sndFileCompleteXFTP"
case .chatItemsStatusesUpdated: "chatItemsStatusesUpdated"
case .sndFileError: "sndFileError"
case .sndFileWarning: "sndFileWarning"
case .cmdOk: "cmdOk"
case .chatCmdError: "chatCmdError"
case .chatError: "chatError"
}
}
var details: String {
switch self {
case let .response(_, json): return json
case let .activeUser(user): return String(describing: user)
case .chatStarted: return noDetails
case .chatRunning: return noDetails
case .chatSuspended: return noDetails
case let .apiChats(u, chats): return withUser(u, String(describing: chats))
case let .newChatItems(u, chatItems):
let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n")
return withUser(u, itemsString)
case let .sndFileProgressXFTP(u, chatItem, _, sentSize, totalSize): return withUser(u, "chatItem: \(String(describing: chatItem))\nsentSize: \(sentSize)\ntotalSize: \(totalSize)")
case let .sndFileCompleteXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem))
case let .chatItemsStatusesUpdated(u, chatItems):
let itemsString = chatItems.map { chatItem in String(describing: chatItem) }.joined(separator: "\n")
return withUser(u, itemsString)
case let .sndFileError(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))")
case let .sndFileWarning(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))")
case .cmdOk: return noDetails
case let .chatCmdError(u, chatError): return withUser(u, String(describing: chatError))
case let .chatError(u, chatError): return withUser(u, String(describing: chatError))
}
}
var noDetails: String { "\(responseType): no details" }
static func chatResponse(_ s: String) -> SEChatResponse {
let d = s.data(using: .utf8)!
// TODO is there a way to do it without copying the data? e.g:
// let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson))
// let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free)
do {
let r = try jsonDecoder.decode(APIResponse<SEChatResponse>.self, from: d)
return r.resp
} catch {
logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)")
}
var type: String?
var json: String?
if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary {
if let jResp = j["resp"] as? NSDictionary, jResp.count == 1 || jResp.count == 2 {
type = jResp.allKeys[0] as? String
if jResp.count == 2 && type == "_owsf" {
type = jResp.allKeys[1] as? String
}
if type == "apiChats" {
if let r = parseApiChats(jResp) {
return .apiChats(user: r.user, chats: r.chats)
}
} else if type == "chatCmdError" {
if let jError = jResp["chatCmdError"] as? NSDictionary {
return .chatCmdError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? ""))
}
} else if type == "chatError" {
if let jError = jResp["chatError"] as? NSDictionary {
return .chatError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? ""))
}
}
}
json = serializeJSON(j, options: .prettyPrinted)
}
return SEChatResponse.response(type: type ?? "invalid", json: json ?? s)
}
var chatError: ChatError? {
switch self {
case let .chatCmdError(_, error): error
case let .chatError(_, error): error
default: nil
}
}
var chatErrorType: ChatErrorType? {
switch self {
case let .chatCmdError(_, .error(error)): error
case let .chatError(_, .error(error)): error
default: nil
}
}
}

View file

@ -303,7 +303,8 @@ class ShareModel: ObservableObject {
}
}
}
switch recvSimpleXMsg(messageTimeout: 1_000_000) {
let r: SEChatResponse? = recvSimpleXMsg(messageTimeout: 1_000_000)
switch r {
case let .sndFileProgressXFTP(_, ci, _, sentSize, totalSize):
guard isMessage(for: ci) else { continue }
networkTimeout = CFAbsoluteTimeGetCurrent()

View file

@ -242,6 +242,8 @@
E5DCF9712C590272007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF96F2C590272007928CC /* Localizable.strings */; };
E5DCF9842C5902CE007928CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9822C5902CE007928CC /* Localizable.strings */; };
E5DCF9982C5906FF007928CC /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = E5DCF9962C5906FF007928CC /* InfoPlist.strings */; };
E5DDBE6E2DC4106800A0EFF0 /* AppAPITypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5DDBE6D2DC4106200A0EFF0 /* AppAPITypes.swift */; };
E5DDBE702DC4217900A0EFF0 /* NSEAPITypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = E5DDBE6F2DC4217900A0EFF0 /* NSEAPITypes.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -643,6 +645,8 @@
E5DCF9A62C590731007928CC /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/InfoPlist.strings; sourceTree = "<group>"; };
E5DCF9A72C590732007928CC /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/InfoPlist.strings; sourceTree = "<group>"; };
E5DCF9A82C590732007928CC /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/InfoPlist.strings; sourceTree = "<group>"; };
E5DDBE6D2DC4106200A0EFF0 /* AppAPITypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppAPITypes.swift; sourceTree = "<group>"; };
E5DDBE6F2DC4217900A0EFF0 /* NSEAPITypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSEAPITypes.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -795,6 +799,7 @@
5C764E87279CBC8E000C6508 /* Model */ = {
isa = PBXGroup;
children = (
E5DDBE6D2DC4106200A0EFF0 /* AppAPITypes.swift */,
5C764E88279CBCB3000C6508 /* ChatModel.swift */,
5C2E260627A2941F00F70299 /* SimpleXAPI.swift */,
5C35CFC727B2782E00FB6C6D /* BGManager.swift */,
@ -990,6 +995,7 @@
isa = PBXGroup;
children = (
5CDCAD5128186DE400503DA2 /* SimpleX NSE.entitlements */,
E5DDBE6F2DC4217900A0EFF0 /* NSEAPITypes.swift */,
5CDCAD472818589900503DA2 /* NotificationService.swift */,
5CDCAD492818589900503DA2 /* Info.plist */,
5CB0BA862826CB3A00B3292C /* InfoPlist.strings */,
@ -1006,9 +1012,9 @@
5CDCAD7228188CFF00503DA2 /* ChatTypes.swift */,
5CDCAD7428188D2900503DA2 /* APITypes.swift */,
5C5E5D3C282447AB00B0488A /* CallTypes.swift */,
5CDCAD7D2818941F00503DA2 /* API.swift */,
CE3097FA2C4C0C9F00180898 /* ErrorAlert.swift */,
5C9FD96A27A56D4D0075386C /* JSON.swift */,
5CDCAD7D2818941F00503DA2 /* API.swift */,
5CDCAD80281A7E2700503DA2 /* Notifications.swift */,
5CBD2859295711D700EC2CF4 /* ImageUtils.swift */,
CE2AD9CD2C452A4D00E844E3 /* ChatUtils.swift */,
@ -1534,6 +1540,7 @@
5CA059EB279559F40002BEB4 /* SimpleXApp.swift in Sources */,
64D0C2C029F9688300B38D5F /* UserAddressView.swift in Sources */,
6448BBB628FA9D56000D2AB9 /* GroupLinkView.swift in Sources */,
E5DDBE6E2DC4106800A0EFF0 /* AppAPITypes.swift in Sources */,
8C9BC2652C240D5200875A27 /* ThemeModeEditor.swift in Sources */,
5CB346E92869E8BA001FD2EF /* PushEnvironment.swift in Sources */,
5C55A91F283AD0E400C4E99E /* CallManager.swift in Sources */,
@ -1606,6 +1613,7 @@
buildActionMask = 2147483647;
files = (
5CDCAD482818589900503DA2 /* NotificationService.swift in Sources */,
E5DDBE702DC4217900A0EFF0 /* NSEAPITypes.swift in Sources */,
5CFE0922282EEAF60002594B /* ZoomableScrollView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

View file

@ -110,19 +110,19 @@ public func resetChatCtrl() {
migrationResult = nil
}
public func sendSimpleXCmd(_ cmd: ChatCommand, _ ctrl: chat_ctrl? = nil) -> ChatResponse {
public func sendSimpleXCmd<CR: ChatRespProtocol>(_ cmd: ChatCmdProtocol, _ ctrl: chat_ctrl? = nil) -> CR {
var c = cmd.cmdString.cString(using: .utf8)!
let cjson = chat_send_cmd(ctrl ?? getChatCtrl(), &c)!
return chatResponse(fromCString(cjson))
return CR.chatResponse(fromCString(cjson))
}
// in microseconds
public let MESSAGE_TIMEOUT: Int32 = 15_000_000
public func recvSimpleXMsg(_ ctrl: chat_ctrl? = nil, messageTimeout: Int32 = MESSAGE_TIMEOUT) -> ChatResponse? {
public func recvSimpleXMsg<CR: ChatRespProtocol>(_ ctrl: chat_ctrl? = nil, messageTimeout: Int32 = MESSAGE_TIMEOUT) -> CR? {
if let cjson = chat_recv_msg_wait(ctrl ?? getChatCtrl(), messageTimeout) {
let s = fromCString(cjson)
return s == "" ? nil : chatResponse(s)
return s == "" ? nil : CR.chatResponse(s)
}
return nil
}
@ -177,89 +177,7 @@ public func fromCString(_ c: UnsafeMutablePointer<CChar>) -> String {
return s
}
public func chatResponse(_ s: String) -> ChatResponse {
let d = s.data(using: .utf8)!
// TODO is there a way to do it without copying the data? e.g:
// let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson))
// let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free)
do {
let r = try callWithLargeStack {
try jsonDecoder.decode(APIResponse.self, from: d)
}
return r.resp
} catch {
logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)")
}
var type: String?
var json: String?
if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary {
if let jResp = j["resp"] as? NSDictionary, jResp.count == 1 || jResp.count == 2 {
type = jResp.allKeys[0] as? String
if jResp.count == 2 && type == "_owsf" {
type = jResp.allKeys[1] as? String
}
if type == "apiChats" {
if let jApiChats = jResp["apiChats"] as? NSDictionary,
let user: UserRef = try? decodeObject(jApiChats["user"] as Any),
let jChats = jApiChats["chats"] as? NSArray {
let chats = jChats.map { jChat in
if let chatData = try? parseChatData(jChat) {
return chatData.0
}
return ChatData.invalidJSON(serializeJSON(jChat, options: .prettyPrinted) ?? "")
}
return .apiChats(user: user, chats: chats)
}
} else if type == "apiChat" {
if let jApiChat = jResp["apiChat"] as? NSDictionary,
let user: UserRef = try? decodeObject(jApiChat["user"] as Any),
let jChat = jApiChat["chat"] as? NSDictionary,
let (chat, navInfo) = try? parseChatData(jChat, jApiChat["navInfo"] as? NSDictionary) {
return .apiChat(user: user, chat: chat, navInfo: navInfo)
}
} else if type == "chatCmdError" {
if let jError = jResp["chatCmdError"] as? NSDictionary {
return .chatCmdError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? ""))
}
} else if type == "chatError" {
if let jError = jResp["chatError"] as? NSDictionary {
return .chatError(user_: decodeUser_(jError), chatError: .invalidJSON(json: errorJson(jError) ?? ""))
}
}
}
json = serializeJSON(j, options: .prettyPrinted)
}
return ChatResponse.response(type: type ?? "invalid", json: json ?? s)
}
private let largeStackSize: Int = 2 * 1024 * 1024
private func callWithLargeStack<T>(_ f: @escaping () throws -> T) throws -> T {
let semaphore = DispatchSemaphore(value: 0)
var result: Result<T, Error>?
let thread = Thread {
do {
result = .success(try f())
} catch {
result = .failure(error)
}
semaphore.signal()
}
thread.stackSize = largeStackSize
thread.qualityOfService = Thread.current.qualityOfService
thread.start()
semaphore.wait()
switch result! {
case let .success(r): return r
case let .failure(e): throw e
}
}
private func decodeUser_(_ jDict: NSDictionary) -> UserRef? {
public func decodeUser_(_ jDict: NSDictionary) -> UserRef? {
if let user_ = jDict["user_"] {
try? decodeObject(user_ as Any)
} else {
@ -267,7 +185,7 @@ private func decodeUser_(_ jDict: NSDictionary) -> UserRef? {
}
}
private func errorJson(_ jDict: NSDictionary) -> String? {
public func errorJson(_ jDict: NSDictionary) -> String? {
if let chatError = jDict["chatError"] {
serializeJSON(chatError)
} else {
@ -275,7 +193,7 @@ private func errorJson(_ jDict: NSDictionary) -> String? {
}
}
func parseChatData(_ jChat: Any, _ jNavInfo: Any? = nil) throws -> (ChatData, NavigationInfo) {
public func parseChatData(_ jChat: Any, _ jNavInfo: Any? = nil) throws -> (ChatData, NavigationInfo) {
let jChatDict = jChat as! NSDictionary
let chatInfo: ChatInfo = try decodeObject(jChatDict["chatInfo"]!)
let chatStats: ChatStats = try decodeObject(jChatDict["chatStats"]!)
@ -294,7 +212,7 @@ func parseChatData(_ jChat: Any, _ jNavInfo: Any? = nil) throws -> (ChatData, Na
return (ChatData(chatInfo: chatInfo, chatItems: chatItems, chatStats: chatStats), navInfo)
}
func decodeObject<T: Decodable>(_ obj: Any) throws -> T {
public func decodeObject<T: Decodable>(_ obj: Any) throws -> T {
try jsonDecoder.decode(T.self, from: JSONSerialization.data(withJSONObject: obj))
}
@ -305,7 +223,7 @@ func decodeProperty<T: Decodable>(_ obj: Any, _ prop: NSString) -> T? {
return nil
}
func serializeJSON(_ obj: Any, options: JSONSerialization.WritingOptions = []) -> String? {
public func serializeJSON(_ obj: Any, options: JSONSerialization.WritingOptions = []) -> String? {
if let d = try? JSONSerialization.data(withJSONObject: obj, options: options) {
return String(decoding: d, as: UTF8.self)
}
@ -313,14 +231,14 @@ func serializeJSON(_ obj: Any, options: JSONSerialization.WritingOptions = []) -
}
public func responseError(_ err: Error) -> String {
if let r = err as? ChatResponse {
switch r {
case let .chatCmdError(_, chatError): return chatErrorString(chatError)
case let .chatError(_, chatError): return chatErrorString(chatError)
default: return "\(String(describing: r.responseType)), details: \(String(describing: r.details))"
if let r = err as? ChatRespProtocol {
if let e = r.chatError {
chatErrorString(e)
} else {
"\(String(describing: r.responseType)), details: \(String(describing: r.details))"
}
} else {
return String(describing: err)
String(describing: err)
}
}

File diff suppressed because it is too large Load diff

View file

@ -3935,7 +3935,7 @@ public enum MsgContent: Equatable, Hashable {
}
}
var cmdString: String {
public var cmdString: String {
"json \(encodeJSON(self))"
}

View file

@ -37,7 +37,7 @@ public struct ErrorAlert: Error {
}
public init(_ error: any Error) {
self = if let chatResponse = error as? ChatResponse {
self = if let chatResponse = error as? ChatRespProtocol {
ErrorAlert(chatResponse)
} else {
ErrorAlert("\(error.localizedDescription)")
@ -48,7 +48,7 @@ public struct ErrorAlert: Error {
self = ErrorAlert("\(chatErrorString(chatError))")
}
public init(_ chatResponse: ChatResponse) {
public init(_ chatResponse: ChatRespProtocol) {
self = if let networkErrorAlert = getNetworkErrorAlert(chatResponse) {
networkErrorAlert
} else {
@ -94,22 +94,21 @@ extension View {
}
}
public func getNetworkErrorAlert(_ r: ChatResponse) -> ErrorAlert? {
switch r {
case let .chatCmdError(_, .errorAgent(.BROKER(addr, .TIMEOUT))):
return ErrorAlert(title: "Connection timeout", message: "Please check your network connection with \(serverHostname(addr)) and try again.")
case let .chatCmdError(_, .errorAgent(.BROKER(addr, .NETWORK))):
return ErrorAlert(title: "Connection error", message: "Please check your network connection with \(serverHostname(addr)) and try again.")
case let .chatCmdError(_, .errorAgent(.BROKER(addr, .HOST))):
return ErrorAlert(title: "Connection error", message: "Server address is incompatible with network settings: \(serverHostname(addr)).")
case let .chatCmdError(_, .errorAgent(.BROKER(addr, .TRANSPORT(.version)))):
return ErrorAlert(title: "Connection error", message: "Server version is incompatible with your app: \(serverHostname(addr)).")
case let .chatCmdError(_, .errorAgent(.SMP(serverAddress, .PROXY(proxyErr)))):
return smpProxyErrorAlert(proxyErr, serverAddress)
case let .chatCmdError(_, .errorAgent(.PROXY(proxyServer, relayServer, .protocolError(.PROXY(proxyErr))))):
return proxyDestinationErrorAlert(proxyErr, proxyServer, relayServer)
default:
return nil
public func getNetworkErrorAlert(_ r: ChatRespProtocol) -> ErrorAlert? {
switch r.chatError {
case let .errorAgent(.BROKER(addr, .TIMEOUT)):
ErrorAlert(title: "Connection timeout", message: "Please check your network connection with \(serverHostname(addr)) and try again.")
case let .errorAgent(.BROKER(addr, .NETWORK)):
ErrorAlert(title: "Connection error", message: "Please check your network connection with \(serverHostname(addr)) and try again.")
case let .errorAgent(.BROKER(addr, .HOST)):
ErrorAlert(title: "Connection error", message: "Server address is incompatible with network settings: \(serverHostname(addr)).")
case let .errorAgent(.BROKER(addr, .TRANSPORT(.version))):
ErrorAlert(title: "Connection error", message: "Server version is incompatible with your app: \(serverHostname(addr)).")
case let .errorAgent(.SMP(serverAddress, .PROXY(proxyErr))):
smpProxyErrorAlert(proxyErr, serverAddress)
case let .errorAgent(.PROXY(proxyServer, relayServer, .protocolError(.PROXY(proxyErr)))):
proxyDestinationErrorAlert(proxyErr, proxyServer, relayServer)
default: nil
}
}