Server information (Monal-style): - Add ServerInformation model and ServerInfoView reachable from Settings, showing server software (XEP-0092), disco#info identities and features, conference (MUC) services via disco#items, and STUN/TURN via XEP-0215. - Add a curated XEP capability list with per-XEP success/error status derived from server/account disco features and connection flags. - Detect PEP (0163) from account disco pubsub#* features, HTTP Upload (0363) from the main domain plus its components, and add Roster Versioning (0237), Pre-Authenticated Roster Subscription (0379), and SASL SCRAM Downgrade Protection (0474, neutral because Martin does not expose SSDP). Connection statistics: - Add LumaConnectionStatsModule, an XmppStanzaFilter registered before stream management, to count sent / acknowledged / received stanzas. - Track last login and SMACKS session timestamps, and read SASL mechanisms and Client State Indication from the raw stream features. MAM / OMEMO handling: - Prefer a plaintext <body> fallback when OMEMO decryption fails instead of showing "failed to decrypt". - Keep the optimistic text of our own outgoing message when its echo cannot be decrypted back, instead of replacing it with a placeholder. Emoji picker: - Add EmojiCatalog and a cross-platform EmojiPickerView. - Open the picker for reactions (replacing the static quick list) and from the composer's smiley button, inserting the chosen emoji into the draft.
69 lines
2.1 KiB
Swift
69 lines
2.1 KiB
Swift
import Foundation
|
|
import Martin
|
|
|
|
/// Counts XEP-0198 stanza statistics (sent / acknowledged / received) at the
|
|
/// stream level so the server-details screen can mirror Monal's connection
|
|
/// statistics. Registered as an `XmppStanzaFilter` *before* stream management
|
|
/// so it can also observe the server's `<a h='N'>` acknowledgements.
|
|
final class LumaConnectionStatsModule: XmppModuleBase, XmppModule, XmppStanzaFilter {
|
|
static let ID = "lumaConnectionStats"
|
|
static let smNamespace = "urn:xmpp:sm:3"
|
|
|
|
let criteria = Criteria.empty()
|
|
let features: [String] = []
|
|
|
|
private let lock = NSLock()
|
|
private var sent: UInt32 = 0
|
|
private var received: UInt32 = 0
|
|
private var acknowledged: UInt32 = 0
|
|
|
|
struct Snapshot: Equatable, Sendable {
|
|
let sent: UInt32
|
|
let acknowledged: UInt32
|
|
let received: UInt32
|
|
|
|
var unacknowledged: UInt32 {
|
|
sent > acknowledged ? sent - acknowledged : 0
|
|
}
|
|
}
|
|
|
|
var snapshot: Snapshot {
|
|
lock.lock()
|
|
defer { lock.unlock() }
|
|
return Snapshot(sent: sent, acknowledged: acknowledged, received: received)
|
|
}
|
|
|
|
func processIncoming(stanza: Stanza) -> Bool {
|
|
if stanza.xmlns == Self.smNamespace, stanza.name == "a" {
|
|
if let raw = stanza.getAttribute("h"), let value = UInt32(raw) {
|
|
lock.lock()
|
|
acknowledged = value
|
|
lock.unlock()
|
|
}
|
|
// Let StreamManagementModule consume the acknowledgement too.
|
|
return false
|
|
}
|
|
if Self.isCountable(stanza) {
|
|
lock.lock()
|
|
received += 1
|
|
lock.unlock()
|
|
}
|
|
return false
|
|
}
|
|
|
|
func processOutgoing(stanza: Stanza) {
|
|
guard Self.isCountable(stanza) else { return }
|
|
lock.lock()
|
|
sent += 1
|
|
lock.unlock()
|
|
}
|
|
|
|
func process(stanza: Stanza) throws {
|
|
// Criteria is empty, so this module is never selected by the dispatcher.
|
|
}
|
|
|
|
private static func isCountable(_ stanza: Stanza) -> Bool {
|
|
stanza.name == "iq" || stanza.name == "message" || stanza.name == "presence"
|
|
}
|
|
}
|