diff --git a/AnotherXMPP/modules/roster/RosterModule.swift b/AnotherXMPP/modules/roster/RosterModule.swift index 903f421..bcddec0 100644 --- a/AnotherXMPP/modules/roster/RosterModule.swift +++ b/AnotherXMPP/modules/roster/RosterModule.swift @@ -1,12 +1,13 @@ +// RFC 6121 +// XEP-0237 import Foundation -// TODO: add versioning (XEP-0237) -// TODO: implement error catching final class RosterModule: XmppModule { let id = "Roseter module" private weak var storage: (any XMPPStorage)? private var fullReqId = "" + private var isVerSupported = false init(_ storage: any XMPPStorage) { self.storage = storage @@ -21,10 +22,29 @@ final class RosterModule: XmppModule { case .streamReady: return .requestRoster + case .xmlInbound(let xml): + if xml.name == "stream:features" { + if let ver = xml.nodes.first(where: { $0.name == "ver" }), ver.xmlns == "urn:xmpp:features:rosterver" { + isVerSupported = true + } + } + return nil + case .requestRoster: + var attributes: [String: String] = [:] + if isVerSupported { + let ver = await storage?.getRosterVer(jid: state.jid) + attributes["ver"] = ver ?? "" + } let req = Stanza.iqGet( from: state.jid.full, - payload: XMLElement(name: "query", xmlns: "jabber:iq:roster", attributes: [:], content: nil, nodes: []) + payload: XMLElement( + name: "query", + xmlns: "jabber:iq:roster", + attributes: attributes, + content: nil, + nodes: [] + ) ) if let req { fullReqId = req.id ?? "???" @@ -46,13 +66,16 @@ final class RosterModule: XmppModule { if let query = stanza.wrapped.nodes.first(where: { $0.name == "query" }), query.xmlns == "jabber:iq:roster" { switch stanza.type { case .iq(.set): - return await processSet(state: state, stanza: stanza) + return nil + // return await processSet(state: state, stanza: stanza) case .iq(.result): - return await processResult(state: state, stanza: stanza) + return nil + // return await processResult(state: state, stanza: stanza) case .iq(.error): // handle errors here + // TODO: implement error catching return nil default: @@ -68,88 +91,88 @@ final class RosterModule: XmppModule { } } -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 - } -} +// 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 +// } +// } diff --git a/project.yml b/project.yml index ad575e7..a104e1e 100644 --- a/project.yml +++ b/project.yml @@ -81,6 +81,7 @@ targets: - path: AnotherIM excludes: - .nvim + - xmls settings: TARGETED_DEVICE_FAMILY: 1 DEBUG_INFORMATION_FORMAT: dwarf-with-dsym