crossmate

A collaborative crossword app for iOS
Log | Files | Refs | LICENSE

commit 4208cbfcd4d46501ba9fbb51cba61f04fc884cd2
parent fa22f91fc24578e27a065075b8ce09ea075c9de8
Author: Michael Camilleri <[email protected]>
Date:   Fri,  8 May 2026 21:39:32 +0900

Create push subscriptions expressly

Diffstat:
MCrossmate/Sync/SyncEngine.swift | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
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