conversations-classic-ios/ConversationsClassic/AppCore/XMPP/XMPPService.swift

267 lines
11 KiB
Swift
Raw Normal View History

2024-06-19 15:15:27 +00:00
import Combine
import Foundation
import GRDB
import Martin
2024-07-29 16:36:22 +00:00
protocol MartinsManager: Martin.RosterManager & Martin.ChatManager & Martin.ChannelManager & Martin.RoomManager {}
2024-06-19 15:15:27 +00:00
final class XMPPService: ObservableObject {
private let manager: MartinsManager
2024-07-29 16:36:22 +00:00
2024-06-19 15:15:27 +00:00
private let clientStatePublisher = PassthroughSubject<(XMPPClient, XMPPClient.State), Never>()
2024-06-24 10:44:55 +00:00
private var clientStateCancellables: Set<AnyCancellable> = []
2024-07-29 16:36:22 +00:00
private let clientMessagesPublisher = PassthroughSubject<(XMPPClient, Martin.Message), Never>()
2024-06-24 10:44:55 +00:00
private var clientMessagesCancellables: Set<AnyCancellable> = []
2024-07-29 16:36:22 +00:00
private let clientFeaturesPublisher = PassthroughSubject<(XMPPClient, [String]), Never>()
2024-07-22 12:02:33 +00:00
private var clientFeaturesCancellables: Set<AnyCancellable> = []
2024-06-19 15:15:27 +00:00
@Published private(set) var clients: [XMPPClient] = []
var clientState: AnyPublisher<(XMPPClient, XMPPClient.State), Never> {
clientStatePublisher.eraseToAnyPublisher()
}
2024-06-24 10:44:55 +00:00
var clientMessages: AnyPublisher<(XMPPClient, Martin.Message), Never> {
clientMessagesPublisher.eraseToAnyPublisher()
}
2024-07-22 12:02:33 +00:00
var clientFeatures: AnyPublisher<(XMPPClient, [String]), Never> {
clientFeaturesPublisher.eraseToAnyPublisher()
}
2024-06-19 15:15:27 +00:00
init(manager: MartinsManager) {
self.manager = manager
}
func updateClients(for accounts: [Account]) {
// get simple diff
let forAdd = accounts
.filter { !self.clients.map { $0.connectionConfiguration.userJid.stringValue }.contains($0.bareJid) }
let forRemove = clients
.map { $0.connectionConfiguration.userJid.stringValue }
.filter { !accounts.map { $0.bareJid }.contains($0) }
// init and add clients
for account in forAdd {
2024-06-24 10:44:55 +00:00
// add client
2024-06-19 15:15:27 +00:00
let client = makeClient(for: account, with: manager)
clients.append(client)
2024-06-24 10:44:55 +00:00
// subscribe to client state
client.$state
2024-06-19 15:15:27 +00:00
.sink { [weak self] state in
self?.clientStatePublisher.send((client, state))
}
2024-06-24 10:44:55 +00:00
.store(in: &clientStateCancellables)
2024-07-22 12:02:33 +00:00
// subscribe to client server features
client.module(DiscoveryModule.self).$serverDiscoResult
.sink { [weak self] disco in
self?.clientFeaturesPublisher.send((client, disco.features))
}
.store(in: &clientFeaturesCancellables)
2024-06-24 10:44:55 +00:00
// subscribe to client messages
client.module(MessageModule.self).messagesPublisher
.sink { [weak self] message in
self?.clientMessagesPublisher.send((client, message.message))
}
.store(in: &clientMessagesCancellables)
2024-06-19 15:15:27 +00:00
2024-07-22 12:18:42 +00:00
// subscribe to carbons
client.module(MessageCarbonsModule.self).carbonsPublisher
.sink { [weak self] carbon in
self?.clientMessagesPublisher.send((client, carbon.message))
}
.store(in: &clientMessagesCancellables)
2024-07-24 13:35:17 +00:00
// subscribe to archived messages
2024-08-08 10:42:48 +00:00
// client.module(.mam).archivedMessagesPublisher
// .sink(receiveValue: { [weak self] archived in
// let message = archived.message
// message.attribute("archived_date", newValue: "\(archived.timestamp.timeIntervalSince1970)")
// self?.clientMessagesPublisher.send((client, message))
// })
// .store(in: &clientMessagesCancellables)
2024-07-22 18:53:26 +00:00
2024-07-24 13:35:17 +00:00
// enable carbons if available
client.module(.messageCarbons).$isAvailable.filter { $0 }
.sink(receiveValue: { [weak client] _ in
client?.module(.messageCarbons).enable()
})
.store(in: &clientMessagesCancellables)
2024-07-22 18:53:26 +00:00
// finally, do login
client.login()
2024-06-19 15:15:27 +00:00
}
// remove clients
for jid in forRemove {
deinitClient(jid: jid)
}
}
private func makeClient(for account: Account, with manager: MartinsManager) -> XMPPClient {
let client = XMPPClient()
// register modules
// core modules RFC 6120
client.modulesManager.register(StreamFeaturesModule())
client.modulesManager.register(SaslModule())
client.modulesManager.register(AuthModule())
client.modulesManager.register(SessionEstablishmentModule())
client.modulesManager.register(ResourceBinderModule())
client.modulesManager.register(DiscoveryModule(identity: .init(category: "client", type: "iOS", name: Const.appName)))
// messaging modules RFC 6121
client.modulesManager.register(RosterModule(rosterManager: manager))
client.modulesManager.register(PresenceModule())
client.modulesManager.register(PubSubModule())
client.modulesManager.register(MessageModule(chatManager: manager))
client.modulesManager.register(MessageArchiveManagementModule())
2024-07-22 12:02:33 +00:00
client.modulesManager.register(MessageCarbonsModule())
2024-07-14 18:28:54 +00:00
// file transfer modules
client.modulesManager.register(HttpFileUploadModule())
2024-06-19 15:15:27 +00:00
// extensions
client.modulesManager.register(SoftwareVersionModule())
client.modulesManager.register(PingModule())
client.connectionConfiguration.userJid = .init(account.bareJid)
client.connectionConfiguration.credentials = .password(password: account.pass)
2024-07-29 16:36:22 +00:00
// group chats
2024-07-30 09:26:30 +00:00
client.modulesManager.register(MucModule(roomManager: manager))
// channels
// client.modulesManager.register(MixModule(channelManager: manager))
2024-07-29 16:36:22 +00:00
2024-06-19 15:15:27 +00:00
// add client to clients
return client
}
func deinitClient(jid: String) {
if let index = clients.firstIndex(where: { $0.connectionConfiguration.userJid.stringValue == jid }) {
let client = clients.remove(at: index)
_ = client.disconnect()
}
}
func getClient(for jid: String) -> XMPPClient? {
clients.first { $0.connectionConfiguration.userJid.stringValue == jid }
}
2024-06-25 12:20:20 +00:00
2024-06-26 10:26:04 +00:00
func sendMessage(message: Message, completion: @escaping (Bool) -> Void) {
guard let client = getClient(for: message.from), let to = message.to else {
completion(false)
return
}
2024-07-02 07:22:28 +00:00
guard let chat = client.module(MessageModule.self).chatManager.chat(for: client.context, with: BareJID(to)) else {
completion(false)
return
}
2024-06-26 10:26:04 +00:00
2024-07-02 07:22:28 +00:00
let msg = chat.createMessage(text: message.body ?? "??", id: message.id)
chat.send(message: msg) { res in
2024-07-01 09:29:31 +00:00
switch res {
case .success:
completion(true)
case .failure:
completion(false)
2024-06-26 10:26:04 +00:00
}
2024-07-01 09:29:31 +00:00
}
2024-06-25 12:20:20 +00:00
}
2024-07-14 18:28:54 +00:00
func uploadAttachment(message: Message, completion: @escaping (Error?, String) -> Void) {
guard let client = getClient(for: message.from), let to = message.to else {
completion(XMPPError.bad_request("No such client"), "")
return
}
guard let fileName = message.attachmentLocalName else {
completion(XMPPError.bad_request("No such file"), "")
return
}
let url = FileProcessing.fileFolder.appendingPathComponent(fileName)
guard let data = try? Data(contentsOf: url) else {
completion(XMPPError.bad_request("No such file"), "")
return
}
2024-07-14 19:22:46 +00:00
guard let chat = client.module(MessageModule.self).chatManager.chat(for: client.context, with: BareJID(to)) else {
completion(XMPPError.bad_request("No such chat"), "")
return
}
2024-07-14 18:28:54 +00:00
let httpModule = client.module(HttpFileUploadModule.self)
httpModule.findHttpUploadComponent { res in
switch res {
case .success(let components):
guard let component = components.first(where: { $0.maxSize > data.count }) else {
completion(XMPPError.bad_request("File too big"), "")
return
}
httpModule.requestUploadSlot(componentJid: component.jid, filename: fileName, size: data.count, contentType: url.mimeType) { res in
switch res {
case .success(let slot):
var request = URLRequest(url: slot.putUri)
2024-07-22 12:02:33 +00:00
for (key, value) in slot.putHeaders {
request.addValue(value, forHTTPHeaderField: key)
2024-07-14 18:28:54 +00:00
}
request.httpMethod = "PUT"
request.httpBody = data
request.addValue(String(data.count), forHTTPHeaderField: "Content-Length")
request.addValue(url.mimeType, forHTTPHeaderField: "Content-Type")
let session = URLSession(configuration: URLSessionConfiguration.default)
session.dataTask(with: request) { _, response, error in
let code = (response as? HTTPURLResponse)?.statusCode ?? 500
guard error == nil, code == 200 || code == 201 else {
completion(XMPPError.bad_request("Upload failed"), "")
return
}
if code == 200 {
completion(XMPPError.bad_request("Invalid response code"), "")
} else {
2024-07-14 19:22:46 +00:00
let mesg = chat.createMessage(text: slot.getUri.absoluteString, id: message.id)
mesg.oob = slot.getUri.absoluteString
chat.send(message: mesg) { res in
switch res {
case .success:
completion(nil, slot.getUri.absoluteString)
case .failure:
completion(XMPPError.bad_request("File uploaded, but message sent failed"), slot.getUri.absoluteString)
}
}
2024-07-14 18:28:54 +00:00
}
}.resume()
2024-07-14 19:22:46 +00:00
case .failure(let error):
completion(error, "")
2024-07-14 18:28:54 +00:00
}
}
2024-07-14 19:22:46 +00:00
case .failure(let error):
completion(error, "")
2024-07-14 18:28:54 +00:00
}
}
}
2024-07-23 14:55:38 +00:00
func requestArchivedMessages(jid: String, to: String?, fromDate: Date) {
guard let client = getClient(for: jid) else {
return
}
client.module(.mam).queryItems(componentJid: JID(jid), with: JID(to), start: fromDate, end: Date(), queryId: UUID().uuidString) { result in
switch result {
case .success(let response):
print("MAM response: \(response)")
case .failure(let error):
print("MAM error: \(error)")
}
}
}
2024-06-19 15:15:27 +00:00
}