another.im-ios/AnotherIM/xmpp/modules/roster/RosterModule.swift

89 lines
3.1 KiB
Swift
Raw Normal View History

2024-12-16 12:51:12 +00:00
import Foundation
2024-12-17 10:00:51 +00:00
// TODO: add versioning (XEP-0237) if needed
2024-12-17 15:28:30 +00:00
// TODO: implement error catching
2024-12-16 12:51:12 +00:00
final class RosterModule: XmppModule {
let id = "Roseter module"
2024-12-17 10:00:51 +00:00
private weak var storage: (any XMPPStorage)?
init(_ storage: any XMPPStorage) {
self.storage = storage
}
2024-12-16 12:51:12 +00:00
func reduce(oldState: ClientState, with _: Event) -> ClientState {
oldState
}
func process(state: ClientState, with event: Event) async -> Event? {
switch event {
case .streamReady:
return .requestRoster
case .requestRoster:
2024-12-17 10:00:51 +00:00
let req = Stanza.iqGet(
from: state.jid.full,
payload: XMLElement(name: "query", xmlns: "jabber:iq:roster", attributes: [:], content: nil, nodes: [])
)
2024-12-16 12:51:12 +00:00
if let req {
return .stanzaOutbound(req)
} else {
return nil
}
2024-12-17 10:00:51 +00:00
case .stanzaInbound(let stanza):
2024-12-17 15:28:30 +00:00
if let query = stanza.wrapped.nodes.first(where: { $0.name == "query" }), query.xmlns == "jabber:iq:roster" {
return await processRoster(state: state, stanza: stanza)
} else {
return nil
2024-12-17 10:00:51 +00:00
}
2024-12-16 12:51:12 +00:00
default:
return nil
}
}
}
2024-12-17 10:00:51 +00:00
private extension RosterModule {
2024-12-17 15:28:30 +00:00
func processRoster(state: ClientState, stanza: Stanza) async -> Event? {
// get inner query
guard let query = stanza.wrapped.nodes.first(where: { $0.name == "query" })
else { return nil }
2024-12-17 10:00:51 +00:00
// get exists roster items
2024-12-17 15:28:30 +00:00
var existItems: [RosterItem] = []
2024-12-17 10:00:51 +00:00
if let data = await storage?.getRoster(jid: state.jid), let decoded = try? JSONDecoder().decode([XMLElement].self, from: data) {
2024-12-17 15:28:30 +00:00
existItems = decoded.compactMap { RosterItem(wrap: $0, owner: state.jid) }
2024-12-17 10:00:51 +00:00
}
2024-12-17 15:28:30 +00:00
// process push (.set from server)
if stanza.type == .iq(.set) {
guard
let item = query.nodes.first(where: { $0.name == "item" }),
let new = RosterItem(wrap: item, owner: state.jid)
else { return nil }
existItems = existItems.filter { $0.jid != new.jid }
existItems.append(new)
guard let data = try? JSONEncoder().encode(existItems.map { $0.wrapped }) else { return nil }
await storage?.setRoster(jid: state.jid, roster: data)
return .rosterUpdated
2024-12-17 10:00:51 +00:00
2024-12-17 15:28:30 +00:00
// process get response (.result from server)
} else if stanza.type == .iq(.get) {
let items = query.nodes.filter { $0.name == "item" }
guard let data = try? JSONEncoder().encode(items) else { return nil }
2024-12-17 10:00:51 +00:00
await storage?.setRoster(jid: state.jid, roster: data)
2024-12-17 15:28:30 +00:00
return .rosterUpdated
} else {
return nil
2024-12-17 10:00:51 +00:00
}
}
}
// <iq to='testmon3@test.anal.company/TwtWkVOZ3liz' type='result' id='7l899q9r'>
// <query ver='27' xmlns='jabber:iq:roster'>
// <item jid='fmodf@conversations.im' subscription='both' xmlns='jabber:iq:roster'/>
// <item subscription='to' jid='testmon4@test.anal.company' xmlns='jabber:iq:roster'/>
// </query>
// </iq>