eleven / messenger Public

Clone
git clone https://amadan.net/eleven/messenger

Plain git — no account needed to clone.

Download

Download this file

1import BackgroundTasks
2import Foundation
3import LChatCore
4
5// 🤖 Background App Refresh: iOS periodically wakes the app (cadence is the
6// system's call — it learns from usage) and BackgroundSync pulls + decrypts +
7// files new messages into the local store off the main thread. Together with
8// the NSE's per-push ingest this keeps chats caught up before they're opened.
9//
10// The identifier must be listed in Info.plist's
11// BGTaskSchedulerPermittedIdentifiers (project.yml), and registration must
12// happen before didFinishLaunching returns (PushManager wires it).
13enum BackgroundRefresh {
14 static var taskID: String {
15 (Bundle.main.bundleIdentifier ?? "com.elevenmessenger.messenger") + ".refresh"
16 }
17
18 static func register() {
19 BGTaskScheduler.shared.register(forTaskWithIdentifier: taskID, using: nil) { task in
20 guard let refresh = task as? BGAppRefreshTask else {
21 task.setTaskCompleted(success: false)
22 return
23 }
24 handle(refresh)
25 }
26 }
27
28 /// (Re)request the next wake. Cheap and idempotent — a duplicate submit
29 /// for a pending request just replaces it. Called when the app backgrounds
30 /// and from the handler itself, so the chain never breaks.
31 static func schedule() {
32 let req = BGAppRefreshTaskRequest(identifier: taskID)
33 req.earliestBeginDate = Date(timeIntervalSinceNow: 15 * 60)
34 try? BGTaskScheduler.shared.submit(req)
35 }
36
37 private static func handle(_ task: BGAppRefreshTask) {
38 schedule()
39 // completed-once guard: the sync finishing and the expiration handler
40 // can race; BGTaskScheduler wants exactly one completion call.
41 let done = OneShot()
42 let work = Task {
43 await BackgroundSync.run(instances: InstancesStore.load())
44 // Release the shared store's connection BEFORE declaring done —
45 // the process suspends any moment after, and a held app-group
46 // file lock at suspension is the 0xdead10cc kill.
47 await MessageStore.shared.suspend()
48 if done.claim() { task.setTaskCompleted(success: true) }
49 }
50 task.expirationHandler = {
51 work.cancel()
52 Task {
53 await MessageStore.shared.suspend()
54 if done.claim() { task.setTaskCompleted(success: false) }
55 }
56 }
57 }
58
59 private final class OneShot: @unchecked Sendable {
60 private let lock = NSLock()
61 private var used = false
62 func claim() -> Bool {
63 lock.lock()
64 defer { lock.unlock() }
65 if used { return false }
66 used = true
67 return true
68 }
69 }
70}
71