Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
4.9 kB · 137 lines
Swift
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138import ComposableNotificationsimport Dependenciesimport Foundationimport HTTPClientimport HTTPTypes
public extension NotificationsClient { /// Registers notification state with an authenticated HTTP server. /// /// The shared HTTP client remains responsible for authentication and retry interceptors. This /// adapter owns ordering: destination and subscription changes are synchronized independently, /// and only one synchronization pass is in flight at a time. static func http(baseURL: URL) -> Self { @Dependency(\.httpClient) var httpClient let coordinator = NotificationRegistrationHTTPCoordinator( baseURL: baseURL, httpClient: httpClient )
return Self( registerDestination: { destination in try await coordinator.update(destination: destination) }, replaceSubscriptions: { subscriptions in try await coordinator.update(subscriptions: subscriptions) }, synchronize: { try await coordinator.synchronize() }, signals: { AsyncStream { $0.finish() } } ) }}
private actor NotificationRegistrationHTTPCoordinator { private let baseURL: URL private let httpClient: HTTPClient private var destination: Push.Destination? private var isSynchronizing = false private var synchronizedDestination: Push.Destination? private var synchronizedSubscriptions: Set<NotificationSubscription>? private var synchronizationWaiters: [CheckedContinuation<Void, Error>] = [] private var subscriptions: Set<NotificationSubscription> = []
init(baseURL: URL, httpClient: HTTPClient) { self.baseURL = baseURL self.httpClient = httpClient }
func update(destination: Push.Destination) async throws { self.destination = destination try await synchronize() }
func update(subscriptions: Set<NotificationSubscription>) async throws { self.subscriptions = subscriptions try await synchronize() }
func synchronize() async throws { if isSynchronizing { try await withCheckedThrowingContinuation { continuation in synchronizationWaiters.append(continuation) } return }
isSynchronizing = true do { try await drainDesiredState() finishSynchronization(with: .success(())) } catch { finishSynchronization(with: .failure(error)) throw error } }
private func drainDesiredState() async throws { while let destination { if destination != synchronizedDestination { try await replaceDestination(destination) synchronizedDestination = destination }
let subscriptions = self.subscriptions if subscriptions != synchronizedSubscriptions { try await replaceSubscriptions(subscriptions) synchronizedSubscriptions = subscriptions }
guard self.destination != synchronizedDestination || self.subscriptions != synchronizedSubscriptions else { return } } }
private func finishSynchronization(with result: Result<Void, any Error>) { isSynchronizing = false let waiters = synchronizationWaiters synchronizationWaiters.removeAll() for waiter in waiters { waiter.resume(with: result) } }
private func replaceDestination(_ destination: Push.Destination) async throws { try await put( NotificationDestinationPayload(destination), path: NotificationRegistrationEndpoint.destinationPath ) }
private func replaceSubscriptions( _ subscriptions: Set<NotificationSubscription> ) async throws { try await put( NotificationSubscriptionsPayload(subscriptions), path: NotificationRegistrationEndpoint.subscriptionsPath ) }
private func put<Body: Encodable>(_ body: Body, path: String) async throws { @Dependency(\.notificationHTTPAuthorization) var notificationHTTPAuthorization let path = path.trimmingCharacters(in: CharacterSet(charactersIn: "/")) var request = HTTPRequest(url: baseURL.appendingPathComponent(path)) request.method = .put request.headerFields[.contentType] = "application/json"
_ = try await withDependencies { $0.requestInterceptors = notificationHTTPAuthorization.requestInterceptors $0.errorInterceptors = notificationHTTPAuthorization.errorInterceptors } operation: { try await httpClient.run(request, JSONEncoder().encode(body)) } }}