another.im-ios/AnotherXMPP/models/Stanza.swift
2025-01-14 04:19:12 +01:00

155 lines
3.9 KiB
Swift

import Foundation
enum StanzaType: Codable & Equatable {
enum IqType: String, Codable & Equatable {
case get
case set
case result
case error
}
enum MessageType: String, Codable & Equatable {
case chat
case groupchat
case headline
case normal
case error
case none
}
enum PresenceType: String, Codable & Equatable {
case subscribe
case unsubscribe
case subscribed
case unsubscribed
}
case iq(IqType)
case message(MessageType)
case presence(PresenceType)
// Should never appear
case unknown
}
struct Stanza {
let wrapped: XMLElement
init?(wrap: XMLElement) {
guard ["iq", "message", "presence"].contains(wrap.name) else { return nil }
wrapped = wrap
}
var type: StanzaType {
switch wrapped.name {
case "iq":
if let type = StanzaType.IqType(rawValue: wrapped.attributes["type"] ?? "") {
return .iq(type)
} else {
warn()
return .unknown
}
case "message":
let type = StanzaType.MessageType(rawValue: wrapped.attributes["type"] ?? "none") ?? .none
return .message(type)
case "presence":
if let type = StanzaType.PresenceType(rawValue: wrapped.attributes["type"] ?? "") {
return .presence(type)
} else {
warn()
return .unknown
}
default:
warn()
return .unknown
}
}
var id: String? {
wrapped.attributes["id"]
}
}
// MARK: Init IQ Stanza
extension Stanza {
static func iqGet(payload: XMLElement) -> Stanza? {
buildIq(direction: "get", from: nil, to: nil, payload: payload)
}
static func iqGet(from: String, payload: XMLElement) -> Stanza? {
buildIq(direction: "get", from: from, to: nil, payload: payload)
}
static func iqSet(payload: XMLElement) -> Stanza? {
buildIq(direction: "set", from: nil, to: nil, payload: payload)
}
static func iqSet(from: String, payload: XMLElement) -> Stanza? {
buildIq(direction: "set", from: from, to: nil, payload: payload)
}
static func iqResult(payload: XMLElement) -> Stanza? {
buildIq(direction: "result", from: nil, to: nil, payload: payload)
}
static func iqResult(from: String, payload: XMLElement) -> Stanza? {
buildIq(direction: "result", from: from, to: nil, payload: payload)
}
// build iq stanza
private static func buildIq(direction: String, from: String?, to: String?, payload: XMLElement) -> Stanza? {
var attributes = ["id": XMLElement.randomId, "type": direction]
if let from {
attributes["from"] = from
}
if let to {
attributes["to"] = to
}
let req = XMLElement(
name: "iq",
xmlns: nil,
attributes: attributes,
content: nil,
nodes: [payload]
)
return Stanza(wrap: req)
}
}
// MARK: Presence Stanza
// case chat
// case online
// case away
// case xa
// case dnd
extension Stanza {
static func initialPresence() -> Stanza {
Stanza(wrap: XMLElement(name: "presence", xmlns: nil, attributes: [:], content: nil, nodes: []))
}
}
// MARK: Additional
private extension Stanza {
func warn() {
print("Something went wrong! with \(wrapped.stringRepresentation.prettyStr)")
}
}
// MARK: Stanza registry
// this is Actor which we use to keep request-response stanza logic
final actor StanzaRegistry {
var stanzas: [Stanza] = []
func enqueue(_ stanza: Stanza) {
stanzas.append(stanza)
}
func deuque(for stanzaId: String) -> Stanza? {
guard let index = stanzas.firstIndex(where: { $0.id == stanzaId }) else { return nil }
return stanzas.remove(at: index)
}
}