roster get
This commit is contained in:
parent
ed82109382
commit
dfa9ce3ec5
|
@ -4,4 +4,5 @@ enum AppAction: Codable {
|
|||
case changeFlow(_ flow: AppFlow)
|
||||
case accountsAction(_ action: AccountsAction)
|
||||
case registrationAction(_ action: RegistrationAction)
|
||||
case contactsAction(_ action: ContactsAction)
|
||||
}
|
||||
|
|
3
Snikket/AppCore/Actions/ContactsActions.swift
Normal file
3
Snikket/AppCore/Actions/ContactsActions.swift
Normal file
|
@ -0,0 +1,3 @@
|
|||
enum ContactsAction: Codable {
|
||||
case remoteContactsFetched
|
||||
}
|
|
@ -26,6 +26,9 @@ extension AppStore {
|
|||
} else {
|
||||
dispatch(.registrationAction(.clientStateChanged(.loginError)))
|
||||
}
|
||||
|
||||
case .clientRosterFetched(let contacts):
|
||||
print(contacts)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Foundation
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
let isConsoleLoggingEnabled = false
|
||||
|
||||
|
@ -13,11 +13,11 @@ let isConsoleLoggingEnabled = false
|
|||
default:
|
||||
let timeStr = dateFormatter.string(from: Date())
|
||||
var actionStr = "\(action)"
|
||||
actionStr = String(actionStr.prefix(200)) + " ..."
|
||||
actionStr = String(actionStr.prefix(500)) + " ..."
|
||||
var stateStr = "\(state)"
|
||||
stateStr = String(stateStr.prefix(200)) + " ..."
|
||||
stateStr = String(stateStr.prefix(500)) + " ..."
|
||||
|
||||
let str = "\(timeStr) ℹ️ \(actionStr)\n\(timeStr) ✅ \(stateStr)\n"
|
||||
let str = "\(timeStr) ℹ️ \(actionStr)\n\(timeStr) ✅ \(stateStr)\n\n"
|
||||
print(str)
|
||||
if isConsoleLoggingEnabled {
|
||||
NSLog(str)
|
||||
|
|
|
@ -17,6 +17,9 @@ extension AppState {
|
|||
|
||||
case let .registrationAction(action):
|
||||
RegistrationState.reducer(state: &state.registrationState, action: action)
|
||||
|
||||
case let .contactsAction(action):
|
||||
print("action")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Foundation
|
||||
import KeychainAccess
|
||||
|
||||
// CHANGE THIS PREFIX TO YOURS!!!
|
||||
// TODO: CHANGE THIS PREFIX TO YOURS!!!
|
||||
private let group: String = "U6CKGHL5VR.imt.narayana.snikket.ios"
|
||||
private let path: String = "snikket.db.key"
|
||||
private let service: String = "snikket"
|
||||
|
|
17
Snikket/View/Components/ChatCell.swift
Normal file
17
Snikket/View/Components/ChatCell.swift
Normal file
|
@ -0,0 +1,17 @@
|
|||
import SwiftUI
|
||||
|
||||
struct ChatCell: View {
|
||||
var body: some View {
|
||||
Text("O")
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
struct ChatCell_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
showPreview(stateConfig: { state in
|
||||
state.currentFlow = .chats
|
||||
})
|
||||
}
|
||||
}
|
||||
#endif
|
|
@ -1,30 +1,48 @@
|
|||
import SwiftUI
|
||||
|
||||
// A SWiftUI big view which represent chats in Telegram-like manner
|
||||
struct ChatsMainScreen: View {
|
||||
@EnvironmentObject var store: AppStore
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
SharedHeader(title: "Chats")
|
||||
|
||||
VStack {
|
||||
Text("Under construction")
|
||||
// list of chats
|
||||
Text("s")
|
||||
}
|
||||
.frame(maxHeight: .infinity)
|
||||
|
||||
.frame(maxHeight: .infinity)
|
||||
SharedTabBar()
|
||||
.frame(height: 48)
|
||||
}
|
||||
.background(Color.Main.backgroundLight)
|
||||
.background(Color.Main.backgroundLight)
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
struct ChatsMainScreen_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
showPreview(stateConfig: { state in
|
||||
state.currentFlow = .chats
|
||||
})
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// struct ChatsMainScreen: View {
|
||||
// @EnvironmentObject var store: AppStore
|
||||
//
|
||||
// var body: some View {
|
||||
// VStack {
|
||||
// SharedHeader(title: "Chats")
|
||||
//
|
||||
// VStack {
|
||||
// Text("Under construction")
|
||||
// }
|
||||
// .frame(maxHeight: .infinity)
|
||||
//
|
||||
// SharedTabBar()
|
||||
// .frame(height: 48)
|
||||
// }
|
||||
// .background(Color.Main.backgroundLight)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// #if DEBUG
|
||||
// struct ChatsMainScreen_Previews: PreviewProvider {
|
||||
// static var previews: some View {
|
||||
// showPreview(stateConfig: { state in
|
||||
// state.currentFlow = .chats
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// #endif
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
// swiftlint:disable all
|
||||
/*
|
||||
This file contains full state for xcode preview purposes.
|
||||
Please, change this file only if you update state during development
|
||||
(if you need something different in state - just change it in preview locally)
|
||||
|
||||
Will work only on debug builds
|
||||
*/
|
||||
// This file contains full state for xcode preview purposes.
|
||||
// Please, change this file only if you update state during development
|
||||
// (if you need something different in state - just change it in preview locally)
|
||||
//
|
||||
// Will work only on debug builds
|
||||
|
||||
#if DEBUG
|
||||
import SwiftUI
|
||||
|
|
|
@ -7,6 +7,7 @@ enum ClientAction: Codable {
|
|||
case streamAction(_ action: StreamAction)
|
||||
case authAction(_ action: AuthAction)
|
||||
case bindResourceAction(_ action: BindResourceAction)
|
||||
case rosterAction(_ action: RosterAction)
|
||||
case externalServiceAction(_ action: ExternalServicesAction)
|
||||
case callAction(_ action: CallAction)
|
||||
}
|
||||
|
|
5
XMPPSwift/Client/Actions/RosterActions.swift
Normal file
5
XMPPSwift/Client/Actions/RosterActions.swift
Normal file
|
@ -0,0 +1,5 @@
|
|||
import Foundation
|
||||
|
||||
enum RosterAction: Codable {
|
||||
case getRoster
|
||||
}
|
|
@ -13,4 +13,5 @@ extension String {
|
|||
let data = Data(base64Encoded: self)!
|
||||
return String(data: data, encoding: .utf8)!
|
||||
}
|
||||
// swiftlint:enable force_unwrapping
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import Foundation
|
||||
import Combine
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
final class BindResourceMiddleware {
|
||||
private var reqId = ""
|
||||
|
||||
func middleware(state: ClientState, action: ClientAction) -> AnyPublisher<ClientAction, Never> {
|
||||
func middleware(state _: ClientState, action: ClientAction) -> AnyPublisher<ClientAction, Never> {
|
||||
switch action {
|
||||
case .bindResourceAction(.bindResource):
|
||||
let resourceName = "iPhone-" + XMLElement.randomId
|
||||
|
@ -28,11 +28,13 @@ final class BindResourceMiddleware {
|
|||
|
||||
case .parserAction(.elementParsed(let element)):
|
||||
if element.name == "iq", element.attributes["id"] == reqId, element.attributes["type"] == "result" {
|
||||
if let jidStr = element
|
||||
.nodes.first(where: { $0.name == "bind" })?
|
||||
.nodes.first(where: { $0.name == "jid" })?
|
||||
.content,
|
||||
let newJid = try? JID(jidStr) {
|
||||
if
|
||||
let jidStr = element
|
||||
.nodes.first(where: { $0.name == "bind" })?
|
||||
.nodes.first(where: { $0.name == "jid" })?
|
||||
.content,
|
||||
let newJid = try? JID(jidStr)
|
||||
{
|
||||
return Just(ClientAction.bindResourceAction(.updateJid(newJid)))
|
||||
.eraseToAnyPublisher()
|
||||
} else {
|
||||
|
@ -47,6 +49,10 @@ final class BindResourceMiddleware {
|
|||
return Just(ClientAction.bindResourceAction(.resourceBound))
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
case .bindResourceAction(.resourceBound):
|
||||
return Just(ClientAction.rosterAction(.getRoster))
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
default:
|
||||
return Empty().eraseToAnyPublisher()
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import Foundation
|
||||
import Combine
|
||||
import Foundation
|
||||
|
||||
let isConsoleLoggingEnabled = false
|
||||
|
||||
|
@ -12,10 +12,11 @@ final class LoggerMiddleware {
|
|||
|
||||
default:
|
||||
let timeStr = dateFormatter.string(from: Date())
|
||||
let len = lineLength(action)
|
||||
var actionStr = "\(action)"
|
||||
let ends = actionStr.count > 800 ? "..." : ""
|
||||
actionStr = String(actionStr.prefix(800)) + ends
|
||||
let str = "\(timeStr) xmpp-client \(state.jid): \(lineSymbol(action))\(actionStr)"
|
||||
let ends = actionStr.count > len ? "..." : ""
|
||||
actionStr = String(actionStr.prefix(len)) + ends
|
||||
let str = "\(timeStr) 🔁 xmpp-client \(state.jid): \(lineSymbol(action))\(actionStr)"
|
||||
print(str)
|
||||
if isConsoleLoggingEnabled {
|
||||
NSLog(str)
|
||||
|
@ -51,4 +52,14 @@ final class LoggerMiddleware {
|
|||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
private func lineLength(_ action: ClientAction) -> Int {
|
||||
switch action {
|
||||
case .rosterAction:
|
||||
return 1000
|
||||
|
||||
default:
|
||||
return 100
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
45
XMPPSwift/Client/Middleware/RosterMiddleware.swift
Normal file
45
XMPPSwift/Client/Middleware/RosterMiddleware.swift
Normal file
|
@ -0,0 +1,45 @@
|
|||
import Combine
|
||||
import Foundation
|
||||
|
||||
final class RosterMiddleware {
|
||||
private var reqId = ""
|
||||
|
||||
func middleware(state: ClientState, action: ClientAction) -> AnyPublisher<ClientAction, Never> {
|
||||
switch action {
|
||||
case .rosterAction(.getRoster):
|
||||
reqId = XMLElement.randomId
|
||||
let req = XMLElement(
|
||||
name: "iq",
|
||||
xmlns: nil,
|
||||
attributes: [
|
||||
"id": reqId,
|
||||
"from": state.jid.description,
|
||||
"type": "get"
|
||||
],
|
||||
content: nil,
|
||||
nodes: [
|
||||
.init(
|
||||
name: "query",
|
||||
xmlns: "jabber:iq:roster",
|
||||
attributes: [:],
|
||||
content: nil,
|
||||
nodes: []
|
||||
)
|
||||
]
|
||||
)
|
||||
return Just(ClientAction.streamAction(.streamSend(req)))
|
||||
.eraseToAnyPublisher()
|
||||
|
||||
case .parserAction(.elementParsed(let element)):
|
||||
if element.name == "iq", element.attributes["id"] == reqId, element.attributes["type"] == "result" {
|
||||
print(element)
|
||||
return Empty().eraseToAnyPublisher()
|
||||
} else {
|
||||
return Empty().eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
default:
|
||||
return Empty().eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import Foundation
|
||||
import Combine
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
final class StreamMiddleware {
|
||||
|
@ -72,7 +72,7 @@ final class StreamMiddleware {
|
|||
break
|
||||
}
|
||||
}
|
||||
let text = element.nodes.first(where: { $0.name == "text"})?.content
|
||||
let text = element.nodes.first(where: { $0.name == "text" })?.content
|
||||
let error = StreamError(type: errType, text: text)
|
||||
return Just(ClientAction.streamAction(.streamError(error)))
|
||||
.eraseToAnyPublisher()
|
||||
|
@ -134,20 +134,23 @@ private extension StreamMiddleware {
|
|||
for node in element.nodes {
|
||||
switch node.name {
|
||||
case "mechanisms":
|
||||
let mechanisms = node.nodes.map({
|
||||
let mechanisms = node.nodes.map {
|
||||
StreamFeature(
|
||||
$0.name,
|
||||
node.nodes.map({ $0.name }).contains("required"),
|
||||
node.nodes.map { $0.name }.contains("required"),
|
||||
_additionalInfo: $0.content
|
||||
)
|
||||
})
|
||||
}
|
||||
features.append(contentsOf: mechanisms)
|
||||
|
||||
case "register", "auth":
|
||||
features.append(.init(node.name, false, _additionalInfo: node.xmlns))
|
||||
|
||||
case "bind":
|
||||
features.append(.init(node.name, true))
|
||||
|
||||
default:
|
||||
features.append(StreamFeature(node.name, node.nodes.map({ $0.name }).contains("required")))
|
||||
features.append(StreamFeature(node.name, node.nodes.map { $0.name }.contains("required")))
|
||||
}
|
||||
}
|
||||
return features
|
||||
|
|
|
@ -55,3 +55,4 @@ private extension JID {
|
|||
}
|
||||
}
|
||||
}
|
||||
// swiftlint:enable large_tuple
|
||||
|
|
16
XMPPSwift/Client/Models/Roster.swift
Normal file
16
XMPPSwift/Client/Models/Roster.swift
Normal file
|
@ -0,0 +1,16 @@
|
|||
import Foundation
|
||||
|
||||
public enum ContactSubscription: String {
|
||||
case none
|
||||
case to
|
||||
case from
|
||||
case both
|
||||
case remove
|
||||
}
|
||||
|
||||
public struct RosterContact {
|
||||
let jid: JID
|
||||
let name: String
|
||||
let subscription: ContactSubscription
|
||||
let groups: [String]
|
||||
}
|
|
@ -171,7 +171,10 @@ final class Connection {
|
|||
}
|
||||
|
||||
#if DEBUG
|
||||
print("\(direction)\(src): \(String(bytes: data, encoding: .ascii) ?? "?")")
|
||||
let flg = false
|
||||
if flg {
|
||||
print("\(direction)\(src): \(String(bytes: data, encoding: .ascii) ?? "?")")
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,6 +29,9 @@ extension ClientState {
|
|||
default:
|
||||
break
|
||||
}
|
||||
|
||||
case .rosterAction:
|
||||
break
|
||||
|
||||
case .externalServiceAction(let action):
|
||||
ExternalServicesState.reducer(state: &state.externalServicesState, action: action)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import Foundation
|
||||
|
||||
// swiftlint:disable large_tuple
|
||||
private typealias Replace = (orig: String, asName: String, asCode: String)
|
||||
private let replaces = [
|
||||
Replace("&", "&", "&"),
|
||||
|
@ -8,6 +9,7 @@ private let replaces = [
|
|||
Replace("\"", """, """),
|
||||
Replace("'", "'", "'")
|
||||
]
|
||||
// swiftlint:enable large_tuple
|
||||
|
||||
enum XMLEscaping {
|
||||
static func unescape(_ value: String) -> String {
|
||||
|
|
|
@ -28,6 +28,7 @@ final class XMPPClient {
|
|||
StreamMiddleware().middleware,
|
||||
AuthMiddleware().middleware,
|
||||
BindResourceMiddleware().middleware,
|
||||
RosterMiddleware().middleware,
|
||||
ExternalServiceMiddleware().middleware,
|
||||
callMiddleware.middleware
|
||||
]
|
||||
|
|
|
@ -4,28 +4,33 @@ import Foundation
|
|||
// MARK: Private action processing
|
||||
extension XMPPService {
|
||||
func _doAction(_ action: Action) {
|
||||
switch action.type {
|
||||
case .initClient:
|
||||
// actions with no client JID
|
||||
if case .deinitClient = action.type {
|
||||
// TODO: check that client disappears from memory
|
||||
clients.removeValue(forKey: action.jid)
|
||||
}
|
||||
if case .initClient = action.type {
|
||||
let client = XMPPClient(jid: action.jid, updateCallback: clientUpdated)
|
||||
clients[action.jid] = client
|
||||
feed.send(.init(jid: action.jid, eventType: .clientInitialised))
|
||||
}
|
||||
|
||||
// actions with client JID
|
||||
guard let client = clients[action.jid] else {
|
||||
feed.send(.init(jid: action.jid, eventType: .serviceError(.unknownClient)))
|
||||
return
|
||||
}
|
||||
switch action.type {
|
||||
case .initClient:
|
||||
break
|
||||
|
||||
case .deinitClient:
|
||||
clients.removeValue(forKey: action.jid)
|
||||
// TODO: check that client disappears from memory
|
||||
break
|
||||
|
||||
case .connectToServer:
|
||||
guard let client = clients[action.jid] else {
|
||||
feed.send(.init(jid: action.jid, eventType: .serviceError(.unknownClient)))
|
||||
return
|
||||
}
|
||||
client.reconnect()
|
||||
|
||||
case .login(let password):
|
||||
guard let client = clients[action.jid] else {
|
||||
feed.send(.init(jid: action.jid, eventType: .serviceError(.unknownClient)))
|
||||
return
|
||||
}
|
||||
client.loginWith(password)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,6 +42,7 @@ public extension XMPPService {
|
|||
case clientInitialised
|
||||
case serverConnectionEstablished
|
||||
case clientAuthResult(authorized: Bool)
|
||||
case clientRosterFetched(contacts: [RosterContact])
|
||||
}
|
||||
|
||||
struct Event {
|
||||
|
|
Loading…
Reference in a new issue