mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2025-06-28 12:19:54 +00:00
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:
parent
e7a4611be9
commit
f5c706f2dd
12 changed files with 2775 additions and 2467 deletions
2327
apps/ios/Shared/Model/AppAPITypes.swift
Normal file
2327
apps/ios/Shared/Model/AppAPITypes.swift
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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 []
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
173
apps/ios/SimpleX NSE/NSEAPITypes.swift
Normal file
173
apps/ios/SimpleX NSE/NSEAPITypes.swift
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
@ -3935,7 +3935,7 @@ public enum MsgContent: Equatable, Hashable {
|
|||
}
|
||||
}
|
||||
|
||||
var cmdString: String {
|
||||
public var cmdString: String {
|
||||
"json \(encodeJSON(self))"
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue