2022-01-29 11:10:04 +00:00
|
|
|
//
|
|
|
|
// ChatAPI.swift
|
|
|
|
// SimpleX
|
|
|
|
//
|
|
|
|
// Created by Evgeny Poberezkin on 27/01/2022.
|
|
|
|
// Copyright © 2022 SimpleX Chat. All rights reserved.
|
|
|
|
//
|
|
|
|
|
|
|
|
import Foundation
|
2022-01-29 23:37:02 +00:00
|
|
|
import UIKit
|
2022-02-09 22:53:06 +00:00
|
|
|
import Dispatch
|
|
|
|
import BackgroundTasks
|
2022-01-29 11:10:04 +00:00
|
|
|
|
|
|
|
private var chatController: chat_ctrl?
|
2022-01-29 23:37:02 +00:00
|
|
|
private let jsonDecoder = getJSONDecoder()
|
|
|
|
private let jsonEncoder = getJSONEncoder()
|
2022-01-29 11:10:04 +00:00
|
|
|
|
|
|
|
enum ChatCommand {
|
2022-02-06 16:18:01 +00:00
|
|
|
case showActiveUser
|
|
|
|
case createActiveUser(profile: Profile)
|
|
|
|
case startChat
|
2022-01-29 11:10:04 +00:00
|
|
|
case apiGetChats
|
2022-01-30 18:27:20 +00:00
|
|
|
case apiGetChat(type: ChatType, id: Int64)
|
|
|
|
case apiSendMessage(type: ChatType, id: Int64, msg: MsgContent)
|
2022-03-17 09:42:59 +00:00
|
|
|
case apiSendMessageQuote(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent)
|
2022-03-30 20:37:47 +04:00
|
|
|
case apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent)
|
|
|
|
case apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteMode)
|
2022-03-10 15:45:40 +04:00
|
|
|
case getUserSMPServers
|
|
|
|
case setUserSMPServers(smpServers: [String])
|
2022-01-30 18:27:20 +00:00
|
|
|
case addContact
|
|
|
|
case connect(connReq: String)
|
2022-01-31 21:28:07 +00:00
|
|
|
case apiDeleteChat(type: ChatType, id: Int64)
|
2022-03-25 22:13:01 +04:00
|
|
|
case apiUpdateProfile(profile: Profile)
|
2022-02-01 17:34:06 +00:00
|
|
|
case createMyAddress
|
|
|
|
case deleteMyAddress
|
|
|
|
case showMyAddress
|
|
|
|
case apiAcceptContact(contactReqId: Int64)
|
|
|
|
case apiRejectContact(contactReqId: Int64)
|
2022-02-12 15:59:43 +00:00
|
|
|
case apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64))
|
2022-01-29 11:10:04 +00:00
|
|
|
case string(String)
|
|
|
|
|
|
|
|
var cmdString: String {
|
|
|
|
get {
|
|
|
|
switch self {
|
2022-02-06 16:18:01 +00:00
|
|
|
case .showActiveUser: return "/u"
|
|
|
|
case let .createActiveUser(profile): return "/u \(profile.displayName) \(profile.fullName)"
|
|
|
|
case .startChat: return "/_start"
|
|
|
|
case .apiGetChats: return "/_get chats"
|
2022-02-12 15:59:43 +00:00
|
|
|
case let .apiGetChat(type, id): return "/_get chat \(ref(type, id)) count=100"
|
|
|
|
case let .apiSendMessage(type, id, mc): return "/_send \(ref(type, id)) \(mc.cmdString)"
|
2022-03-17 09:42:59 +00:00
|
|
|
case let .apiSendMessageQuote(type, id, itemId, mc): return "/_send_quote \(ref(type, id)) \(itemId) \(mc.cmdString)"
|
2022-03-30 20:37:47 +04:00
|
|
|
case let .apiUpdateChatItem(type, id, itemId, mc): return "/_update item \(ref(type, id)) \(itemId) \(mc.cmdString)"
|
|
|
|
case let .apiDeleteChatItem(type, id, itemId, mode): return "/_delete item \(ref(type, id)) \(itemId) \(mode.rawValue)"
|
2022-03-10 15:45:40 +04:00
|
|
|
case .getUserSMPServers: return "/smp_servers"
|
|
|
|
case let .setUserSMPServers(smpServers): return "/smp_servers \(smpServersStr(smpServers: smpServers))"
|
2022-02-06 16:18:01 +00:00
|
|
|
case .addContact: return "/connect"
|
|
|
|
case let .connect(connReq): return "/connect \(connReq)"
|
2022-02-12 15:59:43 +00:00
|
|
|
case let .apiDeleteChat(type, id): return "/_delete \(ref(type, id))"
|
2022-03-25 22:13:01 +04:00
|
|
|
case let .apiUpdateProfile(profile): return "/_profile \(encodeJSON(profile))"
|
2022-02-06 16:18:01 +00:00
|
|
|
case .createMyAddress: return "/address"
|
|
|
|
case .deleteMyAddress: return "/delete_address"
|
|
|
|
case .showMyAddress: return "/show_address"
|
|
|
|
case let .apiAcceptContact(contactReqId): return "/_accept \(contactReqId)"
|
|
|
|
case let .apiRejectContact(contactReqId): return "/_reject \(contactReqId)"
|
2022-02-12 15:59:43 +00:00
|
|
|
case let .apiChatRead(type, id, itemRange: (from, to)): return "/_read chat \(ref(type, id)) from=\(from) to=\(to)"
|
2022-02-06 16:18:01 +00:00
|
|
|
case let .string(str): return str
|
2022-01-29 11:10:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-02-12 15:59:43 +00:00
|
|
|
|
|
|
|
var cmdType: String {
|
|
|
|
get {
|
|
|
|
switch self {
|
|
|
|
case .showActiveUser: return "showActiveUser"
|
|
|
|
case .createActiveUser: return "createActiveUser"
|
|
|
|
case .startChat: return "startChat"
|
|
|
|
case .apiGetChats: return "apiGetChats"
|
|
|
|
case .apiGetChat: return "apiGetChat"
|
|
|
|
case .apiSendMessage: return "apiSendMessage"
|
2022-03-17 09:42:59 +00:00
|
|
|
case .apiSendMessageQuote: return "apiSendMessageQuote"
|
2022-03-30 20:37:47 +04:00
|
|
|
case .apiUpdateChatItem: return "apiUpdateChatItem"
|
|
|
|
case .apiDeleteChatItem: return "apiDeleteChatItem"
|
2022-03-10 15:45:40 +04:00
|
|
|
case .getUserSMPServers: return "getUserSMPServers"
|
|
|
|
case .setUserSMPServers: return "setUserSMPServers"
|
2022-02-12 15:59:43 +00:00
|
|
|
case .addContact: return "addContact"
|
|
|
|
case .connect: return "connect"
|
|
|
|
case .apiDeleteChat: return "apiDeleteChat"
|
2022-03-25 22:13:01 +04:00
|
|
|
case .apiUpdateProfile: return "apiUpdateProfile"
|
2022-02-12 15:59:43 +00:00
|
|
|
case .createMyAddress: return "createMyAddress"
|
|
|
|
case .deleteMyAddress: return "deleteMyAddress"
|
|
|
|
case .showMyAddress: return "showMyAddress"
|
|
|
|
case .apiAcceptContact: return "apiAcceptContact"
|
|
|
|
case .apiRejectContact: return "apiRejectContact"
|
|
|
|
case .apiChatRead: return "apiChatRead"
|
|
|
|
case .string: return "console command"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func ref(_ type: ChatType, _ id: Int64) -> String {
|
|
|
|
"\(type.rawValue)\(id)"
|
|
|
|
}
|
2022-03-10 15:45:40 +04:00
|
|
|
|
|
|
|
func smpServersStr(smpServers: [String]) -> String {
|
|
|
|
smpServers.isEmpty ? "default" : smpServers.joined(separator: ",")
|
|
|
|
}
|
2022-01-29 11:10:04 +00:00
|
|
|
}
|
|
|
|
|
2022-01-29 23:37:02 +00:00
|
|
|
struct APIResponse: Decodable {
|
2022-01-29 11:10:04 +00:00
|
|
|
var resp: ChatResponse
|
|
|
|
}
|
|
|
|
|
2022-01-29 23:37:02 +00:00
|
|
|
enum ChatResponse: Decodable, Error {
|
2022-01-29 11:10:04 +00:00
|
|
|
case response(type: String, json: String)
|
2022-02-06 16:18:01 +00:00
|
|
|
case activeUser(user: User)
|
|
|
|
case chatStarted
|
2022-02-26 20:21:32 +00:00
|
|
|
case chatRunning
|
2022-02-02 12:51:39 +00:00
|
|
|
case apiChats(chats: [ChatData])
|
|
|
|
case apiChat(chat: ChatData)
|
2022-03-10 15:45:40 +04:00
|
|
|
case userSMPServers(smpServers: [String])
|
2022-01-30 18:27:20 +00:00
|
|
|
case invitation(connReqInvitation: String)
|
|
|
|
case sentConfirmation
|
|
|
|
case sentInvitation
|
2022-01-31 21:28:07 +00:00
|
|
|
case contactDeleted(contact: Contact)
|
|
|
|
case userProfileNoChange
|
|
|
|
case userProfileUpdated(fromProfile: Profile, toProfile: Profile)
|
2022-02-01 17:34:06 +00:00
|
|
|
case userContactLink(connReqContact: String)
|
|
|
|
case userContactLinkCreated(connReqContact: String)
|
|
|
|
case userContactLinkDeleted
|
2022-01-29 11:10:04 +00:00
|
|
|
case contactConnected(contact: Contact)
|
2022-02-01 17:34:06 +00:00
|
|
|
case receivedContactRequest(contactRequest: UserContactRequest)
|
|
|
|
case acceptingContactRequest(contact: Contact)
|
|
|
|
case contactRequestRejected
|
2022-02-04 22:13:52 +00:00
|
|
|
case contactUpdated(toContact: Contact)
|
2022-02-05 20:10:47 +00:00
|
|
|
case contactSubscribed(contact: Contact)
|
|
|
|
case contactDisconnected(contact: Contact)
|
|
|
|
case contactSubError(contact: Contact, chatError: ChatError)
|
2022-02-25 20:26:56 +00:00
|
|
|
case contactSubSummary(contactSubscriptions: [ContactSubStatus])
|
2022-02-09 22:53:06 +00:00
|
|
|
case groupSubscribed(groupInfo: GroupInfo)
|
2022-02-25 20:26:56 +00:00
|
|
|
case memberSubErrors(memberSubErrors: [MemberSubError])
|
2022-02-09 22:53:06 +00:00
|
|
|
case groupEmpty(groupInfo: GroupInfo)
|
|
|
|
case userContactLinkSubscribed
|
2022-01-29 23:37:02 +00:00
|
|
|
case newChatItem(chatItem: AChatItem)
|
2022-03-25 22:26:05 +04:00
|
|
|
case chatItemStatusUpdated(chatItem: AChatItem)
|
2022-02-12 15:59:43 +00:00
|
|
|
case chatItemUpdated(chatItem: AChatItem)
|
2022-03-30 20:37:47 +04:00
|
|
|
case chatItemDeleted(deletedChatItem: AChatItem, toChatItem: AChatItem)
|
2022-02-12 15:59:43 +00:00
|
|
|
case cmdOk
|
2022-02-01 17:34:06 +00:00
|
|
|
case chatCmdError(chatError: ChatError)
|
2022-02-06 16:18:01 +00:00
|
|
|
case chatError(chatError: ChatError)
|
2022-01-29 11:10:04 +00:00
|
|
|
|
|
|
|
var responseType: String {
|
|
|
|
get {
|
|
|
|
switch self {
|
|
|
|
case let .response(type, _): return "* \(type)"
|
2022-02-06 16:18:01 +00:00
|
|
|
case .activeUser: return "activeUser"
|
|
|
|
case .chatStarted: return "chatStarted"
|
2022-02-26 20:21:32 +00:00
|
|
|
case .chatRunning: return "chatRunning"
|
2022-01-29 23:37:02 +00:00
|
|
|
case .apiChats: return "apiChats"
|
2022-01-30 18:27:20 +00:00
|
|
|
case .apiChat: return "apiChat"
|
2022-03-10 15:45:40 +04:00
|
|
|
case .userSMPServers: return "userSMPServers"
|
2022-01-30 18:27:20 +00:00
|
|
|
case .invitation: return "invitation"
|
|
|
|
case .sentConfirmation: return "sentConfirmation"
|
|
|
|
case .sentInvitation: return "sentInvitation"
|
2022-01-31 21:28:07 +00:00
|
|
|
case .contactDeleted: return "contactDeleted"
|
|
|
|
case .userProfileNoChange: return "userProfileNoChange"
|
2022-03-25 22:13:01 +04:00
|
|
|
case .userProfileUpdated: return "userProfileUpdated"
|
2022-02-01 17:34:06 +00:00
|
|
|
case .userContactLink: return "userContactLink"
|
|
|
|
case .userContactLinkCreated: return "userContactLinkCreated"
|
|
|
|
case .userContactLinkDeleted: return "userContactLinkDeleted"
|
2022-01-29 23:37:02 +00:00
|
|
|
case .contactConnected: return "contactConnected"
|
2022-02-01 17:34:06 +00:00
|
|
|
case .receivedContactRequest: return "receivedContactRequest"
|
|
|
|
case .acceptingContactRequest: return "acceptingContactRequest"
|
|
|
|
case .contactRequestRejected: return "contactRequestRejected"
|
2022-02-04 22:13:52 +00:00
|
|
|
case .contactUpdated: return "contactUpdated"
|
2022-02-05 20:10:47 +00:00
|
|
|
case .contactSubscribed: return "contactSubscribed"
|
|
|
|
case .contactDisconnected: return "contactDisconnected"
|
|
|
|
case .contactSubError: return "contactSubError"
|
2022-02-25 20:26:56 +00:00
|
|
|
case .contactSubSummary: return "contactSubSummary"
|
2022-02-09 22:53:06 +00:00
|
|
|
case .groupSubscribed: return "groupSubscribed"
|
2022-02-25 20:26:56 +00:00
|
|
|
case .memberSubErrors: return "memberSubErrors"
|
2022-02-09 22:53:06 +00:00
|
|
|
case .groupEmpty: return "groupEmpty"
|
|
|
|
case .userContactLinkSubscribed: return "userContactLinkSubscribed"
|
2022-01-29 23:37:02 +00:00
|
|
|
case .newChatItem: return "newChatItem"
|
2022-03-25 22:26:05 +04:00
|
|
|
case .chatItemStatusUpdated: return "chatItemStatusUpdated"
|
2022-02-12 15:59:43 +00:00
|
|
|
case .chatItemUpdated: return "chatItemUpdated"
|
2022-03-25 22:26:05 +04:00
|
|
|
case .chatItemDeleted: return "chatItemDeleted"
|
2022-02-12 15:59:43 +00:00
|
|
|
case .cmdOk: return "cmdOk"
|
2022-02-01 17:34:06 +00:00
|
|
|
case .chatCmdError: return "chatCmdError"
|
2022-02-06 16:18:01 +00:00
|
|
|
case .chatError: return "chatError"
|
2022-01-29 11:10:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var details: String {
|
|
|
|
get {
|
|
|
|
switch self {
|
|
|
|
case let .response(_, json): return json
|
2022-02-06 16:18:01 +00:00
|
|
|
case let .activeUser(user): return String(describing: user)
|
|
|
|
case .chatStarted: return noDetails
|
2022-02-26 20:21:32 +00:00
|
|
|
case .chatRunning: return noDetails
|
2022-01-29 11:10:04 +00:00
|
|
|
case let .apiChats(chats): return String(describing: chats)
|
2022-01-30 18:27:20 +00:00
|
|
|
case let .apiChat(chat): return String(describing: chat)
|
2022-03-10 15:45:40 +04:00
|
|
|
case let .userSMPServers(smpServers): return String(describing: smpServers)
|
2022-01-30 18:27:20 +00:00
|
|
|
case let .invitation(connReqInvitation): return connReqInvitation
|
2022-02-01 17:34:06 +00:00
|
|
|
case .sentConfirmation: return noDetails
|
|
|
|
case .sentInvitation: return noDetails
|
2022-01-31 21:28:07 +00:00
|
|
|
case let .contactDeleted(contact): return String(describing: contact)
|
2022-02-01 17:34:06 +00:00
|
|
|
case .userProfileNoChange: return noDetails
|
2022-01-31 21:28:07 +00:00
|
|
|
case let .userProfileUpdated(_, toProfile): return String(describing: toProfile)
|
2022-02-01 17:34:06 +00:00
|
|
|
case let .userContactLink(connReq): return connReq
|
|
|
|
case let .userContactLinkCreated(connReq): return connReq
|
|
|
|
case .userContactLinkDeleted: return noDetails
|
2022-01-29 11:10:04 +00:00
|
|
|
case let .contactConnected(contact): return String(describing: contact)
|
2022-02-01 17:34:06 +00:00
|
|
|
case let .receivedContactRequest(contactRequest): return String(describing: contactRequest)
|
|
|
|
case let .acceptingContactRequest(contact): return String(describing: contact)
|
|
|
|
case .contactRequestRejected: return noDetails
|
2022-02-04 22:13:52 +00:00
|
|
|
case let .contactUpdated(toContact): return String(describing: toContact)
|
2022-02-05 20:10:47 +00:00
|
|
|
case let .contactSubscribed(contact): return String(describing: contact)
|
|
|
|
case let .contactDisconnected(contact): return String(describing: contact)
|
|
|
|
case let .contactSubError(contact, chatError): return "contact:\n\(String(describing: contact))\nerror:\n\(String(describing: chatError))"
|
2022-02-25 20:26:56 +00:00
|
|
|
case let .contactSubSummary(contactSubscriptions): return String(describing: contactSubscriptions)
|
2022-02-09 22:53:06 +00:00
|
|
|
case let .groupSubscribed(groupInfo): return String(describing: groupInfo)
|
2022-02-25 20:26:56 +00:00
|
|
|
case let .memberSubErrors(memberSubErrors): return String(describing: memberSubErrors)
|
2022-02-09 22:53:06 +00:00
|
|
|
case let .groupEmpty(groupInfo): return String(describing: groupInfo)
|
|
|
|
case .userContactLinkSubscribed: return noDetails
|
2022-01-29 23:37:02 +00:00
|
|
|
case let .newChatItem(chatItem): return String(describing: chatItem)
|
2022-03-25 22:26:05 +04:00
|
|
|
case let .chatItemStatusUpdated(chatItem): return String(describing: chatItem)
|
2022-02-12 15:59:43 +00:00
|
|
|
case let .chatItemUpdated(chatItem): return String(describing: chatItem)
|
2022-03-30 20:37:47 +04:00
|
|
|
case let .chatItemDeleted(deletedChatItem, toChatItem): return "deletedChatItem:\n\(String(describing: deletedChatItem))\ntoChatItem:\n\(String(describing: toChatItem))"
|
2022-02-12 15:59:43 +00:00
|
|
|
case .cmdOk: return noDetails
|
2022-02-01 17:34:06 +00:00
|
|
|
case let .chatCmdError(chatError): return String(describing: chatError)
|
2022-02-06 16:18:01 +00:00
|
|
|
case let .chatError(chatError): return String(describing: chatError)
|
2022-01-29 23:37:02 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-02-01 17:34:06 +00:00
|
|
|
|
|
|
|
private var noDetails: String { get { "\(responseType): no details" } }
|
2022-01-29 23:37:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
enum TerminalItem: Identifiable {
|
|
|
|
case cmd(Date, ChatCommand)
|
|
|
|
case resp(Date, ChatResponse)
|
|
|
|
|
|
|
|
var id: Date {
|
|
|
|
get {
|
|
|
|
switch self {
|
|
|
|
case let .cmd(id, _): return id
|
|
|
|
case let .resp(id, _): return id
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var label: String {
|
|
|
|
get {
|
|
|
|
switch self {
|
|
|
|
case let .cmd(_, cmd): return "> \(cmd.cmdString.prefix(30))"
|
|
|
|
case let .resp(_, resp): return "< \(resp.responseType)"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var details: String {
|
|
|
|
get {
|
|
|
|
switch self {
|
|
|
|
case let .cmd(_, cmd): return cmd.cmdString
|
|
|
|
case let .resp(_, resp): return resp.details
|
2022-01-29 11:10:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-28 10:44:48 +00:00
|
|
|
private func _sendCmd(_ cmd: ChatCommand) -> ChatResponse {
|
2022-01-29 11:10:04 +00:00
|
|
|
var c = cmd.cmdString.cString(using: .utf8)!
|
2022-02-28 10:44:48 +00:00
|
|
|
return chatResponse(chat_send_cmd(getChatCtrl(), &c))
|
|
|
|
}
|
|
|
|
|
|
|
|
private func beginBGTask(_ handler: (() -> Void)? = nil) -> (() -> Void) {
|
|
|
|
var id: UIBackgroundTaskIdentifier!
|
|
|
|
var running = true
|
|
|
|
let endTask = {
|
|
|
|
// logger.debug("beginBGTask: endTask \(id.rawValue)")
|
|
|
|
if running {
|
|
|
|
running = false
|
|
|
|
if let h = handler {
|
|
|
|
// logger.debug("beginBGTask: user handler")
|
|
|
|
h()
|
|
|
|
}
|
|
|
|
if id != .invalid {
|
|
|
|
UIApplication.shared.endBackgroundTask(id)
|
|
|
|
id = .invalid
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
id = UIApplication.shared.beginBackgroundTask(expirationHandler: endTask)
|
|
|
|
// logger.debug("beginBGTask: \(id.rawValue)")
|
|
|
|
return endTask
|
|
|
|
}
|
|
|
|
|
|
|
|
let msgDelay: Double = 7.5
|
|
|
|
let maxTaskDuration: Double = 15
|
|
|
|
|
|
|
|
private func withBGTask(bgDelay: Double? = nil, f: @escaping () -> ChatResponse) -> ChatResponse {
|
|
|
|
let endTask = beginBGTask()
|
|
|
|
DispatchQueue.global().asyncAfter(deadline: .now() + maxTaskDuration, execute: endTask)
|
|
|
|
let r = f()
|
|
|
|
if let d = bgDelay {
|
|
|
|
DispatchQueue.global().asyncAfter(deadline: .now() + d, execute: endTask)
|
|
|
|
} else {
|
|
|
|
endTask()
|
|
|
|
}
|
|
|
|
return r
|
|
|
|
}
|
|
|
|
|
|
|
|
func chatSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil) -> ChatResponse {
|
2022-02-12 15:59:43 +00:00
|
|
|
logger.debug("chatSendCmd \(cmd.cmdType)")
|
2022-02-28 10:44:48 +00:00
|
|
|
let resp = bgTask
|
|
|
|
? withBGTask(bgDelay: bgDelay) { _sendCmd(cmd) }
|
|
|
|
: _sendCmd(cmd)
|
2022-02-12 15:59:43 +00:00
|
|
|
logger.debug("chatSendCmd \(cmd.cmdType): \(resp.responseType)")
|
2022-02-18 14:33:55 +00:00
|
|
|
if case let .response(_, json) = resp {
|
|
|
|
logger.debug("chatSendCmd \(cmd.cmdType) response: \(json)")
|
|
|
|
}
|
2022-02-09 22:53:06 +00:00
|
|
|
DispatchQueue.main.async {
|
|
|
|
ChatModel.shared.terminalItems.append(.cmd(.now, cmd))
|
|
|
|
ChatModel.shared.terminalItems.append(.resp(.now, resp))
|
|
|
|
}
|
|
|
|
return resp
|
2022-01-29 11:10:04 +00:00
|
|
|
}
|
|
|
|
|
2022-02-28 10:44:48 +00:00
|
|
|
func chatSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil) async -> ChatResponse {
|
2022-02-24 17:16:41 +00:00
|
|
|
await withCheckedContinuation { cont in
|
2022-02-28 10:44:48 +00:00
|
|
|
cont.resume(returning: chatSendCmdSync(cmd, bgTask: bgTask, bgDelay: bgDelay))
|
2022-02-24 17:16:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func chatRecvMsg() async -> ChatResponse {
|
|
|
|
await withCheckedContinuation { cont in
|
2022-02-28 10:44:48 +00:00
|
|
|
_ = withBGTask(bgDelay: msgDelay) {
|
|
|
|
let resp = chatResponse(chat_recv_msg(getChatCtrl())!)
|
|
|
|
cont.resume(returning: resp)
|
|
|
|
return resp
|
|
|
|
}
|
2022-02-24 17:16:41 +00:00
|
|
|
}
|
2022-01-29 11:10:04 +00:00
|
|
|
}
|
|
|
|
|
2022-02-06 18:26:22 +00:00
|
|
|
func apiGetActiveUser() throws -> User? {
|
2022-02-06 21:06:02 +00:00
|
|
|
let _ = getChatCtrl()
|
2022-02-24 17:16:41 +00:00
|
|
|
let r = chatSendCmdSync(.showActiveUser)
|
2022-02-06 18:26:22 +00:00
|
|
|
switch r {
|
|
|
|
case let .activeUser(user): return user
|
|
|
|
case .chatCmdError(.error(.noActiveUser)): return nil
|
|
|
|
default: throw r
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func apiCreateActiveUser(_ p: Profile) throws -> User {
|
2022-02-24 17:16:41 +00:00
|
|
|
let r = chatSendCmdSync(.createActiveUser(profile: p))
|
2022-02-06 18:26:22 +00:00
|
|
|
if case let .activeUser(user) = r { return user }
|
|
|
|
throw r
|
|
|
|
}
|
|
|
|
|
|
|
|
func apiStartChat() throws {
|
2022-02-24 17:16:41 +00:00
|
|
|
let r = chatSendCmdSync(.startChat)
|
2022-02-06 18:26:22 +00:00
|
|
|
if case .chatStarted = r { return }
|
|
|
|
throw r
|
|
|
|
}
|
|
|
|
|
2022-01-31 21:28:07 +00:00
|
|
|
func apiGetChats() throws -> [Chat] {
|
2022-02-24 17:16:41 +00:00
|
|
|
let r = chatSendCmdSync(.apiGetChats)
|
2022-02-02 12:51:39 +00:00
|
|
|
if case let .apiChats(chats) = r { return chats.map { Chat.init($0) } }
|
2022-01-30 18:27:20 +00:00
|
|
|
throw r
|
|
|
|
}
|
|
|
|
|
2022-03-19 17:20:27 +00:00
|
|
|
func apiGetChat(type: ChatType, id: Int64) throws -> Chat {
|
|
|
|
let r = chatSendCmdSync(.apiGetChat(type: type, id: id))
|
2022-02-02 12:51:39 +00:00
|
|
|
if case let .apiChat(chat) = r { return Chat.init(chat) }
|
2022-01-30 18:27:20 +00:00
|
|
|
throw r
|
|
|
|
}
|
|
|
|
|
2022-03-17 09:42:59 +00:00
|
|
|
func apiSendMessage(type: ChatType, id: Int64, quotedItemId: Int64?, msg: MsgContent) async throws -> ChatItem {
|
2022-02-28 10:44:48 +00:00
|
|
|
let chatModel = ChatModel.shared
|
2022-03-17 09:42:59 +00:00
|
|
|
let cmd: ChatCommand
|
|
|
|
if let itemId = quotedItemId {
|
|
|
|
cmd = .apiSendMessageQuote(type: type, id: id, itemId: itemId, msg: msg)
|
|
|
|
} else {
|
|
|
|
cmd = .apiSendMessage(type: type, id: id, msg: msg)
|
|
|
|
}
|
2022-02-28 10:44:48 +00:00
|
|
|
let r: ChatResponse
|
|
|
|
if type == .direct {
|
|
|
|
var cItem: ChatItem!
|
|
|
|
let endTask = beginBGTask({ if cItem != nil { chatModel.messageDelivery.removeValue(forKey: cItem.id) } })
|
|
|
|
r = await chatSendCmd(cmd, bgTask: false)
|
|
|
|
if case let .newChatItem(aChatItem) = r {
|
|
|
|
cItem = aChatItem.chatItem
|
|
|
|
chatModel.messageDelivery[cItem.id] = endTask
|
|
|
|
return cItem
|
|
|
|
}
|
|
|
|
endTask()
|
|
|
|
} else {
|
|
|
|
r = await chatSendCmd(cmd, bgDelay: msgDelay)
|
|
|
|
if case let .newChatItem(aChatItem) = r {
|
|
|
|
return aChatItem.chatItem
|
|
|
|
}
|
|
|
|
}
|
2022-01-30 18:27:20 +00:00
|
|
|
throw r
|
|
|
|
}
|
|
|
|
|
2022-03-30 20:37:47 +04:00
|
|
|
func apiUpdateChatItem(type: ChatType, id: Int64, itemId: Int64, msg: MsgContent) async throws -> ChatItem {
|
|
|
|
let r = await chatSendCmd(.apiUpdateChatItem(type: type, id: id, itemId: itemId, msg: msg), bgDelay: msgDelay)
|
2022-03-25 22:26:05 +04:00
|
|
|
if case let .chatItemUpdated(aChatItem) = r { return aChatItem.chatItem }
|
|
|
|
throw r
|
|
|
|
}
|
|
|
|
|
2022-03-30 20:37:47 +04:00
|
|
|
func apiDeleteChatItem(type: ChatType, id: Int64, itemId: Int64, mode: CIDeleteMode) async throws -> ChatItem {
|
|
|
|
let r = await chatSendCmd(.apiDeleteChatItem(type: type, id: id, itemId: itemId, mode: mode), bgDelay: msgDelay)
|
|
|
|
if case let .chatItemDeleted(_, toChatItem) = r { return toChatItem.chatItem }
|
2022-03-25 22:26:05 +04:00
|
|
|
throw r
|
|
|
|
}
|
|
|
|
|
2022-03-10 15:45:40 +04:00
|
|
|
func getUserSMPServers() throws -> [String] {
|
|
|
|
let r = chatSendCmdSync(.getUserSMPServers)
|
|
|
|
if case let .userSMPServers(smpServers) = r { return smpServers }
|
|
|
|
throw r
|
|
|
|
}
|
|
|
|
|
|
|
|
func setUserSMPServers(smpServers: [String]) async throws {
|
|
|
|
let r = await chatSendCmd(.setUserSMPServers(smpServers: smpServers))
|
|
|
|
if case .cmdOk = r { return }
|
|
|
|
throw r
|
|
|
|
}
|
|
|
|
|
2022-02-26 20:21:32 +00:00
|
|
|
func apiAddContact() throws -> String {
|
2022-02-28 10:44:48 +00:00
|
|
|
let r = chatSendCmdSync(.addContact, bgTask: false)
|
2022-01-30 18:27:20 +00:00
|
|
|
if case let .invitation(connReqInvitation) = r { return connReqInvitation }
|
|
|
|
throw r
|
2022-01-29 23:37:02 +00:00
|
|
|
}
|
|
|
|
|
2022-02-24 17:16:41 +00:00
|
|
|
func apiConnect(connReq: String) async throws {
|
|
|
|
let r = await chatSendCmd(.connect(connReq: connReq))
|
2022-01-29 23:37:02 +00:00
|
|
|
switch r {
|
2022-01-30 18:27:20 +00:00
|
|
|
case .sentConfirmation: return
|
|
|
|
case .sentInvitation: return
|
2022-01-29 23:37:02 +00:00
|
|
|
default: throw r
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-24 17:16:41 +00:00
|
|
|
func apiDeleteChat(type: ChatType, id: Int64) async throws {
|
2022-02-28 10:44:48 +00:00
|
|
|
let r = await chatSendCmd(.apiDeleteChat(type: type, id: id), bgTask: false)
|
2022-01-31 21:28:07 +00:00
|
|
|
if case .contactDeleted = r { return }
|
|
|
|
throw r
|
|
|
|
}
|
|
|
|
|
2022-02-24 17:16:41 +00:00
|
|
|
func apiUpdateProfile(profile: Profile) async throws -> Profile? {
|
2022-03-25 22:13:01 +04:00
|
|
|
let r = await chatSendCmd(.apiUpdateProfile(profile: profile))
|
2022-01-31 21:28:07 +00:00
|
|
|
switch r {
|
|
|
|
case .userProfileNoChange: return nil
|
|
|
|
case let .userProfileUpdated(_, toProfile): return toProfile
|
|
|
|
default: throw r
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-24 17:16:41 +00:00
|
|
|
func apiCreateUserAddress() async throws -> String {
|
|
|
|
let r = await chatSendCmd(.createMyAddress)
|
2022-02-01 17:34:06 +00:00
|
|
|
if case let .userContactLinkCreated(connReq) = r { return connReq }
|
|
|
|
throw r
|
|
|
|
}
|
|
|
|
|
2022-02-24 17:16:41 +00:00
|
|
|
func apiDeleteUserAddress() async throws {
|
|
|
|
let r = await chatSendCmd(.deleteMyAddress)
|
2022-02-01 17:34:06 +00:00
|
|
|
if case .userContactLinkDeleted = r { return }
|
|
|
|
throw r
|
|
|
|
}
|
|
|
|
|
2022-02-24 17:16:41 +00:00
|
|
|
func apiGetUserAddress() async throws -> String? {
|
|
|
|
let r = await chatSendCmd(.showMyAddress)
|
2022-02-01 17:34:06 +00:00
|
|
|
switch r {
|
|
|
|
case let .userContactLink(connReq):
|
|
|
|
return connReq
|
|
|
|
case .chatCmdError(chatError: .errorStore(storeError: .userContactLinkNotFound)):
|
|
|
|
return nil
|
|
|
|
default: throw r
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-24 17:16:41 +00:00
|
|
|
func apiAcceptContactRequest(contactReqId: Int64) async throws -> Contact {
|
|
|
|
let r = await chatSendCmd(.apiAcceptContact(contactReqId: contactReqId))
|
2022-02-01 17:34:06 +00:00
|
|
|
if case let .acceptingContactRequest(contact) = r { return contact }
|
|
|
|
throw r
|
|
|
|
}
|
|
|
|
|
2022-02-24 17:16:41 +00:00
|
|
|
func apiRejectContactRequest(contactReqId: Int64) async throws {
|
|
|
|
let r = await chatSendCmd(.apiRejectContact(contactReqId: contactReqId))
|
2022-02-01 17:34:06 +00:00
|
|
|
if case .contactRequestRejected = r { return }
|
|
|
|
throw r
|
|
|
|
}
|
|
|
|
|
2022-02-24 17:16:41 +00:00
|
|
|
func apiChatRead(type: ChatType, id: Int64, itemRange: (Int64, Int64)) async throws {
|
|
|
|
let r = await chatSendCmd(.apiChatRead(type: type, id: id, itemRange: itemRange))
|
2022-02-12 15:59:43 +00:00
|
|
|
if case .cmdOk = r { return }
|
|
|
|
throw r
|
|
|
|
}
|
|
|
|
|
2022-02-24 17:16:41 +00:00
|
|
|
func acceptContactRequest(_ contactRequest: UserContactRequest) async {
|
2022-02-09 22:53:06 +00:00
|
|
|
do {
|
2022-02-24 17:16:41 +00:00
|
|
|
let contact = try await apiAcceptContactRequest(contactReqId: contactRequest.apiId)
|
2022-02-09 22:53:06 +00:00
|
|
|
let chat = Chat(chatInfo: ChatInfo.direct(contact: contact), chatItems: [])
|
2022-02-24 17:16:41 +00:00
|
|
|
DispatchQueue.main.async { ChatModel.shared.replaceChat(contactRequest.id, chat) }
|
2022-02-09 22:53:06 +00:00
|
|
|
} catch let error {
|
|
|
|
logger.error("acceptContactRequest error: \(error.localizedDescription)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-24 17:16:41 +00:00
|
|
|
func rejectContactRequest(_ contactRequest: UserContactRequest) async {
|
2022-02-09 22:53:06 +00:00
|
|
|
do {
|
2022-02-24 17:16:41 +00:00
|
|
|
try await apiRejectContactRequest(contactReqId: contactRequest.apiId)
|
|
|
|
DispatchQueue.main.async { ChatModel.shared.removeChat(contactRequest.id) }
|
2022-02-09 22:53:06 +00:00
|
|
|
} catch let error {
|
|
|
|
logger.error("rejectContactRequest: \(error.localizedDescription)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-24 17:16:41 +00:00
|
|
|
func markChatRead(_ chat: Chat) async {
|
2022-02-12 15:59:43 +00:00
|
|
|
do {
|
|
|
|
let minItemId = chat.chatStats.minUnreadItemId
|
|
|
|
let itemRange = (minItemId, chat.chatItems.last?.id ?? minItemId)
|
|
|
|
let cInfo = chat.chatInfo
|
2022-02-24 17:16:41 +00:00
|
|
|
try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: itemRange)
|
|
|
|
DispatchQueue.main.async { ChatModel.shared.markChatItemsRead(cInfo) }
|
2022-02-12 15:59:43 +00:00
|
|
|
} catch {
|
|
|
|
logger.error("markChatRead apiChatRead error: \(error.localizedDescription)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-24 17:16:41 +00:00
|
|
|
func markChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) async {
|
2022-02-12 15:59:43 +00:00
|
|
|
do {
|
2022-02-24 17:16:41 +00:00
|
|
|
try await apiChatRead(type: cInfo.chatType, id: cInfo.apiId, itemRange: (cItem.id, cItem.id))
|
|
|
|
DispatchQueue.main.async { ChatModel.shared.markChatItemRead(cInfo, cItem) }
|
2022-02-12 15:59:43 +00:00
|
|
|
} catch {
|
|
|
|
logger.error("markChatItemRead apiChatRead error: \(error.localizedDescription)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-09 22:53:06 +00:00
|
|
|
func initializeChat() {
|
|
|
|
do {
|
|
|
|
ChatModel.shared.currentUser = try apiGetActiveUser()
|
|
|
|
} catch {
|
|
|
|
fatalError("Failed to initialize chat controller or database: \(error)")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class ChatReceiver {
|
2022-02-24 17:16:41 +00:00
|
|
|
private var receiveLoop: Task<Void, Never>?
|
2022-02-09 22:53:06 +00:00
|
|
|
private var receiveMessages = true
|
|
|
|
private var _lastMsgTime = Date.now
|
|
|
|
|
|
|
|
static let shared = ChatReceiver()
|
|
|
|
|
|
|
|
var lastMsgTime: Date { get { _lastMsgTime } }
|
|
|
|
|
2022-02-10 15:52:11 +00:00
|
|
|
func start() {
|
2022-02-09 22:53:06 +00:00
|
|
|
logger.debug("ChatReceiver.start")
|
|
|
|
receiveMessages = true
|
|
|
|
_lastMsgTime = .now
|
|
|
|
if receiveLoop != nil { return }
|
2022-02-24 17:16:41 +00:00
|
|
|
receiveLoop = Task { await receiveMsgLoop() }
|
|
|
|
}
|
|
|
|
|
|
|
|
func receiveMsgLoop() async {
|
|
|
|
let msg = await chatRecvMsg()
|
|
|
|
self._lastMsgTime = .now
|
|
|
|
processReceivedMsg(msg)
|
|
|
|
if self.receiveMessages {
|
2022-02-28 10:44:48 +00:00
|
|
|
do { try await Task.sleep(nanoseconds: 7_500_000) }
|
|
|
|
catch { logger.error("receiveMsgLoop: Task.sleep error: \(error.localizedDescription)") }
|
2022-02-24 17:16:41 +00:00
|
|
|
await receiveMsgLoop()
|
2022-02-09 22:53:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func stop() {
|
2022-02-10 15:52:11 +00:00
|
|
|
logger.debug("ChatReceiver.stop")
|
2022-02-09 22:53:06 +00:00
|
|
|
receiveMessages = false
|
|
|
|
receiveLoop?.cancel()
|
|
|
|
receiveLoop = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func processReceivedMsg(_ res: ChatResponse) {
|
|
|
|
let chatModel = ChatModel.shared
|
2022-01-29 23:37:02 +00:00
|
|
|
DispatchQueue.main.async {
|
2022-02-04 22:13:52 +00:00
|
|
|
chatModel.terminalItems.append(.resp(.now, res))
|
2022-02-09 22:53:06 +00:00
|
|
|
logger.debug("processReceivedMsg: \(res.responseType)")
|
2022-01-29 23:37:02 +00:00
|
|
|
switch res {
|
|
|
|
case let .contactConnected(contact):
|
2022-02-05 20:10:47 +00:00
|
|
|
chatModel.updateContact(contact)
|
|
|
|
chatModel.updateNetworkStatus(contact, .connected)
|
2022-02-09 22:53:06 +00:00
|
|
|
NtfManager.shared.notifyContactConnected(contact)
|
2022-02-01 17:34:06 +00:00
|
|
|
case let .receivedContactRequest(contactRequest):
|
2022-02-02 12:51:39 +00:00
|
|
|
chatModel.addChat(Chat(
|
|
|
|
chatInfo: ChatInfo.contactRequest(contactRequest: contactRequest),
|
|
|
|
chatItems: []
|
|
|
|
))
|
2022-02-09 22:53:06 +00:00
|
|
|
NtfManager.shared.notifyContactRequest(contactRequest)
|
2022-02-04 22:13:52 +00:00
|
|
|
case let .contactUpdated(toContact):
|
|
|
|
let cInfo = ChatInfo.direct(contact: toContact)
|
|
|
|
if chatModel.hasChat(toContact.id) {
|
|
|
|
chatModel.updateChatInfo(cInfo)
|
|
|
|
}
|
2022-02-05 20:10:47 +00:00
|
|
|
case let .contactSubscribed(contact):
|
2022-02-25 20:26:56 +00:00
|
|
|
processContactSubscribed(contact)
|
2022-02-05 20:10:47 +00:00
|
|
|
case let .contactDisconnected(contact):
|
|
|
|
chatModel.updateContact(contact)
|
|
|
|
chatModel.updateNetworkStatus(contact, .disconnected)
|
|
|
|
case let .contactSubError(contact, chatError):
|
2022-02-25 20:26:56 +00:00
|
|
|
processContactSubError(contact, chatError)
|
|
|
|
case let .contactSubSummary(contactSubscriptions):
|
|
|
|
for sub in contactSubscriptions {
|
|
|
|
if let err = sub.contactError {
|
|
|
|
processContactSubError(sub.contact, err)
|
|
|
|
} else {
|
|
|
|
processContactSubscribed(sub.contact)
|
|
|
|
}
|
2022-02-05 20:10:47 +00:00
|
|
|
}
|
2022-01-29 23:37:02 +00:00
|
|
|
case let .newChatItem(aChatItem):
|
2022-02-09 22:53:06 +00:00
|
|
|
let cInfo = aChatItem.chatInfo
|
|
|
|
let cItem = aChatItem.chatItem
|
|
|
|
chatModel.addChatItem(cInfo, cItem)
|
|
|
|
NtfManager.shared.notifyMessageReceived(cInfo, cItem)
|
2022-03-25 22:26:05 +04:00
|
|
|
case let .chatItemStatusUpdated(aChatItem):
|
2022-02-12 15:59:43 +00:00
|
|
|
let cInfo = aChatItem.chatInfo
|
|
|
|
let cItem = aChatItem.chatItem
|
2022-03-30 20:37:47 +04:00
|
|
|
var res = false
|
|
|
|
if !cItem.isDeletedContent() {
|
|
|
|
res = chatModel.upsertChatItem(cInfo, cItem)
|
|
|
|
}
|
|
|
|
if res {
|
2022-02-12 15:59:43 +00:00
|
|
|
NtfManager.shared.notifyMessageReceived(cInfo, cItem)
|
2022-02-28 10:44:48 +00:00
|
|
|
} else if let endTask = chatModel.messageDelivery[cItem.id] {
|
|
|
|
switch cItem.meta.itemStatus {
|
|
|
|
case .sndSent: endTask()
|
|
|
|
case .sndErrorAuth: endTask()
|
|
|
|
case .sndError: endTask()
|
|
|
|
default: break
|
|
|
|
}
|
2022-02-12 15:59:43 +00:00
|
|
|
}
|
2022-03-25 22:26:05 +04:00
|
|
|
case let .chatItemUpdated(aChatItem):
|
|
|
|
let cInfo = aChatItem.chatInfo
|
|
|
|
let cItem = aChatItem.chatItem
|
|
|
|
if chatModel.upsertChatItem(cInfo, cItem) {
|
|
|
|
NtfManager.shared.notifyMessageReceived(cInfo, cItem)
|
|
|
|
}
|
2022-03-30 20:37:47 +04:00
|
|
|
case let .chatItemDeleted(_, toChatItem):
|
|
|
|
let cInfo = toChatItem.chatInfo
|
|
|
|
let cItem = toChatItem.chatItem
|
|
|
|
if cItem.meta.itemDeleted {
|
|
|
|
chatModel.removeChatItem(cInfo, cItem)
|
|
|
|
} else {
|
|
|
|
// currently only broadcast deletion of rcv message can be received, and only this case should happen
|
|
|
|
_ = chatModel.upsertChatItem(cInfo, cItem)
|
|
|
|
}
|
2022-01-29 23:37:02 +00:00
|
|
|
default:
|
2022-02-09 22:53:06 +00:00
|
|
|
logger.debug("unsupported event: \(res.responseType)")
|
2022-01-29 11:10:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-25 20:26:56 +00:00
|
|
|
func processContactSubscribed(_ contact: Contact) {
|
|
|
|
let m = ChatModel.shared
|
|
|
|
m.updateContact(contact)
|
|
|
|
m.updateNetworkStatus(contact, .connected)
|
|
|
|
}
|
|
|
|
|
|
|
|
func processContactSubError(_ contact: Contact, _ chatError: ChatError) {
|
|
|
|
let m = ChatModel.shared
|
|
|
|
m.updateContact(contact)
|
|
|
|
var err: String
|
|
|
|
switch chatError {
|
|
|
|
case .errorAgent(agentError: .BROKER(brokerErr: .NETWORK)): err = "network"
|
|
|
|
case .errorAgent(agentError: .SMP(smpErr: .AUTH)): err = "contact deleted"
|
|
|
|
default: err = String(describing: chatError)
|
|
|
|
}
|
|
|
|
m.updateNetworkStatus(contact, .error(err))
|
|
|
|
}
|
|
|
|
|
2022-01-29 11:10:04 +00:00
|
|
|
private struct UserResponse: Decodable {
|
|
|
|
var user: User?
|
|
|
|
var error: String?
|
|
|
|
}
|
|
|
|
|
2022-02-25 21:07:36 +00:00
|
|
|
private func chatResponse(_ cjson: UnsafeMutablePointer<CChar>) -> ChatResponse {
|
2022-01-29 11:10:04 +00:00
|
|
|
let s = String.init(cString: cjson)
|
|
|
|
let d = s.data(using: .utf8)!
|
2022-01-29 23:37:02 +00:00
|
|
|
// TODO is there a way to do it without copying the data? e.g:
|
2022-01-29 11:10:04 +00:00
|
|
|
// let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson))
|
|
|
|
// let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free)
|
|
|
|
|
2022-01-29 23:37:02 +00:00
|
|
|
// TODO some mechanism to update model without passing it - maybe Publisher / Subscriber?
|
|
|
|
|
2022-01-29 11:10:04 +00:00
|
|
|
do {
|
2022-01-29 23:37:02 +00:00
|
|
|
let r = try jsonDecoder.decode(APIResponse.self, from: d)
|
|
|
|
return r.resp
|
2022-01-29 11:10:04 +00:00
|
|
|
} catch {
|
2022-02-09 22:53:06 +00:00
|
|
|
logger.error("chatResponse jsonDecoder.decode error: \(error.localizedDescription)")
|
2022-01-29 11:10:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var type: String?
|
|
|
|
var json: String?
|
|
|
|
if let j = try? JSONSerialization.jsonObject(with: d) as? NSDictionary {
|
|
|
|
if let j1 = j["resp"] as? NSDictionary, j1.count == 1 {
|
|
|
|
type = j1.allKeys[0] as? String
|
|
|
|
}
|
|
|
|
json = prettyJSON(j)
|
|
|
|
}
|
2022-02-25 21:07:36 +00:00
|
|
|
free(cjson)
|
2022-01-29 23:37:02 +00:00
|
|
|
return ChatResponse.response(type: type ?? "invalid", json: json ?? s)
|
2022-01-29 11:10:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func prettyJSON(_ obj: NSDictionary) -> String? {
|
|
|
|
if let d = try? JSONSerialization.data(withJSONObject: obj, options: .prettyPrinted) {
|
|
|
|
return String(decoding: d, as: UTF8.self)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
private func getChatCtrl() -> chat_ctrl {
|
|
|
|
if let controller = chatController { return controller }
|
2022-02-06 18:26:22 +00:00
|
|
|
let dataDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.path + "/mobile_v1"
|
|
|
|
var cstr = dataDir.cString(using: .utf8)!
|
2022-02-26 20:21:32 +00:00
|
|
|
logger.debug("getChatCtrl: chat_init")
|
|
|
|
ChatModel.shared.terminalItems.append(.cmd(.now, .string("chat_init")))
|
2022-02-06 18:26:22 +00:00
|
|
|
chatController = chat_init(&cstr)
|
2022-02-26 20:21:32 +00:00
|
|
|
ChatModel.shared.terminalItems.append(.resp(.now, .response(type: "chat_controller", json: "chat_controller: no details")))
|
2022-02-06 18:26:22 +00:00
|
|
|
return chatController!
|
2022-01-29 11:10:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private func decodeCJSON<T: Decodable>(_ cjson: UnsafePointer<CChar>) -> T? {
|
|
|
|
let s = String.init(cString: cjson)
|
|
|
|
let d = s.data(using: .utf8)!
|
|
|
|
// let p = UnsafeMutableRawPointer.init(mutating: UnsafeRawPointer(cjson))
|
|
|
|
// let d = Data.init(bytesNoCopy: p, count: strlen(cjson), deallocator: .free)
|
|
|
|
return try? jsonDecoder.decode(T.self, from: d)
|
|
|
|
}
|
|
|
|
|
|
|
|
private func getJSONObject(_ cjson: UnsafePointer<CChar>) -> NSDictionary? {
|
|
|
|
let s = String.init(cString: cjson)
|
|
|
|
let d = s.data(using: .utf8)!
|
|
|
|
return try? JSONSerialization.jsonObject(with: d) as? NSDictionary
|
|
|
|
}
|
|
|
|
|
2022-03-25 22:13:01 +04:00
|
|
|
private func encodeJSON<T: Encodable>(_ value: T) -> String {
|
2022-01-29 11:10:04 +00:00
|
|
|
let data = try! jsonEncoder.encode(value)
|
2022-03-25 22:13:01 +04:00
|
|
|
return String(decoding: data, as: UTF8.self)
|
|
|
|
}
|
|
|
|
|
|
|
|
private func encodeCJSON<T: Encodable>(_ value: T) -> [CChar] {
|
|
|
|
encodeJSON(value).cString(using: .utf8)!
|
2022-01-29 11:10:04 +00:00
|
|
|
}
|
2022-02-01 17:34:06 +00:00
|
|
|
|
|
|
|
enum ChatError: Decodable {
|
2022-02-01 20:30:33 +00:00
|
|
|
case error(errorType: ChatErrorType)
|
2022-02-05 20:10:47 +00:00
|
|
|
case errorAgent(agentError: AgentErrorType)
|
2022-02-01 17:34:06 +00:00
|
|
|
case errorStore(storeError: StoreError)
|
|
|
|
}
|
|
|
|
|
2022-02-01 20:30:33 +00:00
|
|
|
enum ChatErrorType: Decodable {
|
2022-02-06 16:18:01 +00:00
|
|
|
case noActiveUser
|
|
|
|
case activeUserExists
|
|
|
|
case chatNotStarted
|
2022-02-01 20:30:33 +00:00
|
|
|
case invalidConnReq
|
2022-02-06 16:18:01 +00:00
|
|
|
case invalidChatMessage(message: String)
|
2022-02-14 21:52:01 +04:00
|
|
|
case contactNotReady(contact: Contact)
|
2022-02-05 20:10:47 +00:00
|
|
|
case contactGroups(contact: Contact, groupNames: [GroupName])
|
2022-02-06 16:18:01 +00:00
|
|
|
case groupUserRole
|
2022-02-05 20:10:47 +00:00
|
|
|
case groupContactRole(contactName: ContactName)
|
|
|
|
case groupDuplicateMember(contactName: ContactName)
|
|
|
|
case groupDuplicateMemberId
|
|
|
|
case groupNotJoined(groupInfo: GroupInfo)
|
|
|
|
case groupMemberNotActive
|
|
|
|
case groupMemberUserRemoved
|
|
|
|
case groupMemberNotFound(contactName: ContactName)
|
|
|
|
case groupMemberIntroNotFound(contactName: ContactName)
|
|
|
|
case groupCantResendInvitation(groupInfo: GroupInfo, contactName: ContactName)
|
|
|
|
case groupInternal(message: String)
|
|
|
|
case fileNotFound(message: String)
|
|
|
|
case fileAlreadyReceiving(message: String)
|
|
|
|
case fileAlreadyExists(filePath: String)
|
|
|
|
case fileRead(filePath: String, message: String)
|
|
|
|
case fileWrite(filePath: String, message: String)
|
|
|
|
case fileSend(fileId: Int64, agentError: String)
|
|
|
|
case fileRcvChunk(message: String)
|
|
|
|
case fileInternal(message: String)
|
2022-03-25 22:26:05 +04:00
|
|
|
case invalidQuote
|
2022-03-30 20:37:47 +04:00
|
|
|
case invalidChatItemUpdate
|
|
|
|
case invalidChatItemDelete
|
2022-02-05 20:10:47 +00:00
|
|
|
case agentVersion
|
|
|
|
case commandError(message: String)
|
2022-02-01 20:30:33 +00:00
|
|
|
}
|
|
|
|
|
2022-02-01 17:34:06 +00:00
|
|
|
enum StoreError: Decodable {
|
2022-02-05 20:10:47 +00:00
|
|
|
case duplicateName
|
|
|
|
case contactNotFound(contactId: Int64)
|
|
|
|
case contactNotFoundByName(contactName: ContactName)
|
|
|
|
case contactNotReady(contactName: ContactName)
|
|
|
|
case duplicateContactLink
|
2022-02-01 17:34:06 +00:00
|
|
|
case userContactLinkNotFound
|
2022-02-05 20:10:47 +00:00
|
|
|
case contactRequestNotFound(contactRequestId: Int64)
|
|
|
|
case contactRequestNotFoundByName(contactName: ContactName)
|
|
|
|
case groupNotFound(groupId: Int64)
|
|
|
|
case groupNotFoundByName(groupName: GroupName)
|
|
|
|
case groupWithoutUser
|
|
|
|
case duplicateGroupMember
|
|
|
|
case groupAlreadyJoined
|
|
|
|
case groupInvitationNotFound
|
|
|
|
case sndFileNotFound(fileId: Int64)
|
|
|
|
case sndFileInvalid(fileId: Int64)
|
|
|
|
case rcvFileNotFound(fileId: Int64)
|
|
|
|
case fileNotFound(fileId: Int64)
|
|
|
|
case rcvFileInvalid(fileId: Int64)
|
|
|
|
case connectionNotFound(agentConnId: String)
|
|
|
|
case introNotFound
|
|
|
|
case uniqueID
|
|
|
|
case internalError(message: String)
|
|
|
|
case noMsgDelivery(connId: Int64, agentMsgId: String)
|
|
|
|
case badChatItem(itemId: Int64)
|
|
|
|
case chatItemNotFound(itemId: Int64)
|
2022-03-25 22:26:05 +04:00
|
|
|
case quotedChatItemNotFound
|
|
|
|
case chatItemSharedMsgIdNotFound(sharedMsgId: String)
|
2022-02-05 20:10:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
enum AgentErrorType: Decodable {
|
|
|
|
case CMD(cmdErr: CommandErrorType)
|
|
|
|
case CONN(connErr: ConnectionErrorType)
|
|
|
|
case SMP(smpErr: SMPErrorType)
|
|
|
|
case BROKER(brokerErr: BrokerErrorType)
|
|
|
|
case AGENT(agentErr: SMPAgentError)
|
|
|
|
case INTERNAL(internalErr: String)
|
|
|
|
}
|
|
|
|
|
|
|
|
enum CommandErrorType: Decodable {
|
|
|
|
case PROHIBITED
|
|
|
|
case SYNTAX
|
|
|
|
case NO_CONN
|
|
|
|
case SIZE
|
|
|
|
case LARGE
|
|
|
|
}
|
|
|
|
|
|
|
|
enum ConnectionErrorType: Decodable {
|
|
|
|
case NOT_FOUND
|
|
|
|
case DUPLICATE
|
|
|
|
case SIMPLEX
|
|
|
|
case NOT_ACCEPTED
|
|
|
|
case NOT_AVAILABLE
|
|
|
|
}
|
|
|
|
|
|
|
|
enum BrokerErrorType: Decodable {
|
|
|
|
case RESPONSE(smpErr: SMPErrorType)
|
|
|
|
case UNEXPECTED
|
|
|
|
case NETWORK
|
|
|
|
case TRANSPORT(transportErr: SMPTransportError)
|
|
|
|
case TIMEOUT
|
|
|
|
}
|
|
|
|
|
|
|
|
enum SMPErrorType: Decodable {
|
|
|
|
case BLOCK
|
|
|
|
case SESSION
|
|
|
|
case CMD(cmdErr: SMPCommandError)
|
|
|
|
case AUTH
|
|
|
|
case QUOTA
|
|
|
|
case NO_MSG
|
|
|
|
case LARGE_MSG
|
|
|
|
case INTERNAL
|
|
|
|
}
|
|
|
|
|
|
|
|
enum SMPCommandError: Decodable {
|
|
|
|
case UNKNOWN
|
|
|
|
case SYNTAX
|
|
|
|
case NO_AUTH
|
|
|
|
case HAS_AUTH
|
|
|
|
case NO_QUEUE
|
|
|
|
}
|
|
|
|
|
|
|
|
enum SMPTransportError: Decodable {
|
2022-02-20 21:17:24 +00:00
|
|
|
case badBlock
|
|
|
|
case largeMsg
|
|
|
|
case badSession
|
|
|
|
case handshake(handshakeErr: SMPHandshakeError)
|
2022-02-05 20:10:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
enum SMPHandshakeError: Decodable {
|
|
|
|
case PARSE
|
|
|
|
case VERSION
|
|
|
|
case IDENTITY
|
|
|
|
}
|
|
|
|
|
|
|
|
enum SMPAgentError: Decodable {
|
|
|
|
case A_MESSAGE
|
|
|
|
case A_PROHIBITED
|
|
|
|
case A_VERSION
|
|
|
|
case A_ENCRYPTION
|
2022-02-01 17:34:06 +00:00
|
|
|
}
|