From 0dff34318bfb85dad74f853ca18f8372699fe7e7 Mon Sep 17 00:00:00 2001 From: fmodf Date: Mon, 13 Jan 2025 15:14:04 +0100 Subject: [PATCH] wip --- AnotherIM/View/TestScreen.swift | 4 +- AnotherXMPP/models/Stanza.swift | 8 + AnotherXMPP/modules/roster/RosterModule.swift | 145 ++++++------------ 3 files changed, 60 insertions(+), 97 deletions(-) diff --git a/AnotherIM/View/TestScreen.swift b/AnotherIM/View/TestScreen.swift index ae32501..27a12d7 100644 --- a/AnotherIM/View/TestScreen.swift +++ b/AnotherIM/View/TestScreen.swift @@ -31,14 +31,14 @@ struct TestScreen: View { .background { Color.blue.opacity(0.4) } } Button { - cls.addContact(jidStr: "asdadad33@asdfsdf.df", name: "sdsd") + cls.addContact(jidStr: "deltest@asdfsdf.df", name: "sdsd") } label: { Text("Add contact") .padding() .background { Color.blue.opacity(0.4) } } Button { - cls.deleteContact(jidStr: "asdadad11@asdfsdf.df") + cls.deleteContact(jidStr: "deltest@asdfsdf.df") } label: { Text("Remove contact") .padding() diff --git a/AnotherXMPP/models/Stanza.swift b/AnotherXMPP/models/Stanza.swift index c72e80f..f1d9700 100644 --- a/AnotherXMPP/models/Stanza.swift +++ b/AnotherXMPP/models/Stanza.swift @@ -91,6 +91,14 @@ extension Stanza { buildIq(direction: "set", from: from, to: nil, payload: payload) } + static func iqResult(payload: XMLElement) -> Stanza? { + buildIq(direction: "result", from: nil, to: nil, payload: payload) + } + + static func iqResult(from: String, payload: XMLElement) -> Stanza? { + buildIq(direction: "result", from: from, to: nil, payload: payload) + } + // build iq stanza private static func buildIq(direction: String, from: String?, to: String?, payload: XMLElement) -> Stanza? { var attributes = ["id": XMLElement.randomId, "type": direction] diff --git a/AnotherXMPP/modules/roster/RosterModule.swift b/AnotherXMPP/modules/roster/RosterModule.swift index 494577f..b86b0fd 100644 --- a/AnotherXMPP/modules/roster/RosterModule.swift +++ b/AnotherXMPP/modules/roster/RosterModule.swift @@ -2,7 +2,6 @@ // XEP-0237 import Foundation -// TODO: implement error catching final class RosterModule: XmppModule { let id = "Roseter module" @@ -123,6 +122,14 @@ final class RosterModule: XmppModule { return nil } + // if we signaling "result" to server it means roster was updated on this resource + case .stanzaOutbound(let stanza): + if let query = stanza.wrapped.nodes.first(where: { $0.name == "query" }), query.xmlns == "jabber:iq:roster", stanza.type == .iq(.result) { + return .rosterUpdated + } else { + return nil + } + default: return nil } @@ -134,7 +141,7 @@ private extension RosterModule { switch stanza.type { // "set" type stanza from server is just "push", so change roster accordingly case .iq(.set): - // sanity check + // sanity check (according RFC6121 skip this push if its not for current resource) if stanza.wrapped.attributes["to"] != state.jid.bare { return nil } @@ -164,9 +171,25 @@ private extension RosterModule { // save roster guard let data = try? JSONEncoder().encode(existItems.map { $0.wrapped }) else { return nil } await storage?.setRoster(jid: state.jid, roster: data) - return .rosterUpdated - // the "result" is an answer from one of our "set", or initial roster request + // according RFC6120 it is not necessary to answer server with "result" on push, but why not? + let result = Stanza.iqResult( + from: state.jid.full, + payload: XMLElement( + name: "query", + xmlns: "jabber:iq:roster", + attributes: [:], + content: nil, + nodes: [] + ) + ) + if let result { + return .stanzaOutbound(result) + } else { + return nil + } + + // the "result" from server is an answer from one of our "set", or initial roster request case .iq(.result): // get request stanza from registry guard let respId = stanza.id, let req = await registry.deuque(for: respId) else { return nil } @@ -201,17 +224,35 @@ private extension RosterModule { // for result of one of our request case .iq(.set): - break + // get item from request stanza + let xmlItem = req.wrapped + .nodes + .first(where: { $0.name == "query" })? + .nodes + .first(where: { $0.name == "item" }) + guard let xmlItem, let item = RosterItem(wrap: xmlItem, owner: state.jid) else { return nil } + + // filter out exists items + existItems = existItems.filter { $0.id != item.id } + + // append updated/new item (if it not "removed") + if item.subsription != .remove { + existItems.append(item) + } + + // save roster + guard let data = try? JSONEncoder().encode(existItems.map { $0.wrapped }) else { return nil } + await storage?.setRoster(jid: state.jid, roster: data) + return .rosterUpdated default: - break + return nil } - // append items from requested roster - return nil - // TODO: add error handling here case .iq(.error): + guard let respId = stanza.id, let req = await registry.deuque(for: respId) else { return nil } + print("Error on request: \(req.wrapped)\n with: \(stanza.wrapped)") return nil default: @@ -219,89 +260,3 @@ private extension RosterModule { } } } - -// private extension RosterModule { -// private func update(state: ClientState, jidStr: String, args: [String: String]) async -> Event? { -// print(state, jidStr, args) -// return nil -// } -// -// private func delete(state: ClientState, jidStr: String) async -> Event? { -// var existItems: [RosterItem] = [] -// guard -// let data = await storage?.getRoster(jid: state.jid), -// let decoded = try? JSONDecoder().decode([XMLElement].self, from: data), -// let jid = try? JID(jidStr) -// else { -// return nil -// } -// existItems = decoded.compactMap { RosterItem(wrap: $0, owner: state.jid) } -// existItems = existItems.filter { $0.jid != jid } -// return .rosterUpdated -// } -// -// private func processSet(state: ClientState, stanza: Stanza) async -> Event? { -// // sanity check -// if stanza.wrapped.attributes["from"] != state.jid.bare { -// 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 -// let items = stanza.wrapped -// .nodes -// .first(where: { $0.name == "query" })? -// .nodes -// .filter { $0.name == "item" } ?? [] -// for item in items { -// guard let itemJidStr = item.attributes["jid"], let itemJid = try? JID(itemJidStr) else { continue } -// let subscription = item.attributes["subscription"] -// switch subscription { -// // TODO: scheck subscription type for removed contacts -// // on different servers -// case "remove": -// existItems = existItems.filter { $0.jid == itemJid } -// -// // by default just update roster (or add it if its new) -// default: -// if let rosterItem = RosterItem(wrap: item, owner: state.jid) { -// existItems = existItems.filter { $0.jid == itemJid } -// existItems.append(rosterItem) -// } else { -// continue -// } -// } -// } -// -// // save roster -// guard let data = try? JSONEncoder().encode(existItems.map { $0.wrapped }) else { return nil } -// await storage?.setRoster(jid: state.jid, roster: data) -// -// // according to RFC-6121 a set from server (push) -// // shouyld be answered with result -// guard -// let res = Stanza(wrap: XMLElement( -// name: "iq", -// xmlns: nil, -// attributes: [ -// "from": state.jid.full, -// "id": stanza.id ?? "???", -// "type": "result" -// ], -// content: nil, -// nodes: [] -// )) else { return nil } -// return .stanzaOutbound(res) -// } -// -// private func processResult(state _: ClientState, stanza: Stanza) async -> Event? { -// print("--WE HERE 2!") -// print(stanza) -// return nil -// } -// }