123 lines
4.5 KiB
Swift
123 lines
4.5 KiB
Swift
import Foundation
|
|
|
|
// TODO: add versioning (XEP-0237) if needed
|
|
// TODO: implement error catching
|
|
final class RosterModule: XmppModule {
|
|
let id = "Roseter module"
|
|
|
|
private weak var storage: (any XMPPStorage)?
|
|
private var fullReqId = ""
|
|
|
|
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 {
|
|
fullReqId = req.id ?? "???"
|
|
return .stanzaOutbound(req)
|
|
} else {
|
|
return nil
|
|
}
|
|
|
|
case .stanzaInbound(let stanza):
|
|
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
|
|
}
|
|
|
|
case .addRosterItem(let jidStr):
|
|
return updRoster(state: state, target: jidStr, remove: false)
|
|
|
|
case .deleteRosterItem(let jidStr):
|
|
return updRoster(state: state, target: jidStr, remove: true)
|
|
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
private extension RosterModule {
|
|
private 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 }
|
|
|
|
// get exists roster items
|
|
var existItems: [RosterItem] = []
|
|
if let data = await storage?.getRoster(jid: state.jid), let decoded = try? JSONDecoder().decode([XMLElement].self, from: data) {
|
|
existItems = decoded.compactMap { RosterItem(wrap: $0, owner: state.jid) }
|
|
}
|
|
|
|
// 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
|
|
|
|
// process .result from server
|
|
} else if stanza.type == .iq(.result) {
|
|
// process full list
|
|
if stanza.id == fullReqId {
|
|
let items = query.nodes.filter { $0.name == "item" }
|
|
guard let data = try? JSONEncoder().encode(items) else { return nil }
|
|
await storage?.setRoster(jid: state.jid, roster: data)
|
|
|
|
// process changed item
|
|
} else {
|
|
// TODO: Fix removing item!
|
|
// 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 }
|
|
// if new.subsription != .remove {
|
|
// existItems.append(new)
|
|
// } else {
|
|
// print("REMOVED!!!")
|
|
// }
|
|
// guard let data = try? JSONEncoder().encode(existItems.map { $0.wrapped }) else { return nil }
|
|
// await storage?.setRoster(jid: state.jid, roster: data)
|
|
}
|
|
return .rosterUpdated
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
private func updRoster(state: ClientState, target: String, remove: Bool) -> Event? {
|
|
var attributes = ["jid": target]
|
|
if remove {
|
|
attributes["subcription"] = "remove"
|
|
}
|
|
let item = XMLElement(name: "item", xmlns: "jabber:iq:roster", attributes: attributes, content: nil, nodes: [])
|
|
let query = XMLElement(name: "query", xmlns: "jabber:iq:roster", attributes: [:], content: nil, nodes: [item])
|
|
if let req = Stanza.iqSet(from: state.jid.full, payload: query) {
|
|
return .stanzaOutbound(req)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
}
|