PendingMessage.swift 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2018-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. import Foundation
  21. import CocoaLumberjackSwift
  22. @objc class PendingMessage: NSObject, NSCoding {
  23. @objc var completionHandler: (() -> Void)? = nil
  24. var senderId: String
  25. var messageId: String
  26. @objc var key: String
  27. var threemaPushNotification: ThreemaPushNotification?
  28. var fireDate: Date?
  29. @objc var isPendingGroupMessages = false
  30. private var abstractMessage: AbstractMessage?
  31. private var baseMessage: BaseMessage?
  32. private var processed: Bool
  33. // MARK: Public Functions
  34. @objc init(senderIdentity: String, messageIdentity: String) {
  35. senderId = senderIdentity
  36. messageId = messageIdentity
  37. key = senderIdentity + messageIdentity
  38. processed = false
  39. }
  40. @objc convenience init(senderIdentity: String, messageIdentity: String, pushPayload: [String: Any]) {
  41. let threemaPush = try? ThreemaPushNotification(from: pushPayload)
  42. self.init(senderIdentity: senderIdentity, messageIdentity: messageIdentity, threemaPush: threemaPush)
  43. }
  44. // Initalizer for `NSCoding`
  45. private init(senderIdentity: String, messageIdentity: String, threemaPush: ThreemaPushNotification?) {
  46. senderId = senderIdentity
  47. messageId = messageIdentity
  48. key = senderIdentity + messageIdentity
  49. processed = false
  50. threemaPushNotification = threemaPush
  51. }
  52. @objc init(receivedAbstractMessage: AbstractMessage) {
  53. abstractMessage = receivedAbstractMessage
  54. senderId = receivedAbstractMessage.fromIdentity
  55. messageId = receivedAbstractMessage.messageId.hexEncodedString()
  56. key = senderId + messageId
  57. processed = false
  58. }
  59. func isPendingNotification(completion: @escaping (_ pending: Bool) -> Void) {
  60. let center = UNUserNotificationCenter.current()
  61. center.getPendingNotificationRequests { pendingNotifications in
  62. DispatchQueue.main.async {
  63. for pendingNotification in pendingNotifications {
  64. if pendingNotification.identifier == self.key {
  65. completion(true)
  66. return
  67. }
  68. }
  69. completion(false)
  70. }
  71. }
  72. }
  73. func isMessageAlreadyPushed() -> Bool {
  74. return processed
  75. }
  76. // First roundtrip when a new message is received
  77. func startInitialTimedNotification() {
  78. ValidationLogger.shared()?.logString("Push: start initial timed notification for message \(messageId)")
  79. startTimedNotification(setFireDate: true)
  80. }
  81. // Second roundtrip when a new message is received
  82. @objc func addAbstractMessage(message: AbstractMessage) {
  83. ValidationLogger.shared()?.logString("Push: add abstract message for message \(messageId)")
  84. abstractMessage = message
  85. startTimedNotification(setFireDate: true)
  86. }
  87. // Third roundtrip when a new message is received
  88. @objc func addBaseMessage(message: BaseMessage) {
  89. ValidationLogger.shared()?.logString("Push: add basemessage for message \(messageId)")
  90. baseMessage = message
  91. startTimedNotification(setFireDate: true)
  92. }
  93. // Final roundrip when a new message is recieved
  94. @objc func finishedProcessing() {
  95. ValidationLogger.shared()?.logString("Push: finished processing for message \(messageId)")
  96. if isPendingGroupMessages == true {
  97. removeNotification()
  98. completionHandler?()
  99. return
  100. }
  101. guard processed == false else {
  102. return
  103. }
  104. processed = true
  105. startTimedNotification(setFireDate: false)
  106. if let currentBaseMessage = baseMessage {
  107. /* Broadcast a notification, just in case we're currently in another chat within the app */
  108. if AppDelegate.shared().isAppInBackground() == false && self.abstractMessage!.receivedAfterInitialQueueSend == true {
  109. if PendingMessagesManager.canMasterDndSendPush() == true {
  110. if let pushSetting = PushSetting.find(for: currentBaseMessage.conversation) {
  111. if pushSetting.canSendPush(for: currentBaseMessage) {
  112. threemaNewMessageReceived()
  113. if !pushSetting.silent {
  114. NotificationManager.sharedInstance().playReceivedMessageSound()
  115. }
  116. }
  117. } else {
  118. threemaNewMessageReceived()
  119. NotificationManager.sharedInstance().playReceivedMessageSound()
  120. }
  121. }
  122. NotificationManager.sharedInstance().updateUnreadMessagesCount(false)
  123. }
  124. }
  125. // background task to send ack to server
  126. let backgroundKey = kAppAckBackgroundTask + key
  127. BackgroundTaskManager.shared.newBackgroundTask(key: backgroundKey, timeout: Int(kAppAckBackgroundTaskTime)) {
  128. PendingMessagesManager.shared.pendingMessageIsDone(pendingMessage: self, cancelTask: true)
  129. self.completionHandler?()
  130. }
  131. }
  132. @objc final class func createTestNotification(payload: [AnyHashable: Any], completion: @escaping () -> Void) {
  133. let pushSound = UserSettings.shared().pushSound
  134. var pushText = "PushTest"
  135. if let aps = payload["aps"] as? [AnyHashable: Any] {
  136. if let alert = aps["alert"] {
  137. pushText = "\(pushText): \(alert)"
  138. }
  139. }
  140. let notification = UNMutableNotificationContent()
  141. notification.body = pushText
  142. if UserSettings.shared().pushSound != "none" {
  143. notification.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: pushSound! + ".caf"))
  144. }
  145. notification.badge = 999
  146. let notificationRequest = UNNotificationRequest(identifier: "PushTest", content: notification, trigger: nil)
  147. let center = UNUserNotificationCenter.current()
  148. center.add(notificationRequest) { _ in
  149. completion()
  150. }
  151. }
  152. // MARK: Private Functions
  153. private func startTimedNotification(setFireDate: Bool) {
  154. removeNotification()
  155. if PendingMessagesManager.canMasterDndSendPush() == false {
  156. NotificationManager.sharedInstance().updateUnreadMessagesCount(false)
  157. return
  158. }
  159. var pushSetting: PushSetting?
  160. // Check if we should even send any push notification
  161. if let currentBaseMessage = baseMessage {
  162. pushSetting = PushSetting.find(for: currentBaseMessage.conversation)
  163. if let localPushSetting = pushSetting, !localPushSetting.canSendPush(for: currentBaseMessage) {
  164. NotificationManager.sharedInstance().updateUnreadMessagesCount(false)
  165. if AppDelegate.shared().isAppInBackground() == false && self.abstractMessage?.receivedAfterInitialQueueSend == true && processed == true {
  166. threemaNewMessageReceived()
  167. }
  168. return
  169. }
  170. } else {
  171. // Only non-group messages
  172. if let localThreemaPushNotification = threemaPushNotification, localThreemaPushNotification.command == .newMessage {
  173. pushSetting = PushSetting.find(forIdentity: senderId)
  174. if let localPushSetting = pushSetting, !localPushSetting.canSendPush() {
  175. NotificationManager.sharedInstance().updateUnreadMessagesCount(false)
  176. if AppDelegate.shared().isAppInBackground() == false && self.abstractMessage?.receivedAfterInitialQueueSend == true && processed == true {
  177. threemaNewMessageReceived()
  178. }
  179. return
  180. }
  181. }
  182. }
  183. if let localAbstractMessage = abstractMessage,
  184. localAbstractMessage.shouldPush() == false || (localAbstractMessage.shouldPush() == true && localAbstractMessage.isVoIP() == true) {
  185. // dont show notification for this message
  186. return
  187. }
  188. // Don't scheudle messages for incoming voip calls
  189. if let localThreemaPushNotification = threemaPushNotification, localThreemaPushNotification.voip == true {
  190. DDLogDebug("Did not scheudle message for incoming voip call")
  191. return
  192. }
  193. // Yes, we want to send a notification
  194. var fromName: String?
  195. var title: String?
  196. var body: String?
  197. var attachmentName: String?
  198. var attachmentUrl: URL?
  199. var cmd: String?
  200. var categoryIdentifier: String?
  201. var groupId: String?
  202. if abstractMessage != nil && baseMessage != nil {
  203. // all data (maybe not the image) is loaded and we can show the correct message
  204. fromName = abstractMessage!.pushFromName
  205. if fromName == nil || fromName?.count == 0 {
  206. fromName = abstractMessage!.fromIdentity
  207. }
  208. if abstractMessage!.isKind(of: AbstractGroupMessage.self) {
  209. // group message
  210. if !UserSettings.shared().pushShowNickname {
  211. fromName = baseMessage!.sender.displayName
  212. }
  213. if UserSettings.shared().pushDecrypt {
  214. title = baseMessage!.conversation.groupName
  215. if title == nil {
  216. title = fromName
  217. }
  218. body = TextStyleUtils.makeMentionsString(forText: "\(fromName!): \(baseMessage!.previewText()!)")
  219. if (abstractMessage!.isKind(of: GroupImageMessage.self) && (baseMessage as! ImageMessage).image != nil) || (abstractMessage!.isKind(of: GroupVideoMessage.self) && (baseMessage as! VideoMessage).thumbnail != nil) ||
  220. (abstractMessage!.isKind(of: GroupFileMessage.self) && (baseMessage as! FileMessage).thumbnail != nil) {
  221. let tmpDirectory: String! = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).last
  222. let attachmentDirectory: String = tmpDirectory + "/PushImages"
  223. let fileManager = FileManager.default
  224. if fileManager.fileExists(atPath: attachmentDirectory) == false {
  225. do {
  226. try fileManager.createDirectory(at: URL(fileURLWithPath: attachmentDirectory, isDirectory: true), withIntermediateDirectories: false, attributes: nil)
  227. } catch {
  228. DDLogWarn("Unable to create direcotry for push images at \(attachmentDirectory): \(error)")
  229. }
  230. }
  231. attachmentName = "PushImage_\(abstractMessage!.messageId.hexEncodedString())"
  232. let fileName = "/\(attachmentName!).jpg"
  233. let path = attachmentDirectory + fileName
  234. var imageData: ImageData? = nil
  235. if abstractMessage!.isKind(of: GroupVideoMessage.self) {
  236. imageData = (baseMessage as! VideoMessage).thumbnail
  237. }
  238. else if abstractMessage!.isKind(of: GroupFileMessage.self) {
  239. imageData = (baseMessage as! FileMessage).thumbnail
  240. }
  241. else {
  242. imageData = (baseMessage as! ImageMessage).image
  243. }
  244. do {
  245. attachmentUrl = URL(fileURLWithPath: path)
  246. try imageData!.data.write(to:attachmentUrl! , options: .completeFileProtectionUntilFirstUserAuthentication)
  247. }
  248. catch {
  249. // can't write file, no image preview
  250. attachmentName = nil
  251. attachmentUrl = nil
  252. }
  253. }
  254. } else {
  255. body = String(format: NSLocalizedString("new_group_message_from_x", comment: ""), fromName!)
  256. }
  257. // add groupid if message is loaded to open the correct chat after tapping notification
  258. groupId = baseMessage!.conversation.groupId.base64EncodedString(options: NSData.Base64EncodingOptions(rawValue: 0))
  259. cmd = "newgroupmsg"
  260. categoryIdentifier = "GROUP"
  261. } else {
  262. // non group message
  263. if !UserSettings.shared().pushShowNickname {
  264. fromName = baseMessage!.conversation.contact.displayName
  265. }
  266. if UserSettings.shared().pushDecrypt {
  267. title = fromName
  268. body = TextStyleUtils.makeMentionsString(forText: baseMessage!.previewText())
  269. if (abstractMessage!.isKind(of: BoxImageMessage.self) && (baseMessage as! ImageMessage).image != nil) || (abstractMessage!.isKind(of: BoxVideoMessage.self) && (baseMessage as! VideoMessage).thumbnail != nil) ||
  270. (abstractMessage!.isKind(of: BoxFileMessage.self) && (baseMessage as! FileMessage).thumbnail != nil) {
  271. let tmpDirectory: String! = NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).last
  272. let attachmentDirectory: String = tmpDirectory + "/PushImages"
  273. let fileManager = FileManager.default
  274. if fileManager.fileExists(atPath: attachmentDirectory) == false {
  275. do {
  276. try fileManager.createDirectory(at: URL(fileURLWithPath: attachmentDirectory, isDirectory: true), withIntermediateDirectories: false, attributes: nil)
  277. } catch {
  278. DDLogWarn("Unable to create direcotry for push images at \(attachmentDirectory): \(error)")
  279. }
  280. }
  281. attachmentName = "PushImage_\(abstractMessage!.messageId.hexEncodedString())"
  282. let fileName = "/\(attachmentName!).jpg"
  283. let path = attachmentDirectory + fileName
  284. var imageData: ImageData? = nil
  285. if abstractMessage!.isKind(of: BoxVideoMessage.self) {
  286. imageData = (baseMessage as! VideoMessage).thumbnail
  287. }
  288. else if abstractMessage!.isKind(of: BoxFileMessage.self) {
  289. imageData = (baseMessage as! FileMessage).thumbnail
  290. }
  291. else {
  292. imageData = (baseMessage as! ImageMessage).image
  293. }
  294. do {
  295. attachmentUrl = URL(fileURLWithPath: path)
  296. if FileManager.default.fileExists(atPath: path) == true {
  297. try FileManager.default.removeItem(at: attachmentUrl!)
  298. }
  299. try imageData!.data.write(to:attachmentUrl! , options: .completeFileProtectionUntilFirstUserAuthentication)
  300. }
  301. catch {
  302. // can't write file, no image preview
  303. attachmentName = nil
  304. attachmentUrl = nil
  305. }
  306. }
  307. } else {
  308. body = String(format: NSLocalizedString("new_message_from_x", comment: ""), fromName!)
  309. }
  310. cmd = "newmsg"
  311. categoryIdentifier = "SINGLE"
  312. }
  313. }
  314. else if abstractMessage != nil {
  315. // abstract message is loaded and we can show the correct contact in the message
  316. fromName = abstractMessage!.pushFromName
  317. if fromName == nil || fromName?.count == 0 {
  318. fromName = abstractMessage!.fromIdentity
  319. }
  320. if let contact = ContactStore.shared().contact(forIdentity: senderId), !UserSettings.shared().pushShowNickname {
  321. fromName = contact.displayName
  322. }
  323. if abstractMessage!.isKind(of: AbstractGroupMessage.self) {
  324. // group message
  325. if UserSettings.shared().pushDecrypt {
  326. title = NSLocalizedString("new_group_message", comment: "")
  327. body = "\(fromName!): \(abstractMessage!.pushNotificationBody()!)"
  328. } else {
  329. body = String(format: NSLocalizedString("new_group_message_from_x", comment: ""), fromName!)
  330. }
  331. cmd = "newgroupmsg"
  332. categoryIdentifier = "GROUP"
  333. } else {
  334. // non group message
  335. if UserSettings.shared().pushDecrypt {
  336. title = fromName
  337. body = abstractMessage!.pushNotificationBody()
  338. } else {
  339. body = String(format: NSLocalizedString("new_message_from_x", comment: ""), fromName!)
  340. }
  341. cmd = "newmsg"
  342. categoryIdentifier = "SINGLE"
  343. }
  344. }
  345. else {
  346. if let nickname = threemaPushNotification?.nickname {
  347. fromName = nickname
  348. }
  349. if let senderId = threemaPushNotification?.from {
  350. if UserSettings.shared().blacklist.contains(senderId) {
  351. // User is blocked, do not send push
  352. return
  353. }
  354. let contact = ContactStore.shared().contact(forIdentity: senderId)
  355. if contact == nil {
  356. if UserSettings.shared().blockUnknown == true {
  357. // Unknown user, do not send push
  358. return
  359. }
  360. }
  361. if contact != nil && !UserSettings.shared().pushShowNickname {
  362. fromName = contact!.displayName
  363. }
  364. else if fromName == nil || fromName?.count == 0 {
  365. fromName = senderId
  366. }
  367. }
  368. if let localThreemaPushNotification = threemaPushNotification, localThreemaPushNotification.command == .newGroupMessage {
  369. // group message
  370. body = String(format: NSLocalizedString("new_group_message_from_x", comment: ""), fromName!)
  371. cmd = "newgroupmsg"
  372. categoryIdentifier = "GROUP"
  373. } else {
  374. // single message
  375. body = String(format: NSLocalizedString("new_message_from_x", comment: ""), fromName!)
  376. cmd = "newmsg"
  377. categoryIdentifier = "SINGLE"
  378. }
  379. }
  380. var silent = false
  381. if pushSetting != nil {
  382. silent = pushSetting!.silent
  383. }
  384. createNotification(title: title, body: body, attachmentName: attachmentName, attachmentUrl: attachmentUrl, cmd: cmd!, categoryIdentifier: categoryIdentifier!, silent: silent, setFireDate: setFireDate, groupId: groupId, fromName: fromName)
  385. }
  386. /// Remove notification if is there already one
  387. private func removeNotification() {
  388. UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: [key])
  389. }
  390. private func createNotification(title: String?, body: String?, attachmentName: String?, attachmentUrl: URL?, cmd: String, categoryIdentifier: String, silent: Bool, setFireDate: Bool, groupId: String?, fromName: String?) {
  391. var pushSound = UserSettings.shared().pushSound
  392. if categoryIdentifier == "GROUP" {
  393. pushSound = UserSettings.shared().pushGroupSound
  394. }
  395. let notification = UNMutableNotificationContent()
  396. notification.title = title ?? ""
  397. notification.body = body ?? ""
  398. if categoryIdentifier == "GROUP" {
  399. if UserSettings.shared().pushGroupSound != "none" && silent == false {
  400. notification.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: pushSound! + ".caf"))
  401. }
  402. } else {
  403. if UserSettings.shared().pushSound != "none" && silent == false {
  404. notification.sound = UNNotificationSound(named: UNNotificationSoundName(rawValue: pushSound! + ".caf"))
  405. }
  406. }
  407. if let attachmentName = attachmentName, let attachmentUrl = attachmentUrl, processed {
  408. do {
  409. let attachment = try UNNotificationAttachment(identifier: attachmentName, url: attachmentUrl, options: nil)
  410. notification.attachments = [attachment]
  411. }
  412. catch {
  413. }
  414. }
  415. notification.badge = NotificationManager.sharedInstance().unreadMessagesCount(!self.processed)
  416. var trigger: UNTimeIntervalNotificationTrigger? = nil
  417. if setFireDate == true {
  418. trigger = UNTimeIntervalNotificationTrigger(timeInterval: 30, repeats: false)
  419. fireDate = trigger!.nextTriggerDate()
  420. }
  421. if categoryIdentifier == "GROUP" && groupId != nil {
  422. notification.userInfo = ["threema": ["cmd": cmd, "from": senderId, "messageId": messageId, "groupId": groupId!]]
  423. } else {
  424. notification.userInfo = ["threema": ["cmd": cmd, "from": senderId, "messageId": messageId]]
  425. }
  426. if categoryIdentifier == "SINGLE" || categoryIdentifier == "GROUP" {
  427. if UserSettings.shared().pushDecrypt {
  428. notification.categoryIdentifier = categoryIdentifier
  429. } else {
  430. notification.categoryIdentifier = ""
  431. }
  432. } else {
  433. notification.categoryIdentifier = categoryIdentifier
  434. }
  435. // Group notifictions
  436. if categoryIdentifier == "SINGLE" {
  437. notification.threadIdentifier = "SINGLE-\(senderId)"
  438. } else if categoryIdentifier == "GROUP" {
  439. if let groupId = groupId {
  440. notification.threadIdentifier = "GROUP-\(groupId)"
  441. if #available(iOS 12, *) {
  442. if let fromName = fromName {
  443. notification.summaryArgument = fromName
  444. }
  445. }
  446. }
  447. }
  448. let notificationRequest = UNNotificationRequest(identifier: self.key, content: notification, trigger: trigger)
  449. let center = UNUserNotificationCenter.current()
  450. center.add(notificationRequest, withCompletionHandler: { _ in
  451. ValidationLogger.shared().logString("Push: Added message \(self.messageId) to notification center with trigger \(trigger?.timeInterval ?? 0)s")
  452. })
  453. }
  454. private func threemaNewMessageReceived() {
  455. if (baseMessage != nil) {
  456. if Thread.isMainThread {
  457. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "ThreemaNewMessageReceived"), object: baseMessage, userInfo: nil)
  458. } else {
  459. DispatchQueue.main.sync {
  460. NotificationCenter.default.post(name: NSNotification.Name(rawValue: "ThreemaNewMessageReceived"), object: baseMessage, userInfo: nil)
  461. }
  462. }
  463. }
  464. }
  465. // MARK: NSCoding
  466. public convenience required init?(coder aDecoder: NSCoder) {
  467. let dSenderId = aDecoder.decodeObject(forKey: "senderId") as! String
  468. let dMessageId = aDecoder.decodeObject(forKey: "messageId") as! String
  469. let dAbstractMessage = aDecoder.decodeObject(forKey: "abstractMessage") as? AbstractMessage
  470. let dGenericThreemaDict = aDecoder.decodeObject(forKey: "threemaDict")
  471. if dAbstractMessage != nil {
  472. if dAbstractMessage!.fromIdentity == nil {
  473. dAbstractMessage!.fromIdentity = dSenderId
  474. }
  475. self.init(receivedAbstractMessage: dAbstractMessage!)
  476. } else {
  477. if let dThreemaPush = dGenericThreemaDict as? ThreemaPushNotification {
  478. self.init(senderIdentity: dSenderId, messageIdentity: dMessageId, threemaPush: dThreemaPush)
  479. } else if let dThreemaDict = dGenericThreemaDict as? [String: Any] {
  480. // For backwards compatibility before 4.6.2 we also support reading the old format
  481. self.init(senderIdentity: dSenderId, messageIdentity: dMessageId, pushPayload: dThreemaDict)
  482. } else {
  483. self.init(senderIdentity: dSenderId, messageIdentity: dMessageId)
  484. }
  485. }
  486. self.fireDate = aDecoder.decodeObject(forKey: "fireDate") as? Date
  487. self.processed = aDecoder.decodeBool(forKey: "processed")
  488. }
  489. func encode(with aCoder: NSCoder) {
  490. aCoder.encode(self.senderId, forKey: "senderId")
  491. aCoder.encode(self.messageId, forKey: "messageId")
  492. aCoder.encode(self.abstractMessage, forKey: "abstractMessage")
  493. aCoder.encode(self.threemaPushNotification, forKey: "threemaDict")
  494. aCoder.encode(self.processed, forKey: "processed")
  495. aCoder.encode(self.fireDate, forKey: "fireDate")
  496. }
  497. }