import Foundation import monalxmpp final class MonalXmppWrapper: ObservableObject { @Published private(set) var accountsAvailability: AccountsAvailability = .noAccounts @Published private(set) var accounts: [Account] = [] @Published private(set) var contacts: [Contact] = [] @Published private(set) var activeChats: [Chat] = [] private let xmpp: MLXMPPManager private let db: DataLayer private var notificationObservers: [AnyObject] = [] init() { // here is some inits (just for now) MLProcessLock.initialize(forProcess: "MainApp") // init monalxmpp components xmpp = MLXMPPManager.sharedInstance() db = DataLayer.sharedInstance() // subscribe to monalxmpp notifications and fire notification for update subscribeToUpdates() NotificationCenter.default.post(name: Notification.Name(kMonalRefresh), object: nil) // reconnect // xmpp.nowForegrounded() // xmpp.connectIfNecessary() } deinit { notificationObservers.forEach { NotificationCenter.default.removeObserver($0) } } } // MARK: - Public extension MonalXmppWrapper { func tryLogin(_ login: String, _ password: String) async throws { let loginObject = LoginTry(xmpp: xmpp) let result = await loginObject.tryLogin(login, password) if !result { throw AimErrors.loginError } else { NotificationCenter.default.post(name: Notification.Name(kMonalRefresh), object: nil) } } func addContact(contactJid: String, forAccountID: Int) async { let contact = MLContact.createContact(fromJid: contactJid, andAccountID: NSNumber(value: forAccountID)) xmpp.add(contact) } func deleteContact(_ contact: Contact) async throws { if let mlContact = db.contactList().first(where: { $0.contactJid == contact.contactJid }) { xmpp.remove(mlContact) } else { throw AimErrors.contactRemoveError } } func chat(with: Contact) -> MonalChatWrapper { // swiftlint:disable:next force_unwrapping let account = accounts.first { $0.id == with.ownerId }! let chatModel = MonalChatWrapper(account: account, contact: with, db: db, xmpp: xmpp) return chatModel } func chat(with: Chat) -> MonalChatWrapper? { guard let account = accounts.first(where: { $0.id == with.accountId }) else { return nil } var contact = contacts.first(where: { $0.ownerId == with.accountId && $0.contactJid == with.participantJid }) if contact == nil { let semaphore = DispatchSemaphore(value: 0) Task { await addContact(contactJid: with.participantJid, forAccountID: with.accountId) semaphore.signal() } semaphore.wait() refreshChats() refreshContacts() contact = contacts.first(where: { $0.ownerId == with.accountId && $0.contactJid == with.participantJid }) } guard let contact else { return nil } let chatModel = MonalChatWrapper(account: account, contact: contact, db: db, xmpp: xmpp) return chatModel } } // MARK: - Try login from Login screen private final class LoginTry { weak var xmpp: MLXMPPManager? var successObserver: AnyObject? var failureObserver: AnyObject? init(xmpp: MLXMPPManager) { self.xmpp = xmpp } // TODO: Добавить автовключение отключенных аккаунтов при попытке ввести тот же JID // Обработать кейс когда бесячий monalxmpp возвращает nil при попытке добавить тот же JID func tryLogin(_ login: String, _ password: String) async -> Bool { async let notify = await withCheckedContinuation { [weak self] continuation in self?.successObserver = NotificationCenter.default.addObserver(forName: Notification.Name("kMLResourceBoundNotice"), object: nil, queue: .main) { _ in continuation.resume(returning: true) } self?.failureObserver = NotificationCenter.default.addObserver(forName: Notification.Name("kXMPPError"), object: nil, queue: .main) { _ in continuation.resume(returning: false) } } defer { if let successObserver { NotificationCenter.default.removeObserver(successObserver) } if let failureObserver { NotificationCenter.default.removeObserver(failureObserver) } } let accountNumber = xmpp?.login(login, password: password) let result = await notify if let accountNumber, !result { xmpp?.removeAccount(forAccountID: accountNumber) } return result } } // MARK: - Handle notifications private extension MonalXmppWrapper { func subscribeToUpdates() { // General let generalRefresh = NotificationCenter.default.addObserver(forName: Notification.Name(kMonalRefresh), object: nil, queue: .main) { [weak self] _ in self?.refreshAccounts() self?.refreshContacts() self?.refreshChats() } notificationObservers.append(generalRefresh) // For contacts let contactRefresh = NotificationCenter.default.addObserver(forName: Notification.Name(kMonalContactRefresh), object: nil, queue: .main) { [weak self] _ in self?.refreshContacts() self?.refreshChats() } let contactRemove = NotificationCenter.default.addObserver(forName: Notification.Name(kMonalContactRemoved), object: nil, queue: .main) { [weak self] _ in self?.refreshContacts() self?.refreshChats() } notificationObservers.append(contentsOf: [contactRefresh, contactRemove]) // For chats // ??? } func refreshAccounts() { let accounts = db.accountList() .compactMap { dict -> Account? in guard let dict = dict as? NSDictionary else { return nil } return Account(dict) } self.accounts = accounts xmpp.connectIfNecessary() // mark accounts availability if accounts.isEmpty { accountsAvailability = .noAccounts } else if accounts.filter({ $0.isEnabled }).isEmpty { accountsAvailability = .allDisabled } else { accountsAvailability = .someEnabled } } func refreshContacts() { contacts = db.contactList() .filter { $0.isSubscribedTo || $0.hasOutgoingContactRequest || $0.isSubscribedFrom } .filter { !$0.isSelfChat } // removed for now .compactMap { Contact($0) } } func refreshChats() { activeChats = db.activeContacts(withPinned: false) .compactMap { guard let contact = $0 as? MLContact else { return nil } return Chat(contact) } } } // MARK: - Chat object final class MonalChatWrapper: ObservableObject { @Published private(set) var messages: [Message] = [] @Published var replyText: String = "" @Published var isOmemoEnabled: Bool { didSet { toggleOmemo(isOmemoEnabled) } } let contact: Contact private let monalContact: MLContact private let account: Account private let xmpp: MLXMPPManager private let db: DataLayer private var notificationObservers: [AnyObject] = [] private var mamRequestInProgress = false init(account: Account, contact: Contact, db: DataLayer, xmpp: MLXMPPManager) { self.contact = contact self.account = account self.db = db self.xmpp = xmpp // swiftlint:disable:next force_unwrapping monalContact = db.contactList().first { $0.accountID.intValue == contact.ownerId && $0.contactJid == contact.contactJid }! isOmemoEnabled = monalContact.isEncrypted subscribe() NotificationCenter.default.post(name: Notification.Name(kMonalNewMessageNotice), object: nil) // } deinit { notificationObservers.forEach { NotificationCenter.default.removeObserver($0) } } var chatTitle: String { contact.name ?? contact.contactJid } func sendText(_ text: String) { let newMessageId = UUID().uuidString _ = db.addMessageHistory( to: contact.contactJid, forAccount: monalContact.accountID, withMessage: text, actuallyFrom: account.jid, withId: newMessageId, encrypted: monalContact.isEncrypted, messageType: kMessageTypeText, mimeType: nil, size: nil ) print(newMessageId) xmpp.sendMessage(text, to: monalContact, isEncrypted: monalContact.isEncrypted, isUpload: false, messageId: newMessageId) } func requestMAM() { if mamRequestInProgress { return } mamRequestInProgress = true guard let acc = xmpp.getEnabledAccount(forID: NSNumber(value: account.id)) else { return } let lastStanzaId = messages.last?.stanzaId // last here because list is reversed ?? db.lastStanzaId(forAccount: NSNumber(value: account.id)) acc.setMAMQueryMostRecentFor(monalContact, before: lastStanzaId) { [weak self] msgs, _ in self?.mamRequestInProgress = false if !(msgs ?? []).isEmpty { self?.refreshMessages() } } } } private extension MonalChatWrapper { func subscribe() { let newMsg = NotificationCenter.default.addObserver(forName: Notification.Name(kMonalNewMessageNotice), object: nil, queue: .main) { [weak self] _ in self?.refreshMessages() } notificationObservers.append(newMsg) let sentMsg = NotificationCenter.default.addObserver(forName: Notification.Name(kMonalSentMessageNotice), object: nil, queue: .main) { [weak self] _ in self?.refreshMessages() } notificationObservers.append(sentMsg) } func refreshMessages() { let messages = db.messages(forContact: contact.contactJid, forAccount: NSNumber(value: contact.ownerId)) .compactMap { obj -> Message? in guard let message = obj as? MLMessage else { return nil } return Message(message) } .sorted { $0.timestamp > $1.timestamp } self.messages = messages } func toggleOmemo(_ new: Bool) { if monalContact.isEncrypted != new { monalContact.toggleEncryption(new) } } }