This commit is contained in:
fmodf 2024-12-17 16:28:30 +01:00
parent 0be3f68710
commit 7a722e200c
5 changed files with 51 additions and 24 deletions

View file

@ -66,6 +66,7 @@ final class TestStorage: XMPPStorage {
func setRoster(jid: JID, roster: Data) async { func setRoster(jid: JID, roster: Data) async {
self.roster[jid.bare] = roster self.roster[jid.bare] = roster
print("updated")
} }
} }

View file

@ -43,7 +43,9 @@ enum Event {
case streamReady case streamReady
case requestRoster case requestRoster
// case gotRoster // by request or by server push case rosterUpdated
case addRosterItem(jidStr: String)
case deleteRosterItem(jidStr: String)
} }
// MARK: State // MARK: State
@ -118,6 +120,18 @@ extension XMPPClient {
await fire(.startClientLogin(jid: jid, credsId: credentialsId)) await fire(.startClientLogin(jid: jid, credsId: credentialsId))
} }
} }
func addContact(jidStr: String) {
Task {
await fire(.addRosterItem(jidStr: jidStr))
}
}
func deleteRosterItem(jidStr: String) {
Task {
await fire(.deleteRosterItem(jidStr: jidStr))
}
}
} }
// MARK: Private part // MARK: Private part

View file

@ -30,7 +30,7 @@ enum RosterSubsriptionType: String {
// Roster is a "transparent" structure // Roster is a "transparent" structure
// which is just wrap xml item around // which is just wrap xml item around
struct Roster: Identifiable, Equatable { struct RosterItem: Identifiable, Equatable {
let owner: String let owner: String
let wrapped: XMLElement let wrapped: XMLElement
@ -62,8 +62,8 @@ struct Roster: Identifiable, Equatable {
return RosterSubsriptionType(rawValue: str) ?? .none return RosterSubsriptionType(rawValue: str) ?? .none
} }
static func == (_ rhs: Roster, _ lhs: Roster) -> Bool { static func == (_ rhs: RosterItem, _ lhs: RosterItem) -> Bool {
rhs.id == lhs.id rhs.id == lhs.id && rhs.wrapped == lhs.wrapped
} }
} }

View file

@ -1,6 +1,6 @@
import Foundation import Foundation
struct XMLElement: Codable, CustomStringConvertible { struct XMLElement: Codable, Equatable, CustomStringConvertible {
let name: String let name: String
let xmlns: String? let xmlns: String?
let attributes: [String: String] let attributes: [String: String]

View file

@ -1,6 +1,7 @@
import Foundation import Foundation
// TODO: add versioning (XEP-0237) if needed // TODO: add versioning (XEP-0237) if needed
// TODO: implement error catching
final class RosterModule: XmppModule { final class RosterModule: XmppModule {
let id = "Roseter module" let id = "Roseter module"
@ -31,12 +32,11 @@ final class RosterModule: XmppModule {
} }
case .stanzaInbound(let stanza): 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" { if let query = stanza.wrapped.nodes.first(where: { $0.name == "query" }), query.xmlns == "jabber:iq:roster" {
return await processRoster(state: state, xml: query) return await processRoster(state: state, stanza: stanza)
} } else {
}
return nil return nil
}
default: default:
return nil return nil
@ -45,28 +45,40 @@ final class RosterModule: XmppModule {
} }
private extension RosterModule { private extension RosterModule {
func processRoster(state: ClientState, xml: XMLElement) async -> Event? { 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 // get exists roster items
var existItems: [Roster] = [] var existItems: [RosterItem] = []
if let data = await storage?.getRoster(jid: state.jid), let decoded = try? JSONDecoder().decode([XMLElement].self, from: data) { 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) } existItems = decoded.compactMap { RosterItem(wrap: $0, owner: state.jid) }
} }
// extract items from incoming xml // process push (.set from server)
var newItems = xml.nodes if stanza.type == .iq(.set) {
.compactMap { Roster(wrap: $0, owner: state.jid) } guard
let item = query.nodes.first(where: { $0.name == "item" }),
// manage it ????? let new = RosterItem(wrap: item, owner: state.jid)
var roster: [XMLElement] = newItems.map { $0.wrapped } else { return nil }
existItems = existItems.filter { $0.jid != new.jid }
// save updated roster existItems.append(new)
if let data = try? JSONEncoder().encode(roster) { guard let data = try? JSONEncoder().encode(existItems.map { $0.wrapped }) else { return nil }
await storage?.setRoster(jid: state.jid, roster: data) 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)
return .rosterUpdated
} else {
return nil return nil
} }
} }
}
// <iq to='testmon3@test.anal.company/TwtWkVOZ3liz' type='result' id='7l899q9r'> // <iq to='testmon3@test.anal.company/TwtWkVOZ3liz' type='result' id='7l899q9r'>
// <query ver='27' xmlns='jabber:iq:roster'> // <query ver='27' xmlns='jabber:iq:roster'>