blob: 659039e285e64c54597437a0d60f6e4f26adecad [file] [log] [blame]
kkostiuk74d1ae42021-06-17 11:10:15 -04001/*
2 * Copyright (C) 2021-2022 Savoir-faire Linux Inc.
3 *
4 * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21import UserNotifications
22import UIKit
23import CallKit
24import Foundation
25import CoreFoundation
26import os
27import Darwin
kkostiuke10e6572022-07-26 15:41:12 -040028import Contacts
kkostiuk74d1ae42021-06-17 11:10:15 -040029
30protocol DarwinNotificationHandler {
31 func listenToMainAppResponse(completion: @escaping (Bool) -> Void)
32 func removeObserver()
33}
34
35enum NotificationField: String {
36 case key
37 case accountId = "to"
38 case aps
39}
40
kkostiuke10e6572022-07-26 15:41:12 -040041enum LocalNotificationType: String {
42 case message
43 case file
44}
45
kkostiuk74d1ae42021-06-17 11:10:15 -040046class NotificationService: UNNotificationServiceExtension {
47
kkostiuke10e6572022-07-26 15:41:12 -040048 private static let localNotificationName = Notification.Name("com.savoirfairelinux.jami.appActive.internal")
kkostiuk74d1ae42021-06-17 11:10:15 -040049
Kateryna Kostiuk6fc15812022-08-12 10:17:34 -040050 private let notificationTimeout = DispatchTimeInterval.seconds(25)
kkostiuk74d1ae42021-06-17 11:10:15 -040051
52 private let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter()
53
54 private var contentHandler: ((UNNotificationContent) -> Void)?
55 private var bestAttemptContent = UNMutableNotificationContent()
56
57 private var adapterService: AdapterService = AdapterService(withAdapter: Adapter())
58
59 private var accountIsActive = false
60 var tasksCompleted = false /// all values from dht parsed, conversation synchronized if needed and files downloaded
61 var numberOfFiles = 0 /// number of files need to be downloaded
Kateryna Kostiuk3b581712022-07-21 08:42:14 -040062 var numberOfMessages = 0 /// number of scheduled messages
kkostiuk74d1ae42021-06-17 11:10:15 -040063 var syncCompleted = false
64 private let tasksGroup = DispatchGroup()
Kateryna Kostiuk19437652022-08-02 13:02:21 -040065 var accountId = ""
kkostiuke10e6572022-07-26 15:41:12 -040066
67 typealias LocalNotification = (content: UNMutableNotificationContent, type: LocalNotificationType)
68
69 private var pendingLocalNotifications = [String: [LocalNotification]]() /// local notification waiting for name lookup
70 private var pendingCalls = [String: [AnyHashable: Any]]() /// calls waiting for name lookup
71 private var names = [String: String]() /// map of peerId and best name
kkostiuk74d1ae42021-06-17 11:10:15 -040072 // swiftlint:disable cyclomatic_complexity
73 override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
74 self.contentHandler = contentHandler
75 defer {
76 finish()
77 }
78 let requestData = requestToDictionary(request: request)
79 if requestData.isEmpty {
80 return
81 }
82
83 /// if main app is active extension should save notification data and let app handle notification
84 saveData(data: requestData)
85 if appIsActive() {
86 return
87 }
88
Kateryna Kostiuk19437652022-08-02 13:02:21 -040089 guard let account = requestData[NotificationField.accountId.rawValue] else { return }
90 accountId = account
kkostiuke10e6572022-07-26 15:41:12 -040091
kkostiuk74d1ae42021-06-17 11:10:15 -040092 /// app is not active. Querry value from dht
93 guard let proxyURL = getProxyCaches(data: requestData),
kkostiuk53c55ff2022-07-18 15:02:17 -040094 var proxy = try? String(contentsOf: proxyURL, encoding: .utf8) else {
kkostiuk74d1ae42021-06-17 11:10:15 -040095 return
96 }
Kateryna Kostiuk1c0e7562022-08-23 15:17:44 -040097 proxy = ensureURLPrefix(urlString: proxy)
kkostiuk74d1ae42021-06-17 11:10:15 -040098 guard let urlPrpxy = URL(string: proxy),
99 let url = getRequestURL(data: requestData, proxyURL: urlPrpxy) else {
100 return
101 }
102 tasksGroup.enter()
Kateryna Kostiuk0acc5f52022-08-15 10:21:26 -0400103 let defaultSession = URLSession(configuration: .default)
104 let task = defaultSession.dataTask(with: url) {[weak self] (data, _, _) in
kkostiuk74d1ae42021-06-17 11:10:15 -0400105 guard let self = self,
106 let data = data else {
Kateryna Kostiuk6fc15812022-08-12 10:17:34 -0400107 self?.verifyTasksStatus()
kkostiuk74d1ae42021-06-17 11:10:15 -0400108 return
109 }
110 let str = String(decoding: data, as: UTF8.self)
111 let lines = str.split(whereSeparator: \.isNewline)
112 for line in lines {
113 do {
114 guard let jsonData = line.data(using: .utf8),
115 let map = try JSONSerialization.jsonObject(with: jsonData, options: .allowFragments) as? [String: Any],
116 let keyPath = self.getKeyPath(data: requestData),
117 let treatedMessages = self.getTreatedMessagesPath(data: requestData) else {
Kateryna Kostiuk6fc15812022-08-12 10:17:34 -0400118 self.verifyTasksStatus()
119 return
120 }
kkostiuk74d1ae42021-06-17 11:10:15 -0400121 let result = self.adapterService.decrypt(keyPath: keyPath.path, messagesPath: treatedMessages.path, value: map)
122 let handleCall: (String, String) -> Void = { [weak self] (peerId, hasVideo) in
123 guard let self = self else {
124 return
125 }
Kateryna Kostiukfd5e6f12022-08-02 11:24:05 -0400126 /// jami will be started. Set accounts to not active state
127 if self.accountIsActive {
128 self.accountIsActive = false
129 self.adapterService.stop()
kkostiuk74d1ae42021-06-17 11:10:15 -0400130 }
Kateryna Kostiukfd5e6f12022-08-02 11:24:05 -0400131 var info = request.content.userInfo
132 info["peerId"] = peerId
133 info["hasVideo"] = hasVideo
Kateryna Kostiuk19437652022-08-02 13:02:21 -0400134 let name = self.bestName(accountId: self.accountId, contactId: peerId)
Kateryna Kostiukfd5e6f12022-08-02 11:24:05 -0400135 if name.isEmpty {
136 info["displayName"] = peerId
137 self.pendingCalls[peerId] = info
Kateryna Kostiuk19437652022-08-02 13:02:21 -0400138 self.startAddressLookup(address: peerId, accountId: self.accountId)
Kateryna Kostiukfd5e6f12022-08-02 11:24:05 -0400139 return
140 }
141 info["displayName"] = name
142 self.presentCall(info: info)
kkostiuk74d1ae42021-06-17 11:10:15 -0400143 }
144 switch result {
145 case .call(let peerId, let hasVideo):
146 handleCall(peerId, "\(hasVideo)")
kkostiukabde7b92022-07-28 13:15:56 -0400147 return
kkostiuk74d1ae42021-06-17 11:10:15 -0400148 case .gitMessage:
149 /// check if account already acive
150 guard !self.accountIsActive else { break }
151 self.accountIsActive = true
Kateryna Kostiuk19437652022-08-02 13:02:21 -0400152 self.adapterService.startAccountsWithListener(accountId: self.accountId) { [weak self] event, eventData in
kkostiuk74d1ae42021-06-17 11:10:15 -0400153 guard let self = self else {
154 return
155 }
156 switch event {
157 case .message:
Kateryna Kostiuk3b581712022-07-21 08:42:14 -0400158 self.numberOfMessages += 1
Kateryna Kostiuk2648aab2022-08-30 14:24:03 -0400159 self.configureMessageNotification(from: eventData.jamiId, body: eventData.content, accountId: self.accountId, conversationId: eventData.conversationId, groupTitle: "")
kkostiuk74d1ae42021-06-17 11:10:15 -0400160 case .fileTransferDone:
161 if let url = URL(string: eventData.content) {
Kateryna Kostiuk19437652022-08-02 13:02:21 -0400162 self.configureFileNotification(from: eventData.jamiId, url: url, accountId: self.accountId, conversationId: eventData.conversationId)
Kateryna Kostiuk3b581712022-07-21 08:42:14 -0400163 } else {
164 self.numberOfFiles -= 1
165 self.verifyTasksStatus()
kkostiuk74d1ae42021-06-17 11:10:15 -0400166 }
kkostiuk74d1ae42021-06-17 11:10:15 -0400167 case .syncCompleted:
168 self.syncCompleted = true
169 self.verifyTasksStatus()
170 case .fileTransferInProgress:
171 self.numberOfFiles += 1
172 case .call:
173 handleCall(eventData.jamiId, eventData.content)
Kateryna Kostiuk2648aab2022-08-30 14:24:03 -0400174 case .invitation:
175 self.syncCompleted = true
176 self.numberOfMessages += 1
177 self.configureMessageNotification(from: eventData.jamiId, body: eventData.content, accountId: self.accountId, conversationId: eventData.conversationId, groupTitle: eventData.groupTitle)
kkostiuk74d1ae42021-06-17 11:10:15 -0400178 }
179 }
180 case .unknown:
181 break
182 }
183 } catch {
184 print("serialization failed , \(error)")
185 }
186 }
187 self.verifyTasksStatus()
188 }
189 task.resume()
190 _ = tasksGroup.wait(timeout: .now() + notificationTimeout)
191 }
192
193 override func serviceExtensionTimeWillExpire() {
194 finish()
195 }
196
197 private func verifyTasksStatus() {
198 guard !self.tasksCompleted else { return } /// we already left taskGroup
kkostiuke10e6572022-07-26 15:41:12 -0400199 /// waiting for lookup
200 if !pendingCalls.isEmpty || !pendingLocalNotifications.isEmpty {
201 return
202 }
kkostiuk74d1ae42021-06-17 11:10:15 -0400203 /// We could finish in two cases:
204 /// 1. we did not start account we are not waiting for the signals from the daemon
205 /// 2. conversation synchronization completed and all files downloaded
Kateryna Kostiuk3b581712022-07-21 08:42:14 -0400206 if !self.accountIsActive || (self.syncCompleted && self.numberOfFiles == 0 && self.numberOfMessages == 0) {
kkostiuk74d1ae42021-06-17 11:10:15 -0400207 self.tasksCompleted = true
208 self.tasksGroup.leave()
209 }
210 }
211
212 private func finish() {
213 if self.accountIsActive {
214 self.accountIsActive = false
215 self.adapterService.stop()
216 }
kkostiuke10e6572022-07-26 15:41:12 -0400217 /// cleanup pending notifications
218 if !self.pendingCalls.isEmpty, let info = self.pendingCalls.first?.value {
219 self.presentCall(info: info)
220 } else {
221 for notifications in pendingLocalNotifications {
222 for notification in notifications.value {
223 self.presentLocalNotification(notification: notification)
224 }
225 }
kkostiukabde7b92022-07-28 13:15:56 -0400226 pendingLocalNotifications.removeAll()
kkostiuke10e6572022-07-26 15:41:12 -0400227 }
kkostiuk74d1ae42021-06-17 11:10:15 -0400228 if let contentHandler = contentHandler {
229 contentHandler(self.bestAttemptContent)
230 }
231 }
232
233 private func appIsActive() -> Bool {
234 let group = DispatchGroup()
235 defer {
236 self.removeObserver()
237 group.leave()
238 }
239 var appIsActive = false
240 group.enter()
241 /// post darwin notification and wait for the answer from the main app. If answer received app is active
242 self.listenToMainAppResponse { _ in
243 appIsActive = true
244 }
245 CFNotificationCenterPostNotification(notificationCenter, CFNotificationName(Constants.notificationReceived), nil, nil, true)
246 /// wait fro 100 milliseconds. If no answer from main app is received app is not active.
247 _ = group.wait(timeout: .now() + 0.3)
248
249 return appIsActive
250 }
251
252 private func saveData(data: [String: String]) {
253 guard let userDefaults = UserDefaults(suiteName: Constants.appGroupIdentifier) else {
254 return
255 }
256 var notificationData = [[String: String]]()
257 if let existingData = userDefaults.object(forKey: Constants.notificationData) as? [[String: String]] {
258 notificationData = existingData
259 }
260 notificationData.append(data)
261 userDefaults.set(notificationData, forKey: Constants.notificationData)
262 }
263
264 private func setNotificationCount(notification: UNMutableNotificationContent) {
265 guard let userDefaults = UserDefaults(suiteName: Constants.appGroupIdentifier) else {
266 return
267 }
268
269 if let count = userDefaults.object(forKey: Constants.notificationsCount) as? NSNumber {
270 let new: NSNumber = count.intValue + 1 as NSNumber
271 notification.badge = new
272 userDefaults.set(new, forKey: Constants.notificationsCount)
273 }
274 }
275
276 private func requestToDictionary(request: UNNotificationRequest) -> [String: String] {
277 var dictionary = [String: String]()
278 let userInfo = request.content.userInfo
279 for key in userInfo.keys {
280 /// "aps" is a field added for alert notification type, so it could be received in the extension. This field is not needed by dht
281 if String(describing: key) == NotificationField.aps.rawValue {
282 continue
283 }
284 if let value = userInfo[key] {
285 let keyString = String(describing: key)
286 let valueString = String(describing: value)
287 dictionary[keyString] = valueString
288 }
289 }
290 return dictionary
291 }
292
293 private func requestedData(request: UNNotificationRequest, map: [String: Any]) -> Bool {
294 guard let userInfo = request.content.userInfo as? [String: Any] else { return false }
295 guard let valueIds = userInfo["valueIds"] as? [String: String],
296 let id = map["id"] else {
297 return false
298 }
299 return valueIds.values.contains("\(id)")
300 }
kkostiuke10e6572022-07-26 15:41:12 -0400301
302 private func bestName(accountId: String, contactId: String) -> String {
303 if let name = self.names[contactId], !name.isEmpty {
304 return name
305 }
306 if let contactProfileName = self.contactProfileName(accountId: accountId, contactId: contactId),
307 !contactProfileName.isEmpty {
308 self.names[contactId] = contactProfileName
309 return contactProfileName
310 }
311 let registeredName = self.adapterService.getNameFor(address: contactId, accountId: accountId)
312 if !registeredName.isEmpty {
313 self.names[contactId] = registeredName
314 }
315 return registeredName
316 }
317
318 private func startAddressLookup(address: String, accountId: String) {
319 var nameServer = self.adapterService.getNameServerFor(accountId: accountId)
Kateryna Kostiuk1c0e7562022-08-23 15:17:44 -0400320 nameServer = ensureURLPrefix(urlString: nameServer)
kkostiuke10e6572022-07-26 15:41:12 -0400321 let urlString = nameServer + "/addr/" + address
Kateryna Kostiuk6fc15812022-08-12 10:17:34 -0400322 guard let url = URL(string: urlString) else {
323 self.lookupCompleted(address: address, name: nil)
324 return
325 }
Kateryna Kostiuk0acc5f52022-08-15 10:21:26 -0400326 let defaultSession = URLSession(configuration: .default)
327 let task = defaultSession.dataTask(with: url) {[weak self](data, response, _) in
kkostiuke10e6572022-07-26 15:41:12 -0400328 guard let self = self else { return }
329 var name: String?
330 defer {
331 self.lookupCompleted(address: address, name: name)
332 }
333 guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200,
334 let data = data else {
335 return
336 }
337 do {
338 guard let map = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: String] else { return }
339 if map["name"] != nil {
340 name = map["name"]
341 self.names[address] = name
342 }
343 } catch {
344 print("serialization failed , \(error)")
345 }
346 }
347 task.resume()
348 }
349
Kateryna Kostiuk1c0e7562022-08-23 15:17:44 -0400350 private func ensureURLPrefix(urlString: String) -> String {
351 var urlWithPrefix = urlString
352 if !urlWithPrefix.hasPrefix("http://") && !urlWithPrefix.hasPrefix("https://") {
353 urlWithPrefix = "http://" + urlWithPrefix
354 }
355 return urlWithPrefix
356 }
357
kkostiuke10e6572022-07-26 15:41:12 -0400358 private func lookupCompleted(address: String, name: String?) {
359 for call in pendingCalls where call.key == address {
360 var info = call.value
361 if let name = name {
362 info["displayName"] = name
363 }
364 presentCall(info: info)
kkostiuke10e6572022-07-26 15:41:12 -0400365 return
366 }
367 for pending in pendingLocalNotifications where pending.key == address {
368 let notifications = pending.value
369 for notification in notifications {
370 if let name = name {
371 notification.content.title = name
372 }
373 presentLocalNotification(notification: notification)
374 }
375 pendingLocalNotifications.removeValue(forKey: address)
376 }
377 }
378
379 private func needUpdateNotification(notification: LocalNotification, peerId: String, accountId: String) {
380 if var pending = pendingLocalNotifications[peerId] {
381 pending.append(notification)
382 pendingLocalNotifications[peerId] = pending
383 } else {
384 pendingLocalNotifications[peerId] = [notification]
385 }
386 startAddressLookup(address: peerId, accountId: accountId)
387 }
kkostiuk74d1ae42021-06-17 11:10:15 -0400388}
389// MARK: paths
390extension NotificationService {
391
392 private func getRequestURL(data: [String: String], proxyURL: URL) -> URL? {
393 guard let key = data[NotificationField.key.rawValue] else {
394 return nil
395 }
396 return proxyURL.appendingPathComponent(key)
397 }
398
399 private func getKeyPath(data: [String: String]) -> URL? {
400 guard let documentsPath = Constants.documentsPath,
401 let accountId = data[NotificationField.accountId.rawValue] else {
402 return nil
403 }
404 return documentsPath.appendingPathComponent(accountId).appendingPathComponent("ring_device.key")
405 }
406
407 private func getTreatedMessagesPath(data: [String: String]) -> URL? {
408 guard let cachesPath = Constants.cachesPath,
409 let accountId = data[NotificationField.accountId.rawValue] else {
410 return nil
411 }
412 return cachesPath.appendingPathComponent(accountId).appendingPathComponent("treatedMessages")
413 }
414
415 private func getProxyCaches(data: [String: String]) -> URL? {
416 guard let cachesPath = Constants.cachesPath,
417 let accountId = data[NotificationField.accountId.rawValue] else {
418 return nil
419 }
420 return cachesPath.appendingPathComponent(accountId).appendingPathComponent("dhtproxy")
421 }
kkostiuke10e6572022-07-26 15:41:12 -0400422
423 private func contactProfileName(accountId: String, contactId: String) -> String? {
424 guard let documents = Constants.documentsPath else { return nil }
425 let profileURI = "ring:" + contactId
426 let profilePath = documents.path + "/" + "\(accountId)" + "/profiles/" + "\(Data(profileURI.utf8).base64EncodedString()).vcf"
427 if !FileManager.default.fileExists(atPath: profilePath) { return nil }
428
429 guard let data = FileManager.default.contents(atPath: profilePath),
430 let vCards = try? CNContactVCardSerialization.contacts(with: data),
Kateryna Kostiuk1efd0012022-09-13 12:08:36 -0400431 let vCard = vCards.first else { return nil }
kkostiuke10e6572022-07-26 15:41:12 -0400432 return vCard.familyName.isEmpty ? vCard.givenName : vCard.familyName
433 }
kkostiuk74d1ae42021-06-17 11:10:15 -0400434}
435
436// MARK: DarwinNotificationHandler
437extension NotificationService: DarwinNotificationHandler {
438 func listenToMainAppResponse(completion: @escaping (Bool) -> Void) {
439 let observer = Unmanaged.passUnretained(self).toOpaque()
440 CFNotificationCenterAddObserver(notificationCenter,
441 observer, { (_, _, _, _, _) in
kkostiuke10e6572022-07-26 15:41:12 -0400442 NotificationCenter.default.post(name: NotificationService.localNotificationName,
kkostiuk74d1ae42021-06-17 11:10:15 -0400443 object: nil,
444 userInfo: nil)
445 },
446 Constants.notificationAppIsActive,
447 nil,
448 .deliverImmediately)
kkostiuke10e6572022-07-26 15:41:12 -0400449 NotificationCenter.default.addObserver(forName: NotificationService.localNotificationName, object: nil, queue: nil) { _ in
kkostiuk74d1ae42021-06-17 11:10:15 -0400450 completion(true)
451 }
452 }
453
454 func removeObserver() {
455 let observer = Unmanaged.passUnretained(self).toOpaque()
456 CFNotificationCenterRemoveEveryObserver(notificationCenter, observer)
kkostiuke10e6572022-07-26 15:41:12 -0400457 NotificationCenter.default.removeObserver(self, name: NotificationService.localNotificationName, object: nil)
kkostiuk74d1ae42021-06-17 11:10:15 -0400458 }
459
460}
461
462// MARK: present notifications
463extension NotificationService {
464 private func createAttachment(identifier: String, image: UIImage, options: [NSObject: AnyObject]?) -> UNNotificationAttachment? {
465 let fileManager = FileManager.default
466 let tmpSubFolderName = ProcessInfo.processInfo.globallyUniqueString
467 let tmpSubFolderURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(tmpSubFolderName, isDirectory: true)
468 do {
469 try fileManager.createDirectory(at: tmpSubFolderURL, withIntermediateDirectories: true, attributes: nil)
470 let imageFileIdentifier = identifier
471 let fileURL = tmpSubFolderURL.appendingPathComponent(imageFileIdentifier)
472 let imageData = UIImage.pngData(image)
473 try imageData()?.write(to: fileURL)
474 let imageAttachment = try UNNotificationAttachment.init(identifier: identifier, url: fileURL, options: options)
475 return imageAttachment
476 } catch {}
477 return nil
478 }
479
kkostiuk8e397cd2022-07-27 15:08:10 -0400480 private func configureFileNotification(from: String, url: URL, accountId: String, conversationId: String) {
kkostiuk74d1ae42021-06-17 11:10:15 -0400481 let content = UNMutableNotificationContent()
kkostiuke10e6572022-07-26 15:41:12 -0400482 content.sound = UNNotificationSound.default
kkostiuk74d1ae42021-06-17 11:10:15 -0400483 let imageName = url.lastPathComponent
kkostiuk74d1ae42021-06-17 11:10:15 -0400484 content.body = imageName
kkostiuk8e397cd2022-07-27 15:08:10 -0400485 var data = [String: String]()
486 data[Constants.NotificationUserInfoKeys.participantID.rawValue] = from
487 data[Constants.NotificationUserInfoKeys.accountID.rawValue] = accountId
488 data[Constants.NotificationUserInfoKeys.conversationID.rawValue] = conversationId
489 content.userInfo = data
kkostiuk74d1ae42021-06-17 11:10:15 -0400490 if let image = UIImage(contentsOfFile: url.path), let attachement = createAttachment(identifier: imageName, image: image, options: nil) {
491 content.attachments = [ attachement ]
492 }
kkostiuke10e6572022-07-26 15:41:12 -0400493 let title = self.bestName(accountId: accountId, contactId: from)
494 if title.isEmpty {
495 content.title = from
496 needUpdateNotification(notification: LocalNotification(content, .file), peerId: from, accountId: accountId)
497 } else {
498 content.title = title
499 presentLocalNotification(notification: LocalNotification(content, .file))
500 }
501 }
502
Kateryna Kostiuk2648aab2022-08-30 14:24:03 -0400503 private func configureMessageNotification(from: String, body: String, accountId: String, conversationId: String, groupTitle: String) {
kkostiuke10e6572022-07-26 15:41:12 -0400504 let content = UNMutableNotificationContent()
505 content.body = body
kkostiuk74d1ae42021-06-17 11:10:15 -0400506 content.sound = UNNotificationSound.default
kkostiuk8e397cd2022-07-27 15:08:10 -0400507 var data = [String: String]()
508 data[Constants.NotificationUserInfoKeys.participantID.rawValue] = from
509 data[Constants.NotificationUserInfoKeys.accountID.rawValue] = accountId
510 data[Constants.NotificationUserInfoKeys.conversationID.rawValue] = conversationId
511 content.userInfo = data
Kateryna Kostiuk2648aab2022-08-30 14:24:03 -0400512 let title = !groupTitle.isEmpty ? groupTitle : self.bestName(accountId: accountId, contactId: from)
kkostiuke10e6572022-07-26 15:41:12 -0400513 if title.isEmpty {
514 content.title = from
515 needUpdateNotification(notification: LocalNotification(content, .message), peerId: from, accountId: accountId)
516 } else {
517 content.title = title
518 presentLocalNotification(notification: LocalNotification(content, .message))
519 }
520 }
521
522 private func presentLocalNotification(notification: LocalNotification) {
523 let content = notification.content
kkostiuk74d1ae42021-06-17 11:10:15 -0400524 setNotificationCount(notification: content)
525 let notificationTrigger = UNTimeIntervalNotificationTrigger(timeInterval: 0.01, repeats: false)
Kateryna Kostiuk3b581712022-07-21 08:42:14 -0400526 let notificationRequest = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: notificationTrigger)
527 UNUserNotificationCenter.current().add(notificationRequest) { [weak self] (error) in
kkostiuke10e6572022-07-26 15:41:12 -0400528 if notification.type == .message {
529 self?.numberOfMessages -= 1
530 } else {
531 self?.numberOfFiles -= 1
532 }
Kateryna Kostiuk3b581712022-07-21 08:42:14 -0400533 self?.verifyTasksStatus()
kkostiuk74d1ae42021-06-17 11:10:15 -0400534 if let error = error {
535 print("Unable to Add Notification Request (\(error), \(error.localizedDescription))")
536 }
537 }
538 }
539
kkostiuke10e6572022-07-26 15:41:12 -0400540 private func presentCall(info: [AnyHashable: Any]) {
Kateryna Kostiukfd5e6f12022-08-02 11:24:05 -0400541 CXProvider.reportNewIncomingVoIPPushPayload(info, completion: { error in
542 print("NotificationService", "Did report voip notification, error: \(String(describing: error))")
543 })
kkostiukabde7b92022-07-28 13:15:56 -0400544 self.pendingCalls.removeAll()
545 self.pendingLocalNotifications.removeAll()
kkostiuke10e6572022-07-26 15:41:12 -0400546 self.verifyTasksStatus()
kkostiuk74d1ae42021-06-17 11:10:15 -0400547 }
548}