import Foundation import monalxmpp enum AccountsAvailability { case noAccounts case allDisabled case someEnabled } final class WrapperXMPP: 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() { // init monalxmpp components xmpp = MLXMPPManager.sharedInstance() db = DataLayer.sharedInstance() // subscribe to monalxmpp notifications and fire notification for update subscribeToUpdates() processOnStart() } deinit { notificationObservers.forEach { NotificationCenter.default.removeObserver($0) } } } // MARK: - Public extension WrapperXMPP { func tryLogin(_ login: String, _ password: String) async throws { let scenario = ScenarioLogIn() let result = await scenario.tryLogin(login, password) if !result { throw AimErrors.loginError } } 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) -> WrapperChat { // swiftlint:disable:next force_unwrapping let account = accounts.first { $0.id == with.ownerId }! let chatModel = WrapperChat(account: account, contact: with, db: db, xmpp: xmpp) return chatModel } func chat(with: Chat) -> WrapperChat? { 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() contact = contacts.first(where: { $0.ownerId == with.accountId && $0.contactJid == with.participantJid }) } guard let contact else { return nil } let chatModel = WrapperChat(account: account, contact: contact, db: db, xmpp: xmpp) return chatModel } } // MARK: - Handle notifications private extension WrapperXMPP { // Subsribe to monalxmpp events func subscribeToUpdates() { notificationObservers.append( NotificationCenter.default.addObserver(forName: Notification.Name(kMonalRefresh), object: nil, queue: .main) { [weak self] notification in self?.processEvent(kMonalRefresh, notification.userInfo?["contact"]) } ) notificationObservers.append( NotificationCenter.default.addObserver(forName: Notification.Name(kMonalContactRefresh), object: nil, queue: .main) { [weak self] notification in self?.processEvent(kMonalContactRefresh, notification.userInfo?["contact"]) } ) notificationObservers.append( NotificationCenter.default.addObserver(forName: Notification.Name(kMonalContactRemoved), object: nil, queue: .main) { [weak self] notification in self?.processEvent(kMonalContactRemoved, notification.userInfo?["contact"]) } ) notificationObservers.append( NotificationCenter.default.addObserver(forName: Notification.Name(kMLResourceBoundNotice), object: nil, queue: .main) { [weak self] _ in self?.processOnStart() // DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { // } } ) } // Process monalxmpp events func processEvent(_ notificationName: String, _ object: Any?) { switch notificationName { case kMonalRefresh: print("refresh?") case kMonalContactRefresh: if let mlContact = object as? MLContact, !mlContact.isSelfChat { if let contact = Contact(mlContact) { if let index = contacts.firstIndex(where: { $0.id == mlContact.id }) { contacts[index] = contact } else { contacts.append(contact) contacts.sort { $0.name < $1.name } } } } case kMonalContactRemoved: if let mlContact = object as? MLContact { contacts = contacts.filter { $0.id != mlContact.id } } default: break } } // Initital monalxmpp db fetch func processOnStart() { // get all accounts and contacts once let accounts = db.accountList() .compactMap { dict -> Account? in guard let dict = dict as? NSDictionary else { return nil } return Account(dict) } self.accounts = accounts // check if active accounts existed if accounts.isEmpty { accountsAvailability = .noAccounts } else if accounts.filter({ $0.isEnabled }).isEmpty { accountsAvailability = .allDisabled } else { accountsAvailability = .someEnabled } // get all contacts if !accounts.isEmpty { contacts = db.contactList() .filter { $0.isSubscribedTo || $0.hasOutgoingContactRequest || $0.isSubscribedFrom } .filter { !$0.isSelfChat } // removed for now .compactMap { Contact($0) } // get active chats if !contacts.isEmpty { activeChats = db.activeContacts(withPinned: false) .compactMap { guard let contact = $0 as? MLContact else { return nil } return Chat(contact) } } } // try reconnect active clients xmpp.connectIfNecessary() } }