117 lines
3.8 KiB
Swift
117 lines
3.8 KiB
Swift
|
import Combine
|
||
|
import Foundation
|
||
|
import Martin
|
||
|
|
||
|
enum ClientState: Equatable {
|
||
|
enum ClientConnectionState {
|
||
|
case connected
|
||
|
case disconnected
|
||
|
}
|
||
|
|
||
|
case disabled
|
||
|
case enabled(ClientConnectionState)
|
||
|
}
|
||
|
|
||
|
final class Client: ObservableObject {
|
||
|
@Published private(set) var state: ClientState = .enabled(.disconnected)
|
||
|
@Published private(set) var credentials: Credentials
|
||
|
|
||
|
private var connection: XMPPClient
|
||
|
private var connectionCancellable: AnyCancellable?
|
||
|
|
||
|
private var rosterManager = RosterManager()
|
||
|
|
||
|
init(credentials: Credentials) {
|
||
|
self.credentials = credentials
|
||
|
state = credentials.isActive ? .enabled(.disconnected) : .disabled
|
||
|
connection = Self.prepareConnection(credentials, rosterManager)
|
||
|
connectionCancellable = connection.$state
|
||
|
.sink { [weak self] state in
|
||
|
guard let self = self else { return }
|
||
|
guard self.credentials.isActive else {
|
||
|
self.state = .disabled
|
||
|
return
|
||
|
}
|
||
|
switch state {
|
||
|
case .connected:
|
||
|
self.state = .enabled(.connected)
|
||
|
|
||
|
default:
|
||
|
self.state = .enabled(.disconnected)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func addRoster(_ roster: Roster) async throws {
|
||
|
_ = try await connection.module(.roster).addItem(
|
||
|
jid: JID(roster.contactBareJid),
|
||
|
name: roster.name,
|
||
|
groups: roster.data.groups
|
||
|
)
|
||
|
}
|
||
|
|
||
|
func deleteRoster(_ roster: Roster) async throws {
|
||
|
_ = try await connection.module(.roster).removeItem(jid: JID(roster.contactBareJid))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
extension Client {
|
||
|
enum ClientLoginResult {
|
||
|
case success(Client)
|
||
|
case failure
|
||
|
}
|
||
|
|
||
|
static func tryLogin(with credentials: Credentials) async -> ClientLoginResult {
|
||
|
let client = Client(credentials: credentials)
|
||
|
do {
|
||
|
try await client.connection.loginAndWait()
|
||
|
return .success(client)
|
||
|
} catch {
|
||
|
return .failure
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private extension Client {
|
||
|
static func prepareConnection(_ credentials: Credentials, _ roster: RosterManager) -> XMPPClient {
|
||
|
let client = XMPPClient()
|
||
|
|
||
|
// register modules
|
||
|
// core modules RFC 6120
|
||
|
client.modulesManager.register(StreamFeaturesModule())
|
||
|
client.modulesManager.register(SaslModule())
|
||
|
client.modulesManager.register(AuthModule())
|
||
|
client.modulesManager.register(SessionEstablishmentModule())
|
||
|
client.modulesManager.register(ResourceBinderModule())
|
||
|
client.modulesManager.register(DiscoveryModule(identity: .init(category: "client", type: "iOS", name: Const.appName)))
|
||
|
|
||
|
// messaging modules RFC 6121
|
||
|
client.modulesManager.register(RosterModule(rosterManager: roster))
|
||
|
client.modulesManager.register(PresenceModule())
|
||
|
|
||
|
// client.modulesManager.register(PubSubModule())
|
||
|
// client.modulesManager.register(MessageModule(chatManager: manager))
|
||
|
// client.modulesManager.register(MessageArchiveManagementModule())
|
||
|
|
||
|
// client.modulesManager.register(MessageCarbonsModule())
|
||
|
|
||
|
// file transfer modules
|
||
|
// client.modulesManager.register(HttpFileUploadModule())
|
||
|
|
||
|
// extensions
|
||
|
client.modulesManager.register(SoftwareVersionModule())
|
||
|
client.modulesManager.register(PingModule())
|
||
|
client.connectionConfiguration.userJid = .init(credentials.bareJid)
|
||
|
client.connectionConfiguration.credentials = .password(password: credentials.pass)
|
||
|
|
||
|
// group chats
|
||
|
// client.modulesManager.register(MucModule(roomManager: manager))
|
||
|
|
||
|
// channels
|
||
|
// client.modulesManager.register(MixModule(channelManager: manager))
|
||
|
|
||
|
// add client to clients
|
||
|
return client
|
||
|
}
|
||
|
}
|