wip
This commit is contained in:
parent
5510dd3900
commit
0be3f68710
|
@ -5,7 +5,8 @@ import SwiftUI
|
|||
struct AnotherIMApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
StartScreen()
|
||||
// StartScreen()
|
||||
TestScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,5 @@
|
|||
import SwiftUI
|
||||
|
||||
// let login = "kudahtk@conversations.im"
|
||||
// let pass = "derevo77!"
|
||||
|
||||
// let login = "testmon4@test.anal.company"
|
||||
// let pass = "12345"
|
||||
|
||||
let login = "testmon3@test.anal.company"
|
||||
let pass = "12345"
|
||||
|
||||
struct StartScreen: View {
|
||||
var body: some View {
|
||||
ZStack {
|
||||
|
@ -19,79 +10,5 @@ struct StartScreen: View {
|
|||
.frame(width: 200, height: 200)
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
.onAppear {
|
||||
// doTestA()
|
||||
doMainTest()
|
||||
}
|
||||
}
|
||||
|
||||
func doMainTest() {
|
||||
let userAgent = UserAgent(
|
||||
uuid: "aaa65fa7-5555-4749-d1a5-740edbf81764",
|
||||
software: "another.im",
|
||||
device: "iOS"
|
||||
)
|
||||
let cls = XMPPClient(storage: TestStorage(), userAgent: userAgent)
|
||||
|
||||
// swiftlint:disable:next force_try
|
||||
let jid = try! JID(login)
|
||||
cls.tryLogin(jid: jid, credentialsId: UUID())
|
||||
}
|
||||
|
||||
func doTestA() {
|
||||
let xml = XMLElement(
|
||||
name: "test-me",
|
||||
xmlns: "urn:test:me",
|
||||
attributes: ["some1": "some-val-1", "type": "test-req"],
|
||||
content: "asdffweqfqefq34234t2tergfsagewr",
|
||||
nodes: [
|
||||
XMLElement(
|
||||
name: "sub-test-me",
|
||||
xmlns: "urn:test:me",
|
||||
attributes: ["some1": "some-val-1"],
|
||||
content: "asdffweqfqefq34234t2tergfsagewr",
|
||||
nodes: [
|
||||
XMLElement(
|
||||
name: "sub2-test-me",
|
||||
xmlns: "urn:test:me",
|
||||
attributes: [:],
|
||||
content: nil,
|
||||
nodes: []
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
print("before")
|
||||
print(xml, "\n")
|
||||
print("after:")
|
||||
let encoded = try? JSONEncoder().encode(xml)
|
||||
if let encoded {
|
||||
print(String(decoding: encoded, as: UTF8.self))
|
||||
let xml2 = try? JSONDecoder().decode(XMLElement.self, from: encoded)
|
||||
if let xml2 {
|
||||
print("\n\n\n")
|
||||
print(xml2)
|
||||
}
|
||||
print("after all\n")
|
||||
print("\(encoded as NSData)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final class TestStorage: XMPPStorage {
|
||||
private var rosterVer: [String: String] = [:]
|
||||
|
||||
func getRosterVersion(jid: JID) async -> String? {
|
||||
rosterVer[jid.bare]
|
||||
}
|
||||
|
||||
func setRosterVersion(jid: JID, ver: String) async {
|
||||
rosterVer[jid.bare] = ver
|
||||
}
|
||||
|
||||
func getCredentialsByUUID(_ uuid: UUID) async -> Credentials? {
|
||||
print(uuid)
|
||||
return ["password": pass]
|
||||
}
|
||||
}
|
||||
|
|
112
AnotherIM/View/TestScreen.swift
Normal file
112
AnotherIM/View/TestScreen.swift
Normal file
|
@ -0,0 +1,112 @@
|
|||
import SwiftUI
|
||||
|
||||
// let login = "kudahtk@conversations.im"
|
||||
// let pass = "derevo77!"
|
||||
|
||||
// let login = "testmon4@test.anal.company"
|
||||
// let pass = "12345"
|
||||
|
||||
let login = "testmon3@test.anal.company"
|
||||
let pass = "12345"
|
||||
|
||||
//
|
||||
let userAgent = UserAgent(
|
||||
uuid: "aaa65fa7-5555-4749-d1a5-740edbf81764",
|
||||
software: "another.im",
|
||||
device: "iOS"
|
||||
)
|
||||
let cls = XMPPClient(storage: TestStorage(), userAgent: userAgent)
|
||||
|
||||
struct TestScreen: View {
|
||||
var body: some View {
|
||||
ZStack {
|
||||
Color.Material.Background.light
|
||||
VStack {
|
||||
Button {
|
||||
doConnect()
|
||||
} label: {
|
||||
Text("Connect")
|
||||
.padding()
|
||||
.background { Color.blue.opacity(0.4) }
|
||||
}
|
||||
Button {
|
||||
// cls.requestRoster()
|
||||
} label: {
|
||||
Text("Request roster")
|
||||
.padding()
|
||||
.background { Color.blue.opacity(0.4) }
|
||||
}
|
||||
}
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
|
||||
func doConnect() {
|
||||
// swiftlint:disable:next force_try
|
||||
let jid = try! JID(login)
|
||||
cls.tryLogin(jid: jid, credentialsId: UUID())
|
||||
}
|
||||
}
|
||||
|
||||
final class TestStorage: XMPPStorage {
|
||||
private var roster: [String: Data] = [:]
|
||||
|
||||
func getCredentialsByUUID(_ uuid: UUID) async -> Credentials? {
|
||||
print(uuid)
|
||||
return ["password": pass]
|
||||
}
|
||||
|
||||
func deleteRoster(jid: JID) async {
|
||||
roster.removeValue(forKey: jid.bare)
|
||||
}
|
||||
|
||||
func getRoster(jid: JID) async -> Data? {
|
||||
roster[jid.bare]
|
||||
}
|
||||
|
||||
func setRoster(jid: JID, roster: Data) async {
|
||||
self.roster[jid.bare] = roster
|
||||
}
|
||||
}
|
||||
|
||||
extension TestScreen {
|
||||
// func doTestA() {
|
||||
// let xml = XMLElement(
|
||||
// name: "test-me",
|
||||
// xmlns: "urn:test:me",
|
||||
// attributes: ["some1": "some-val-1", "type": "test-req"],
|
||||
// content: "asdffweqfqefq34234t2tergfsagewr",
|
||||
// nodes: [
|
||||
// XMLElement(
|
||||
// name: "sub-test-me",
|
||||
// xmlns: "urn:test:me",
|
||||
// attributes: ["some1": "some-val-1"],
|
||||
// content: "asdffweqfqefq34234t2tergfsagewr",
|
||||
// nodes: [
|
||||
// XMLElement(
|
||||
// name: "sub2-test-me",
|
||||
// xmlns: "urn:test:me",
|
||||
// attributes: [:],
|
||||
// content: nil,
|
||||
// nodes: []
|
||||
// )
|
||||
// ]
|
||||
// )
|
||||
// ]
|
||||
// )
|
||||
// print("before")
|
||||
// print(xml, "\n")
|
||||
// print("after:")
|
||||
// let encoded = try? JSONEncoder().encode(xml)
|
||||
// if let encoded {
|
||||
// print(String(decoding: encoded, as: UTF8.self))
|
||||
// let xml2 = try? JSONDecoder().decode(XMLElement.self, from: encoded)
|
||||
// if let xml2 {
|
||||
// print("\n\n\n")
|
||||
// print(xml2)
|
||||
// }
|
||||
// print("after all\n")
|
||||
// print("\(encoded as NSData)")
|
||||
// }
|
||||
// }
|
||||
}
|
|
@ -101,14 +101,17 @@ final class XMPPClient {
|
|||
AuthorizationModule(self.storage),
|
||||
StanzaModule(self.storage),
|
||||
DiscoveryModule(),
|
||||
RosterModule()
|
||||
RosterModule(self.storage)
|
||||
]
|
||||
|
||||
init(storage: any XMPPStorage, userAgent: UserAgent) {
|
||||
self.storage = storage
|
||||
state.userAgent = userAgent
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Public part
|
||||
extension XMPPClient {
|
||||
func tryLogin(jid: JID, credentialsId: UUID) {
|
||||
logger.update(jid.full)
|
||||
Task {
|
||||
|
|
|
@ -7,8 +7,11 @@ protocol XMPPStorage: AnyObject {
|
|||
func getCredentialsByUUID(_ uuid: UUID) async -> Credentials?
|
||||
|
||||
// roster
|
||||
func getRosterVersion(jid: JID) async -> String?
|
||||
func setRosterVersion(jid: JID, ver: String) async
|
||||
func deleteRoster(jid: JID) async
|
||||
// where Data is byte representation of array of roster items
|
||||
// i.e. [XMLElement] -> Data
|
||||
func getRoster(jid: JID) async -> Data?
|
||||
func setRoster(jid: JID, roster: Data) async
|
||||
|
||||
// messages
|
||||
|
||||
|
|
|
@ -27,3 +27,44 @@ enum RosterSubsriptionType: String {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Roster is a "transparent" structure
|
||||
// which is just wrap xml item around
|
||||
struct Roster: Identifiable, Equatable {
|
||||
let owner: String
|
||||
let wrapped: XMLElement
|
||||
|
||||
init?(wrap: XMLElement, owner: JID) {
|
||||
guard
|
||||
wrap.name == "item",
|
||||
wrap.attributes.keys.contains("jid"),
|
||||
wrap.xmlns == "jabber:iq:roster"
|
||||
else { return nil }
|
||||
self.owner = owner.bare
|
||||
wrapped = wrap
|
||||
}
|
||||
|
||||
var id: String {
|
||||
"\(owner)|\(jid?.bare ?? "???")"
|
||||
}
|
||||
|
||||
var jid: JID? {
|
||||
guard let jidStr = wrapped.attributes["jid"] else { return nil }
|
||||
return try? JID(jidStr)
|
||||
}
|
||||
|
||||
var name: String? {
|
||||
wrapped.name
|
||||
}
|
||||
|
||||
var subsription: RosterSubsriptionType {
|
||||
let str = wrapped.attributes["subscription"] ?? "none"
|
||||
return RosterSubsriptionType(rawValue: str) ?? .none
|
||||
}
|
||||
|
||||
static func == (_ rhs: Roster, _ lhs: Roster) -> Bool {
|
||||
rhs.id == lhs.id
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Add groups and annotattions!
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
import Foundation
|
||||
|
||||
// TODO: add versioning (XEP-0237) if needed
|
||||
final class RosterModule: XmppModule {
|
||||
let id = "Roseter module"
|
||||
|
||||
private weak var storage: (any XMPPStorage)?
|
||||
|
||||
init(_ storage: any XMPPStorage) {
|
||||
self.storage = storage
|
||||
}
|
||||
|
||||
func reduce(oldState: ClientState, with _: Event) -> ClientState {
|
||||
oldState
|
||||
}
|
||||
|
@ -13,16 +20,57 @@ final class RosterModule: XmppModule {
|
|||
return .requestRoster
|
||||
|
||||
case .requestRoster:
|
||||
// TODO: check version!
|
||||
let req = Stanza.iqGet(from: state.jid.full, payload: XMLElement(name: "query", xmlns: "jabber:iq:roster", attributes: [:], content: nil, nodes: []))
|
||||
let req = Stanza.iqGet(
|
||||
from: state.jid.full,
|
||||
payload: XMLElement(name: "query", xmlns: "jabber:iq:roster", attributes: [:], content: nil, nodes: [])
|
||||
)
|
||||
if let req {
|
||||
return .stanzaOutbound(req)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
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" {
|
||||
return await processRoster(state: state, xml: query)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension RosterModule {
|
||||
func processRoster(state: ClientState, xml: XMLElement) async -> Event? {
|
||||
// get exists roster items
|
||||
var existItems: [Roster] = []
|
||||
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) }
|
||||
}
|
||||
|
||||
// extract items from incoming xml
|
||||
var newItems = xml.nodes
|
||||
.compactMap { Roster(wrap: $0, owner: state.jid) }
|
||||
|
||||
// manage it ?????
|
||||
var roster: [XMLElement] = newItems.map { $0.wrapped }
|
||||
|
||||
// save updated roster
|
||||
if let data = try? JSONEncoder().encode(roster) {
|
||||
await storage?.setRoster(jid: state.jid, roster: data)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// <iq to='testmon3@test.anal.company/TwtWkVOZ3liz' type='result' id='7l899q9r'>
|
||||
// <query ver='27' xmlns='jabber:iq:roster'>
|
||||
// <item jid='fmodf@conversations.im' subscription='both' xmlns='jabber:iq:roster'/>
|
||||
// <item subscription='to' jid='testmon4@test.anal.company' xmlns='jabber:iq:roster'/>
|
||||
// </query>
|
||||
// </iq>
|
||||
|
|
Loading…
Reference in a new issue