wip
This commit is contained in:
parent
0be3f68710
commit
7a722e200c
|
@ -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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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'>
|
||||||
|
|
Loading…
Reference in a new issue