diff --git a/AnotherIM/View/TestScreen.swift b/AnotherIM/View/TestScreen.swift index 2e77561..36372ab 100644 --- a/AnotherIM/View/TestScreen.swift +++ b/AnotherIM/View/TestScreen.swift @@ -30,9 +30,16 @@ struct TestScreen: View { .background { Color.blue.opacity(0.4) } } Button { - // cls.requestRoster() + cls.addContact(jidStr: "asdadad@asdfsdf.df") } label: { - Text("Request roster") + Text("Add contact") + .padding() + .background { Color.blue.opacity(0.4) } + } + Button { + cls.deleteContact(jidStr: "asdadad@asdfsdf.df") + } label: { + Text("Remove contact") .padding() .background { Color.blue.opacity(0.4) } } @@ -51,9 +58,8 @@ struct TestScreen: View { final class TestStorage: XMPPStorage { private var roster: [String: Data] = [:] - func getCredentialsByUUID(_ uuid: UUID) async -> Credentials? { - print(uuid) - return ["password": pass] + func getCredentialsByUUID(_: UUID) async -> Credentials? { + ["password": pass] } func deleteRoster(jid: JID) async { @@ -66,7 +72,10 @@ final class TestStorage: XMPPStorage { func setRoster(jid: JID, roster: Data) async { self.roster[jid.bare] = roster - print("updated") + + if let arr = try? JSONDecoder().decode([XMLElement].self, from: roster) { + print("ROSTER: \(arr)\n\n") + } } } diff --git a/AnotherIM/xmpp/XMPPClient.swift b/AnotherIM/xmpp/XMPPClient.swift index 4585017..481b3d4 100644 --- a/AnotherIM/xmpp/XMPPClient.swift +++ b/AnotherIM/xmpp/XMPPClient.swift @@ -127,7 +127,7 @@ extension XMPPClient { } } - func deleteRosterItem(jidStr: String) { + func deleteContact(jidStr: String) { Task { await fire(.deleteRosterItem(jidStr: jidStr)) } diff --git a/AnotherIM/xmpp/models/Roster.swift b/AnotherIM/xmpp/models/Roster.swift index c105f35..19020c7 100644 --- a/AnotherIM/xmpp/models/Roster.swift +++ b/AnotherIM/xmpp/models/Roster.swift @@ -30,6 +30,7 @@ enum RosterSubsriptionType: String { // Roster is a "transparent" structure // which is just wrap xml item around +// TODO: Add groups and annotattions! struct RosterItem: Identifiable, Equatable { let owner: String let wrapped: XMLElement @@ -66,5 +67,3 @@ struct RosterItem: Identifiable, Equatable { rhs.id == lhs.id && rhs.wrapped == lhs.wrapped } } - -// TODO: Add groups and annotattions! diff --git a/AnotherIM/xmpp/modules/roster/RosterModule.swift b/AnotherIM/xmpp/modules/roster/RosterModule.swift index 3928af5..2850d35 100644 --- a/AnotherIM/xmpp/modules/roster/RosterModule.swift +++ b/AnotherIM/xmpp/modules/roster/RosterModule.swift @@ -6,6 +6,7 @@ final class RosterModule: XmppModule { let id = "Roseter module" private weak var storage: (any XMPPStorage)? + private var fullReqId = "" init(_ storage: any XMPPStorage) { self.storage = storage @@ -26,6 +27,7 @@ final class RosterModule: XmppModule { payload: XMLElement(name: "query", xmlns: "jabber:iq:roster", attributes: [:], content: nil, nodes: []) ) if let req { + fullReqId = req.id ?? "???" return .stanzaOutbound(req) } else { return nil @@ -38,6 +40,12 @@ final class RosterModule: XmppModule { 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 } @@ -45,7 +53,7 @@ final class RosterModule: XmppModule { } private extension RosterModule { - func processRoster(state: ClientState, stanza: Stanza) async -> Event? { + 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 } @@ -68,21 +76,47 @@ private extension RosterModule { await storage?.setRoster(jid: state.jid, roster: data) return .rosterUpdated - // 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 } - await storage?.setRoster(jid: state.jid, roster: data) + // 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 + } + } +}