another.im-ios/Monal/another.im/XMPP/MonalXmppWrapper.swift
2024-11-25 11:51:00 +01:00

212 lines
7.4 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.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 {
let chatModel = MonalChatWrapper(contact: with, db: db, xmpp: xmpp)
return chatModel
}
func chat(with: Chat) -> MonalChatWrapper? {
guard let contact = contacts.first(where: { $0.ownerId == with.accountId && $0.contactJid == with.participantJid }) else { return nil }
let chatModel = MonalChatWrapper(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) { notification in
print(notification.debugDescription)
continuation.resume(returning: true)
}
self?.failureObserver = NotificationCenter.default.addObserver(forName: Notification.Name("kXMPPError"), object: nil, queue: .main) { notification in
print(notification.debugDescription)
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] = []
let contact: Contact
private let xmpp: MLXMPPManager
private let db: DataLayer
private var notificationObservers: [AnyObject] = []
init(contact: Contact, db: DataLayer, xmpp: MLXMPPManager) {
self.contact = contact
self.db = db
self.xmpp = xmpp
}
deinit {
notificationObservers.forEach { NotificationCenter.default.removeObserver($0) }
}
private func subscribe() {
let newMsg = NotificationCenter.default.addObserver(forName: Notification.Name(kMonalNewMessageNotice), object: nil, queue: .main) { [weak self] _ in
self?.refreshMessages()
}
notificationObservers.append(newMsg)
}
private func refreshMessages() {
messages = db.messages(forContact: contact.contactJid, forAccount: NSNumber(value: contact.ownerId))
.compactMap {
guard let message = $0 as? MLMessage else { return nil }
return Message(message)
}
}
}