another.im-ios/Monal/another.im/XMPP/MonalXmppWrapper.swift
2024-11-28 18:04:59 +01:00

304 lines
11 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.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 }
DispatchQueue.main.async { [weak self] in
self?.messages = messages
}
}
func toggleOmemo(_ new: Bool) {
if monalContact.isEncrypted != new {
monalContact.toggleEncryption(new)
}
}
}