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? { guard let account = accounts.first(where: { $0.id == with.ownerId }) else { return nil } return WrapperChat(with: with, account: account) } func chat(with: Chat) -> WrapperChat? { guard let account = accounts.first(where: { $0.id == with.accountId }) else { return nil } return WrapperChat(with: with, account: account) } } // MARK: - Handle notifications private extension WrapperXMPP { // Subsribe to monalxmpp events func subscribeToUpdates() { let notificationNames = [ kMonalRefresh, kMonalContactRefresh, kMonalContactRemoved, kMLResourceBoundNotice, kMonalNewMessageNotice, kMonalHistoryMessagesNotice ] notificationObservers = notificationNames.map { name in NotificationCenter.default.addObserver(forName: Notification.Name(name), object: nil, queue: .main) { [weak self] notification in self?.processEvent(notification) } } } // Process monalxmpp events func processEvent(_ notification: Notification) { switch notification.name.rawValue { case kMonalRefresh: print("refresh?") case kMonalContactRefresh: if let mlContact = notification.userInfo?["contact"] 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 = notification.userInfo?["contact"] as? MLContact { contacts = contacts.filter { $0.id != mlContact.id } } case kMLResourceBoundNotice: processOnStart() case kMonalNewMessageNotice, kMonalHistoryMessagesNotice: if let mlContact = notification.userInfo?["contact"] as? MLContact, !mlContact.isSelfChat { let contactJid = mlContact.contactJid let accountId = mlContact.account?.accountID.intValue ?? 0 if activeChats.first(where: { $0.accountId == accountId && $0.participantJid == contactJid }) == nil { if let chat = Chat(mlContact) { activeChats.append(chat) activeChats.sort { $0.participantJid < $1.participantJid } } } } 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() } }