Refactor parsing inbound messages (#577)

* DRY + helper extension to get data from Message

* Flatten guard > do > if > switch + use `try?`

* Use failable init instead of a global function
This commit is contained in:
Islam
2025-09-11 19:18:06 +02:00
committed by GitHub
parent e72fe50ffa
commit 2f83433247
+30 -31
View File
@@ -382,27 +382,13 @@ final class NostrRelayManager: ObservableObject {
switch result { switch result {
case .success(let message): case .success(let message):
switch message {
case .string(let text):
// Parse off-main to reduce UI jank, then hop back for state updates // Parse off-main to reduce UI jank, then hop back for state updates
Task.detached(priority: .utility) { Task.detached(priority: .utility) {
guard let parsed = parseInboundMessage(text) else { return } guard let parsed = ParsedInbound(message) else { return }
await MainActor.run { await MainActor.run {
NostrRelayManager.shared.handleParsedMessage(parsed, from: relayUrl) NostrRelayManager.shared.handleParsedMessage(parsed, from: relayUrl)
} }
} }
case .data(let data):
if let text = String(data: data, encoding: .utf8) {
Task.detached(priority: .utility) {
guard let parsed = parseInboundMessage(text) else { return }
await MainActor.run {
NostrRelayManager.shared.handleParsedMessage(parsed, from: relayUrl)
}
}
}
@unknown default:
break
}
// Continue receiving // Continue receiving
Task { @MainActor in Task { @MainActor in
@@ -634,42 +620,55 @@ private enum ParsedInbound {
case ok(eventId: String, success: Bool, reason: String) case ok(eventId: String, success: Bool, reason: String)
case eose(subscriptionId: String) case eose(subscriptionId: String)
case notice(String) case notice(String)
}
// Off-main JSON parse to avoid UI jank; pure function, not actor-isolated init?(_ message: URLSessionWebSocketTask.Message) {
private func parseInboundMessage(_ message: String) -> ParsedInbound? { guard let data = message.data,
guard let data = message.data(using: .utf8) else { return nil } let array = try? JSONSerialization.jsonObject(with: data) as? [Any],
do {
if let array = try JSONSerialization.jsonObject(with: data) as? [Any],
array.count >= 2, array.count >= 2,
let type = array[0] as? String { let type = array[0] as? String
else {
return nil
}
switch type { switch type {
case "EVENT": case "EVENT":
if array.count >= 3, if array.count >= 3,
let subId = array[1] as? String, let subId = array[1] as? String,
let eventDict = array[2] as? [String: Any] { let eventDict = array[2] as? [String: Any],
let event = try NostrEvent(from: eventDict) let event = try? NostrEvent(from: eventDict) {
return .event(subId: subId, event: event) self = .event(subId: subId, event: event)
} }
case "EOSE": case "EOSE":
if let subId = array[1] as? String { return .eose(subscriptionId: subId) } if let subId = array[1] as? String {
self = .eose(subscriptionId: subId)
}
case "OK": case "OK":
if array.count >= 3, if array.count >= 3,
let eventId = array[1] as? String, let eventId = array[1] as? String,
let success = array[2] as? Bool { let success = array[2] as? Bool {
let reason = array.count >= 4 ? (array[3] as? String ?? "no reason given") : "no reason given" let reason = array.count >= 4 ? (array[3] as? String ?? "no reason given") : "no reason given"
return .ok(eventId: eventId, success: success, reason: reason) self = .ok(eventId: eventId, success: success, reason: reason)
} }
case "NOTICE": case "NOTICE":
if array.count >= 2, let msg = array[1] as? String { return .notice(msg) } if array.count >= 2, let msg = array[1] as? String {
self = .notice(msg)
}
default: default:
break
}
return nil return nil
} }
}
private extension URLSessionWebSocketTask.Message {
var data: Data? {
switch self {
case .string(let text): text.data(using: .utf8)
case .data(let data): data
@unknown default: nil
} }
} catch {
// Ignore
} }
return nil
} }
// MARK: - Nostr Protocol Types // MARK: - Nostr Protocol Types