import Foundation // TODO: add versioning (XEP-0237) if needed final class RosterModule: XmppModule { let id = "Roseter module" private weak var storage: (any XMPPStorage)? init(_ storage: any XMPPStorage) { self.storage = storage } 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: let req = Stanza.iqGet( from: state.jid.full, payload: XMLElement(name: "query", xmlns: "jabber:iq:roster", attributes: [:], content: nil, nodes: []) ) if let req { return .stanzaOutbound(req) } else { return nil } case .stanzaInbound(let stanza): if stanza.type == .iq(.result) { if let query = stanza.wrapped.nodes.first(where: { $0.name == "query" }), query.xmlns == "jabber:iq:roster" { return await processRoster(state: state, xml: query) } } return nil default: return nil } } } private extension RosterModule { func processRoster(state: ClientState, xml: XMLElement) async -> Event? { // get exists roster items var existItems: [Roster] = [] if let data = await storage?.getRoster(jid: state.jid), let decoded = try? JSONDecoder().decode([XMLElement].self, from: data) { existItems = decoded.compactMap { Roster(wrap: $0, owner: state.jid) } } // extract items from incoming xml var newItems = xml.nodes .compactMap { Roster(wrap: $0, owner: state.jid) } // manage it ????? var roster: [XMLElement] = newItems.map { $0.wrapped } // save updated roster if let data = try? JSONEncoder().encode(roster) { await storage?.setRoster(jid: state.jid, roster: data) } return nil } } // // // // // //