130 lines
4.1 KiB
Swift
130 lines
4.1 KiB
Swift
import Combine
|
|
import Foundation
|
|
import WatchConnectivity
|
|
|
|
@MainActor
|
|
public final class WatchConnectivityService: NSObject, ObservableObject {
|
|
@Published public private(set) var activationState: WCSessionActivationState = .notActivated
|
|
@Published public private(set) var isReachable = false
|
|
@Published public private(set) var lastSynchronizedAt: Date?
|
|
@Published public private(set) var lastError: String?
|
|
|
|
public var onEnvelope: ((WatchSyncEnvelopeV1) -> Void)?
|
|
public var onAcknowledgement: ((Set<UUID>) -> Void)?
|
|
|
|
private let session: WCSession?
|
|
private let encoder = WatchCoding.encoder
|
|
private let decoder = WatchCoding.decoder
|
|
|
|
public override init() {
|
|
session = WCSession.isSupported() ? .default : nil
|
|
super.init()
|
|
}
|
|
|
|
public func activate() {
|
|
guard let session else { return }
|
|
session.delegate = self
|
|
session.activate()
|
|
activationState = session.activationState
|
|
isReachable = session.isReachable
|
|
}
|
|
|
|
public func sendLive(_ envelope: WatchSyncEnvelopeV1) {
|
|
guard let session, session.isReachable else { return }
|
|
do {
|
|
let data = try encoder.encode(envelope)
|
|
session.sendMessageData(data, replyHandler: nil) { [weak self] error in
|
|
Task { @MainActor in self?.lastError = error.localizedDescription }
|
|
}
|
|
} catch {
|
|
lastError = error.localizedDescription
|
|
}
|
|
}
|
|
|
|
/// Guaranteed delivery for durable mutations. WatchConnectivity persists these while offline.
|
|
public func transfer(_ envelope: WatchSyncEnvelopeV1) {
|
|
guard let session else { return }
|
|
do {
|
|
session.transferUserInfo(["envelope": try encoder.encode(envelope)])
|
|
} catch {
|
|
lastError = error.localizedDescription
|
|
}
|
|
}
|
|
|
|
public func flush(outbox: [WatchSyncEnvelopeV1]) {
|
|
for envelope in outbox { transfer(envelope) }
|
|
}
|
|
|
|
public func updateApplicationContext(preferences: WatchPreferencesV1) {
|
|
guard let session else { return }
|
|
do {
|
|
try session.updateApplicationContext(["preferences": try encoder.encode(preferences)])
|
|
} catch {
|
|
lastError = error.localizedDescription
|
|
}
|
|
}
|
|
|
|
private func receive(_ data: Data) {
|
|
do {
|
|
let envelope = try decoder.decode(WatchSyncEnvelopeV1.self, from: data)
|
|
lastSynchronizedAt = Date()
|
|
if envelope.kind == .acknowledgement,
|
|
let acknowledgement = try? envelope.decodePayload(WatchAcknowledgementV1.self) {
|
|
onAcknowledgement?(Set(acknowledgement.eventIDs))
|
|
} else {
|
|
onEnvelope?(envelope)
|
|
}
|
|
} catch {
|
|
lastError = error.localizedDescription
|
|
}
|
|
}
|
|
}
|
|
|
|
extension WatchConnectivityService: WCSessionDelegate {
|
|
nonisolated public func session(
|
|
_ session: WCSession,
|
|
activationDidCompleteWith activationState: WCSessionActivationState,
|
|
error: Error?
|
|
) {
|
|
Task { @MainActor [weak self] in
|
|
self?.activationState = activationState
|
|
self?.isReachable = session.isReachable
|
|
self?.lastError = error?.localizedDescription
|
|
}
|
|
}
|
|
|
|
nonisolated public func sessionReachabilityDidChange(_ session: WCSession) {
|
|
Task { @MainActor [weak self] in self?.isReachable = session.isReachable }
|
|
}
|
|
|
|
nonisolated public func session(_ session: WCSession, didReceiveMessageData messageData: Data) {
|
|
Task { @MainActor [weak self] in self?.receive(messageData) }
|
|
}
|
|
|
|
nonisolated public func session(_ session: WCSession, didReceiveUserInfo userInfo: [String: Any] = [:]) {
|
|
guard let data = userInfo["envelope"] as? Data else { return }
|
|
Task { @MainActor [weak self] in self?.receive(data) }
|
|
}
|
|
|
|
nonisolated public func session(
|
|
_ session: WCSession,
|
|
didReceiveApplicationContext applicationContext: [String: Any]
|
|
) {
|
|
guard let data = applicationContext["preferences"] as? Data else { return }
|
|
Task { @MainActor [weak self] in
|
|
guard let self,
|
|
let preferences = try? self.decoder.decode(WatchPreferencesV1.self, from: data) else { return }
|
|
let payload = try? self.encoder.encode(preferences)
|
|
if let payload {
|
|
self.onEnvelope?(.init(
|
|
deviceID: "iphone",
|
|
sequence: 0,
|
|
kind: .preferences,
|
|
payload: payload
|
|
))
|
|
}
|
|
self.lastSynchronizedAt = Date()
|
|
}
|
|
}
|
|
}
|