IceCubesApp/Packages/Env/Sources/Env/StreamWatcher.swift

178 lines
5 KiB
Swift
Raw Normal View History

import Combine
import Foundation
import Models
import Network
import Observation
2024-02-02 17:26:24 +00:00
import OSLog
@MainActor
@Observable public class StreamWatcher {
private var client: Client?
private var task: URLSessionWebSocketTask?
private var watchedStreams: [Stream] = []
2023-02-03 18:44:55 +00:00
private var instanceStreamingURL: URL?
2023-01-17 10:36:01 +00:00
private let decoder = JSONDecoder()
private let encoder = JSONEncoder()
2023-07-19 05:46:25 +00:00
private var retryDelay: Int = 10
2023-01-17 10:36:01 +00:00
public enum Stream: String {
2024-01-03 13:59:28 +00:00
case federated = "public"
case local
case user
case direct
}
2023-01-17 10:36:01 +00:00
public var events: [any StreamEvent] = []
public var unreadNotificationsCount: Int = 0
public var latestEvent: (any StreamEvent)?
2024-02-14 11:48:14 +00:00
2024-02-02 17:26:24 +00:00
private let logger = Logger(subsystem: "com.icecubesapp", category: "stream")
2023-01-17 10:36:01 +00:00
public static let shared = StreamWatcher()
2024-02-14 11:48:14 +00:00
private init() {
decoder.keyDecodingStrategy = .convertFromSnakeCase
}
2023-01-17 10:36:01 +00:00
2023-02-03 18:44:55 +00:00
public func setClient(client: Client, instanceStreamingURL: URL?) {
if self.client != nil {
stopWatching()
}
self.client = client
2023-02-03 18:44:55 +00:00
self.instanceStreamingURL = instanceStreamingURL
connect()
}
2023-01-17 10:36:01 +00:00
private func connect() {
guard let task = try? client?.makeWebSocketTask(
endpoint: Streaming.streaming,
instanceStreamingURL: instanceStreamingURL
) else {
return
}
self.task = task
self.task?.resume()
receiveMessage()
}
2023-01-17 10:36:01 +00:00
public func watch(streams: [Stream]) {
if client?.isAuth == false {
2022-12-25 12:09:43 +00:00
return
}
if task == nil {
connect()
}
watchedStreams = streams
2024-02-14 11:48:14 +00:00
for stream in streams {
sendMessage(message: StreamMessage(type: "subscribe", stream: stream.rawValue))
}
}
2023-01-17 10:36:01 +00:00
public func stopWatching() {
task?.cancel()
task = nil
}
2023-01-17 10:36:01 +00:00
private func sendMessage(message: StreamMessage) {
if let encodedMessage = try? encoder.encode(message),
2023-07-19 05:46:25 +00:00
let stringMessage = String(data: encodedMessage, encoding: .utf8)
{
task?.send(.string(stringMessage), completionHandler: { _ in })
}
}
2023-01-17 10:36:01 +00:00
private func receiveMessage() {
task?.receive(completionHandler: { [weak self] result in
2023-09-16 12:15:03 +00:00
guard let self else { return }
switch result {
case let .success(message):
switch message {
case let .string(string):
do {
guard let data = string.data(using: .utf8) else {
2024-02-02 17:26:24 +00:00
logger.error("Error decoding streaming event string")
return
}
2023-09-16 12:15:03 +00:00
let rawEvent = try decoder.decode(RawStreamEvent.self, from: data)
2024-02-02 17:26:24 +00:00
logger.info("Stream update: \(rawEvent.event)")
2023-09-16 12:15:03 +00:00
if let event = rawEventToEvent(rawEvent: rawEvent) {
Task { @MainActor in
self.events.append(event)
self.latestEvent = event
if let event = event as? StreamEventNotification, event.notification.status?.visibility != .direct {
2022-12-25 12:09:43 +00:00
self.unreadNotificationsCount += 1
}
}
}
} catch {
2024-02-02 17:26:24 +00:00
logger.error("Error decoding streaming event: \(error.localizedDescription)")
}
2023-01-17 10:36:01 +00:00
default:
break
}
2023-01-17 10:36:01 +00:00
2023-09-16 12:15:03 +00:00
receiveMessage()
2023-01-17 10:36:01 +00:00
case .failure:
2023-09-16 12:15:03 +00:00
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(retryDelay)) {
self.retryDelay += 30
2023-01-04 17:37:58 +00:00
self.stopWatching()
self.connect()
self.watch(streams: self.watchedStreams)
}
}
})
}
2023-01-17 10:36:01 +00:00
private func rawEventToEvent(rawEvent: RawStreamEvent) -> (any StreamEvent)? {
guard let payloadData = rawEvent.payload.data(using: .utf8) else {
return nil
}
do {
switch rawEvent.event {
case "update":
let status = try decoder.decode(Status.self, from: payloadData)
return StreamEventUpdate(status: status)
case "status.update":
let status = try decoder.decode(Status.self, from: payloadData)
return StreamEventStatusUpdate(status: status)
case "delete":
return StreamEventDelete(status: rawEvent.payload)
case "notification":
let notification = try decoder.decode(Notification.self, from: payloadData)
return StreamEventNotification(notification: notification)
case "conversation":
let conversation = try decoder.decode(Conversation.self, from: payloadData)
return StreamEventConversation(conversation: conversation)
default:
return nil
}
} catch {
2024-02-02 17:26:24 +00:00
logger.error("Error decoding streaming event to final event: \(error.localizedDescription)")
logger.error("Raw data: \(rawEvent.payload)")
return nil
}
}
2024-02-14 11:48:14 +00:00
public func emmitDeleteEvent(for status: String) {
let event = StreamEventDelete(status: status)
2024-02-14 11:48:14 +00:00
events.append(event)
latestEvent = event
}
2024-02-14 11:48:14 +00:00
public func emmitEditEvent(for status: Status) {
let event = StreamEventStatusUpdate(status: status)
2024-02-14 11:48:14 +00:00
events.append(event)
latestEvent = event
}
2024-02-14 11:48:14 +00:00
public func emmitPostEvent(for status: Status) {
let event = StreamEventUpdate(status: status)
2024-02-14 11:48:14 +00:00
events.append(event)
latestEvent = event
}
}