add sse client for real-time backend events
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
67
clients/macos/MagnumOpus/Services/SSEClient.swift
Normal file
67
clients/macos/MagnumOpus/Services/SSEClient.swift
Normal file
@@ -0,0 +1,67 @@
|
||||
import Foundation
|
||||
|
||||
struct ServerEvent {
|
||||
let id: String?
|
||||
let event: String?
|
||||
let data: String
|
||||
}
|
||||
|
||||
@Observable
|
||||
final class SSEClient {
|
||||
private let url: URL
|
||||
private var task: Task<Void, Never>?
|
||||
|
||||
var lastEvent: ServerEvent?
|
||||
|
||||
init(url: URL) {
|
||||
self.url = url
|
||||
}
|
||||
|
||||
func connect(onEvent: @escaping (ServerEvent) -> Void) {
|
||||
task = Task {
|
||||
do {
|
||||
var request = URLRequest(url: url)
|
||||
request.setValue("text/event-stream", forHTTPHeaderField: "Accept")
|
||||
|
||||
let (bytes, _) = try await URLSession.shared.bytes(for: request)
|
||||
|
||||
var currentEvent: String?
|
||||
var currentData = ""
|
||||
var currentId: String?
|
||||
|
||||
for try await line in bytes.lines {
|
||||
if line.isEmpty {
|
||||
if !currentData.isEmpty {
|
||||
let event = ServerEvent(
|
||||
id: currentId,
|
||||
event: currentEvent,
|
||||
data: currentData.trimmingCharacters(in: .newlines)
|
||||
)
|
||||
self.lastEvent = event
|
||||
onEvent(event)
|
||||
currentEvent = nil
|
||||
currentData = ""
|
||||
currentId = nil
|
||||
}
|
||||
} else if line.hasPrefix("data: ") {
|
||||
currentData += String(line.dropFirst(6)) + "\n"
|
||||
} else if line.hasPrefix("event: ") {
|
||||
currentEvent = String(line.dropFirst(7))
|
||||
} else if line.hasPrefix("id: ") {
|
||||
currentId = String(line.dropFirst(4))
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
if !Task.isCancelled {
|
||||
try? await Task.sleep(for: .seconds(5))
|
||||
connect(onEvent: onEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func disconnect() {
|
||||
task?.cancel()
|
||||
task = nil
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user