-
-
Notifications
You must be signed in to change notification settings - Fork 83
Expand file tree
/
Copy pathNotificationService.swift
More file actions
195 lines (182 loc) · 10.3 KB
/
Copy pathNotificationService.swift
File metadata and controls
195 lines (182 loc) · 10.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
import UserNotifications
import CallKit
import DcCore
class NotificationService: UNNotificationServiceExtension {
let dcAccounts = DcAccounts.shared
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
Task {
await didReceive(request, withContentHandler: contentHandler)
}
}
func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) async {
let bestAttemptContent = request.content
let nowTimestamp = Date().timeIntervalSince1970
UserDefaults.pushToDebugArray("🤜")
let dnc = DarwinNotificationCenter.current
if await dnc.didReply(.appRunningConfirmation, to: .appRunningQuestion, timeout: .now() + .seconds(2)) {
UserDefaults.pushToDebugArray("ABORT4_AS_MAIN_RUNS")
contentHandler(silentNotification())
return
}
if UserDefaults.nseFetching {
UserDefaults.pushToDebugArray("ABORT5_AS_NSE_RUNS")
contentHandler(silentNotification())
return
}
UserDefaults.setNseFetching(for: 26)
dcAccounts.openDatabase(writeable: false)
let eventEmitter = dcAccounts.getEventEmitter()
// Send the bestAttempt notification when memory is critical because the process will be killed
// by the system soon and any notification is better than nothing.
var exitedDueToCriticalMemory = false
let memoryPressureSource = DispatchSource.makeMemoryPressureSource(eventMask: .critical)
memoryPressureSource.setEventHandler { [weak memoryPressureSource] in
guard let memoryPressureSource, !memoryPressureSource.isCancelled, !exitedDueToCriticalMemory else { return }
memoryPressureSource.cancel()
// Order of importance because we might crash very soon
exitedDueToCriticalMemory = true
UserDefaults.setNseFetching(for: 3)
UserDefaults.pushToDebugArray("ERR5_LOW_MEM")
contentHandler(bestAttemptContent)
}
memoryPressureSource.activate()
guard dcAccounts.backgroundFetch(timeout: 25) && !exitedDueToCriticalMemory else {
UserDefaults.pushToDebugArray("ERR3_CORE")
UserDefaults.setNseFetchingDone()
if !exitedDueToCriticalMemory {
contentHandler(bestAttemptContent)
}
return
}
UserDefaults.setNseFetchingDone()
memoryPressureSource.cancel()
var notifications: [UNMutableNotificationContent] = []
while true {
guard let event = eventEmitter.getNextEvent() else { break }
if event.id == DC_EVENT_ACCOUNTS_BACKGROUND_FETCH_DONE { break }
if event.id == DC_EVENT_INCOMING_MSG {
let dcContext = dcAccounts.get(id: event.accountId)
let chat = dcContext.getChat(chatId: event.data1Int)
let msg = dcContext.getMessage(id: event.data2Int)
if let content = UNMutableNotificationContent(forMessage: msg, chat: chat, context: dcContext) {
notifications.append(content)
}
} else if event.id == DC_EVENT_INCOMING_REACTION {
let dcContext = dcAccounts.get(id: event.accountId)
let msg = dcContext.getMessage(id: event.data2Int)
let chat = dcContext.getChat(chatId: msg.chatId)
if let content = UNMutableNotificationContent(forReaction: event.data2String, from: event.data1Int, msg: msg, chat: chat, context: dcContext) {
notifications.append(content)
}
} else if event.id == DC_EVENT_INCOMING_WEBXDC_NOTIFY {
let dcContext = dcAccounts.get(id: event.accountId)
let msg = dcContext.getMessage(id: event.data2Int)
let chat = dcContext.getChat(chatId: msg.chatId)
if let content = UNMutableNotificationContent(forWebxdcNotification: event.data2String, msg: msg, chat: chat, context: dcContext) {
notifications.append(content)
}
} else if event.id == DC_EVENT_CALL_MISSED {
let dcContext = dcAccounts.get(id: event.accountId)
let chat = dcContext.getChat(chatId: event.data2Int)
let msg = dcContext.getMessage(id: event.data1Int)
if let content = UNMutableNotificationContent(forMissedCallMsg: msg, chat: chat, context: dcContext) {
notifications.append(content)
}
} else if event.id == DC_EVENT_INCOMING_CALL {
UserDefaults.pushToDebugArray("☎️")
let payload: [String: Any] = [
"event_id": Int(DC_EVENT_INCOMING_CALL),
"account_id": event.accountId,
"message_id": event.data1Int,
"place_call_info": event.data2String,
"has_video": event.data2Int == 1,
]
if !canUseCallKit {
UserDefaults.shared?.set(payload, forKey: UserDefaults.incomingCallPayloadKey)
let dcContext = dcAccounts.get(id: event.accountId)
let msg = dcContext.getMessage(id: event.data2Int)
let chat = dcContext.getChat(chatId: msg.chatId)
if let content = UNMutableNotificationContent(forIncomingCallMsg: msg, chat: chat, context: dcContext) {
let request = UNNotificationRequest(identifier: "incoming-call", content: content, trigger: nil)
try? await UNUserNotificationCenter.current().add(request)
}
} else if #available(iOSApplicationExtension 14.5, *) {
// reportNewIncomingVoIPPushPayload ends up in didReceiveIncomingPushWith in the main app
CXProvider.reportNewIncomingVoIPPushPayload(payload) { error in
if let error {
UserDefaults.pushToDebugArray("ERR6 " + error.localizedDescription)
} else {
UserDefaults.pushToDebugArray("OK2")
}
}
} else {
let content = UNMutableNotificationContent()
let msg = dcAccounts.get(id: event.accountId).getMessage(id: event.data1Int)
content.title = "Incoming Call"
content.body = "Calls require iOS 14.5 or newer"
content.userInfo["account_id"] = event.accountId
content.userInfo["chat_id"] = msg.chatId
content.userInfo["message_id"] = msg.id
notifications.append(content)
}
} else if event.id == DC_EVENT_CALL_ENDED || event.id == DC_EVENT_INCOMING_CALL_ACCEPTED {
UserDefaults.pushToDebugArray(event.id == DC_EVENT_CALL_ENDED ? "☎️ENDED" : "☎️ACCEPTED")
UserDefaults.shared?.set(nil, forKey: UserDefaults.incomingCallPayloadKey)
if #available(iOSApplicationExtension 14.5, *), canUseCallKit {
// reportNewIncomingVoIPPushPayload ends up in didReceiveIncomingPushWith in the main app
CXProvider.reportNewIncomingVoIPPushPayload([
"event_id": Int(event.id),
"account_id": event.accountId,
"message_id": event.data1Int,
] as [String: Any]) { error in
if let error {
UserDefaults.pushToDebugArray("ERR7 " + error.localizedDescription)
} else {
UserDefaults.pushToDebugArray("OK4")
}
}
}
} else if event.id == DC_EVENT_MSGS_NOTICED {
let noticedThreadId = "\(event.accountId)-\(event.data1Int)"
notifications = notifications.filter { $0.threadIdentifier != noticedThreadId }
let nc = UNUserNotificationCenter.current()
let deliveredNotificationIds = await nc.deliveredNotifications()
.filter { $0.request.content.threadIdentifier == noticedThreadId }
.map { $0.request.identifier }
nc.removeDeliveredNotifications(withIdentifiers: deliveredNotificationIds)
}
}
// Queue all notifications
for notification in notifications {
let req = UNNotificationRequest(identifier: UUID().uuidString, content: notification, trigger: nil)
do {
try await UNUserNotificationCenter.current().add(req)
} catch {
UserDefaults.pushToDebugArray("ERR6_UNUNC")
}
}
// This silent notification updates the badge number
let silentNotification = silentNotification()
silentNotification.badge = dcAccounts.getFreshMessagesCount() as NSNumber
dcAccounts.closeDatabase()
if notifications.isEmpty {
UserDefaults.pushToDebugArray(String(format: "OK3 %.3fs", Date().timeIntervalSince1970 - nowTimestamp))
} else {
UserDefaults.shared?.set(true, forKey: UserDefaults.hasExtensionAttemptedToSend) // force UI updates in case app was suspended
UserDefaults.pushToDebugArray(String(format: "OK2 %.3fs", Date().timeIntervalSince1970 - nowTimestamp))
}
contentHandler(silentNotification)
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
// For Delta Chat, it is just fine to do nothing - assume eg. bad network or mail servers not reachable,
// then a "You have new messages" is the best that can be done.
UserDefaults.pushToDebugArray("ERR4_TIME")
UserDefaults.setNseFetchingDone()
}
/// Do not show anything; requires `com.apple.developer.usernotifications.filtering` entitlement
private func silentNotification() -> UNMutableNotificationContent {
UNMutableNotificationContent()
}
}