commit 4208cbfcd4d46501ba9fbb51cba61f04fc884cd2
parent fa22f91fc24578e27a065075b8ce09ea075c9de8
Author: Michael Camilleri <[email protected]>
Date: Fri, 8 May 2026 21:39:32 +0900
Create push subscriptions expressly
Diffstat:
1 file changed, 52 insertions(+), 0 deletions(-)
diff --git a/Crossmate/Sync/SyncEngine.swift b/Crossmate/Sync/SyncEngine.swift
@@ -124,6 +124,11 @@ actor SyncEngine {
// MARK: - Lifecycle
+ /// Database subscription IDs. Stable, per-scope, idempotent on re-creation
+ /// so we can always attempt a save without first listing.
+ private static let privateSubscriptionID = "crossmate-private-db-subscription"
+ private static let sharedSubscriptionID = "crossmate-shared-db-subscription"
+
/// Creates both `CKSyncEngine` instances, restoring previously-saved state
/// so pending changes and change tokens survive restarts. Call once after
/// wiring callbacks.
@@ -153,6 +158,53 @@ actor SyncEngine {
stateSerialization: sharedState,
delegate: self
))
+
+ // CKSyncEngine's automatic subscription creation is unreliable in
+ // practice — diagnostics on real devices showed both scopes with zero
+ // subscriptions even after a healthy initial fetch and push, which
+ // means CloudKit never fires pushes and the engine silently degrades
+ // to its periodic poll. Create the database subscriptions ourselves;
+ // CKDatabase.save is idempotent for an existing subscriptionID.
+ Task { await ensureDatabaseSubscriptions() }
+ }
+
+ private func ensureDatabaseSubscriptions() async {
+ await ensureDatabaseSubscription(
+ database: container.privateCloudDatabase,
+ subscriptionID: Self.privateSubscriptionID,
+ label: "private"
+ )
+ await ensureDatabaseSubscription(
+ database: container.sharedCloudDatabase,
+ subscriptionID: Self.sharedSubscriptionID,
+ label: "shared"
+ )
+ }
+
+ private func ensureDatabaseSubscription(
+ database: CKDatabase,
+ subscriptionID: String,
+ label: String
+ ) async {
+ do {
+ let existing = try await database.allSubscriptions()
+ if existing.contains(where: { $0.subscriptionID == subscriptionID }) {
+ await trace("\(label) subscription already present (\(subscriptionID))")
+ return
+ }
+ } catch {
+ await trace("\(label) allSubscriptions probe failed: \(describe(error)) — attempting save anyway")
+ }
+ let subscription = CKDatabaseSubscription(subscriptionID: subscriptionID)
+ let info = CKSubscription.NotificationInfo()
+ info.shouldSendContentAvailable = true
+ subscription.notificationInfo = info
+ do {
+ _ = try await database.save(subscription)
+ await trace("\(label) subscription created (\(subscriptionID))")
+ } catch {
+ await trace("\(label) subscription save FAILED: \(describe(error))")
+ }
}
// MARK: - Outbound