Harden Apple Watch snapshot synchronization
This commit is contained in:
@@ -563,6 +563,8 @@ fileprivate struct FfiConverterData: FfiConverterRustBuffer {
|
||||
|
||||
public protocol WatchCoreProtocol: AnyObject, Sendable {
|
||||
|
||||
func applyAuthoritativeSnapshot(snapshot: Data) throws -> WatchSnapshotUpdate
|
||||
|
||||
func applySnapshot(snapshot: Data) throws -> WatchSnapshotUpdate
|
||||
|
||||
func noPersistedSnapshot() throws
|
||||
@@ -633,6 +635,16 @@ open class WatchCore: WatchCoreProtocol, @unchecked Sendable {
|
||||
|
||||
|
||||
|
||||
open func applyAuthoritativeSnapshot(snapshot: Data)throws -> WatchSnapshotUpdate {
|
||||
return try FfiConverterTypeWatchSnapshotUpdate_lift(try rustCallWithError(FfiConverterTypeWatchFfiError_lift) {
|
||||
uniffiCallStatus in
|
||||
uniffi_ironstorage_watch_fn_method_watchcore_apply_authoritative_snapshot(
|
||||
self.uniffiCloneHandle(),
|
||||
FfiConverterData.lower(snapshot),uniffiCallStatus
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
open func applySnapshot(snapshot: Data)throws -> WatchSnapshotUpdate {
|
||||
return try FfiConverterTypeWatchSnapshotUpdate_lift(try rustCallWithError(FfiConverterTypeWatchFfiError_lift) {
|
||||
uniffiCallStatus in
|
||||
@@ -1384,6 +1396,9 @@ private let initializationResult: InitializationResult = {
|
||||
if (uniffi_ironstorage_watch_checksum_func_watch_core() != 47212) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_ironstorage_watch_checksum_method_watchcore_apply_authoritative_snapshot() != 29816) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
if (uniffi_ironstorage_watch_checksum_method_watchcore_apply_snapshot() != 6162) {
|
||||
return InitializationResult.apiChecksumMismatch
|
||||
}
|
||||
|
||||
@@ -253,6 +253,11 @@ uint64_t uniffi_ironstorage_watch_fn_clone_watchcore(uint64_t handle, RustCallSt
|
||||
void uniffi_ironstorage_watch_fn_free_watchcore(uint64_t handle, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_APPLY_AUTHORITATIVE_SNAPSHOT
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_APPLY_AUTHORITATIVE_SNAPSHOT
|
||||
RustBuffer uniffi_ironstorage_watch_fn_method_watchcore_apply_authoritative_snapshot(uint64_t ptr, RustBuffer snapshot, RustCallStatus *_Nonnull out_status
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_APPLY_SNAPSHOT
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_FN_METHOD_WATCHCORE_APPLY_SNAPSHOT
|
||||
RustBuffer uniffi_ironstorage_watch_fn_method_watchcore_apply_snapshot(uint64_t ptr, RustBuffer snapshot, RustCallStatus *_Nonnull out_status
|
||||
@@ -563,6 +568,12 @@ void ffi_ironstorage_watch_rust_future_complete_void(uint64_t handle, RustCallSt
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_FUNC_WATCH_CORE
|
||||
uint16_t uniffi_ironstorage_watch_checksum_func_watch_core(void
|
||||
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_APPLY_AUTHORITATIVE_SNAPSHOT
|
||||
#define UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_APPLY_AUTHORITATIVE_SNAPSHOT
|
||||
uint16_t uniffi_ironstorage_watch_checksum_method_watchcore_apply_authoritative_snapshot(void
|
||||
|
||||
);
|
||||
#endif
|
||||
#ifndef UNIFFI_FFIDEF_UNIFFI_IRONSTORAGE_WATCH_CHECKSUM_METHOD_WATCHCORE_APPLY_SNAPSHOT
|
||||
|
||||
@@ -46,6 +46,7 @@ private final class WatchSnapshotCoordinator: NSObject, WCSessionDelegate {
|
||||
Notification.Name.ironStorageWatchSnapshotDidChange,
|
||||
.ironStorageLocalStoreDidChange,
|
||||
.ironStorageAuthenticationDidChange,
|
||||
UIApplication.didBecomeActiveNotification,
|
||||
] {
|
||||
NotificationCenter.default.addObserver(
|
||||
self,
|
||||
@@ -103,21 +104,7 @@ private final class WatchSnapshotCoordinator: NSObject, WCSessionDelegate {
|
||||
Self.snapshotKey: snapshot,
|
||||
Self.deliveredReceiptKey: Data(transfer.deliveredReceipt),
|
||||
]
|
||||
do {
|
||||
try session.updateApplicationContext(payload)
|
||||
if session.isReachable {
|
||||
session.sendMessage(payload, replyHandler: nil) { _ in
|
||||
session.transferUserInfo(payload)
|
||||
}
|
||||
} else {
|
||||
session.transferUserInfo(payload)
|
||||
}
|
||||
} catch {
|
||||
_ = try? authentication.failWatchSnapshot(
|
||||
revision: transfer.revision,
|
||||
detail: "The latest Apple Watch snapshot could not be queued."
|
||||
)
|
||||
}
|
||||
publish(payload, through: session)
|
||||
notifyStatusChanged()
|
||||
case .failure:
|
||||
// A locked GPG key leaves the previous replacement pending. Authentication
|
||||
@@ -127,6 +114,18 @@ private final class WatchSnapshotCoordinator: NSObject, WCSessionDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
private func publish(_ payload: [String: Any], through session: WCSession) {
|
||||
for transfer in session.outstandingUserInfoTransfers
|
||||
where transfer.userInfo[Self.snapshotKey] != nil {
|
||||
transfer.cancel()
|
||||
}
|
||||
try? session.updateApplicationContext(payload)
|
||||
session.transferUserInfo(payload)
|
||||
if session.isReachable {
|
||||
session.sendMessage(payload, replyHandler: nil) { _ in }
|
||||
}
|
||||
}
|
||||
|
||||
private func recordUnavailable(unpaired: Bool, detail: String) {
|
||||
guard let authentication else { return }
|
||||
_ = try? authentication.setWatchSnapshotUnavailable(unpaired: unpaired, detail: detail)
|
||||
|
||||
@@ -85,6 +85,12 @@ private final class WatchSnapshotTransport: NSObject, ObservableObject, WCSessio
|
||||
|
||||
func sceneBecameActive() {
|
||||
restoreSnapshot()
|
||||
guard WCSession.isSupported() else { return }
|
||||
let session = WCSession.default
|
||||
session.activate()
|
||||
if session.activationState == .activated {
|
||||
receive(session.receivedApplicationContext, session: session, authoritative: true)
|
||||
}
|
||||
}
|
||||
|
||||
func sceneBecameInactive() {
|
||||
@@ -108,7 +114,11 @@ private final class WatchSnapshotTransport: NSObject, ObservableObject, WCSessio
|
||||
}
|
||||
}
|
||||
|
||||
private func receive(_ applicationContext: [String: Any], session: WCSession) {
|
||||
private func receive(
|
||||
_ applicationContext: [String: Any],
|
||||
session: WCSession,
|
||||
authoritative: Bool = false
|
||||
) {
|
||||
try? core.syncStarted()
|
||||
guard var snapshot = applicationContext[Self.snapshotKey] as? Data else {
|
||||
try? core.syncFinished()
|
||||
@@ -117,7 +127,11 @@ private final class WatchSnapshotTransport: NSObject, ObservableObject, WCSessio
|
||||
}
|
||||
defer { snapshot.resetBytes(in: 0..<snapshot.count) }
|
||||
do {
|
||||
let update = try core.applySnapshot(snapshot: snapshot)
|
||||
let update = if authoritative {
|
||||
try core.applyAuthoritativeSnapshot(snapshot: snapshot)
|
||||
} else {
|
||||
try core.applySnapshot(snapshot: snapshot)
|
||||
}
|
||||
switch update.persistence {
|
||||
case .keep:
|
||||
break
|
||||
@@ -128,20 +142,25 @@ private final class WatchSnapshotTransport: NSObject, ObservableObject, WCSessio
|
||||
}
|
||||
refreshPresentation()
|
||||
guard !update.receipt.isEmpty else { return }
|
||||
let acknowledgement = [Self.receiptKey: update.receipt]
|
||||
if session.isReachable {
|
||||
session.sendMessage(acknowledgement, replyHandler: nil) { _ in
|
||||
session.transferUserInfo(acknowledgement)
|
||||
}
|
||||
} else {
|
||||
session.transferUserInfo(acknowledgement)
|
||||
}
|
||||
acknowledge(update.receipt, through: session)
|
||||
} catch {
|
||||
try? core.syncFailed()
|
||||
refreshPresentation()
|
||||
}
|
||||
}
|
||||
|
||||
private func acknowledge(_ receipt: Data, through session: WCSession) {
|
||||
let acknowledgement = [Self.receiptKey: receipt]
|
||||
for transfer in session.outstandingUserInfoTransfers
|
||||
where transfer.userInfo[Self.receiptKey] != nil {
|
||||
transfer.cancel()
|
||||
}
|
||||
session.transferUserInfo(acknowledgement)
|
||||
if session.isReachable {
|
||||
session.sendMessage(acknowledgement, replyHandler: nil) { _ in }
|
||||
}
|
||||
}
|
||||
|
||||
func refreshPresentation(at date: Date = .now) {
|
||||
presentation = try? core.presentationAt(
|
||||
unixSeconds: UInt64(date.timeIntervalSince1970)
|
||||
@@ -160,7 +179,11 @@ private final class WatchSnapshotTransport: NSObject, ObservableObject, WCSessio
|
||||
self.refreshPresentation()
|
||||
return
|
||||
}
|
||||
self.receive(session.receivedApplicationContext, session: session)
|
||||
self.receive(
|
||||
session.receivedApplicationContext,
|
||||
session: session,
|
||||
authoritative: true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +192,7 @@ private final class WatchSnapshotTransport: NSObject, ObservableObject, WCSessio
|
||||
didReceiveApplicationContext applicationContext: [String: Any]
|
||||
) {
|
||||
Task { @MainActor [weak self] in
|
||||
self?.receive(applicationContext, session: session)
|
||||
self?.receive(applicationContext, session: session, authoritative: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user