WCSessionManager.swift 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2019-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 WCSessionManager: NSObject {
  23. @objc static let shared = WCSessionManager()
  24. private var sessions: [Data: WCSession] = [Data: WCSession]()
  25. private(set) var running: [Data] = [Data]()
  26. private var runningSessionsQueue:DispatchQueue = DispatchQueue(label: "ch.threema.runningSessionsQueue", attributes: [])
  27. private var observerAlreadySet: Bool = false
  28. private override init() {
  29. super.init()
  30. loadSessionsFromArchive()
  31. }
  32. public class func isWebHostAllowed(scannedHostName: String, whiteList: String) -> Bool {
  33. if whiteList.count == 0 {
  34. return false
  35. }
  36. let arr = whiteList.components(separatedBy: ",")
  37. for host in arr {
  38. let pattern = host.trimmingCharacters(in: .whitespaces)
  39. if pattern == scannedHostName {
  40. return true
  41. }
  42. let slicedPattern = String(pattern.dropFirst())
  43. if pattern.hasPrefix("*") && scannedHostName.hasSuffix(slicedPattern) {
  44. return true;
  45. }
  46. }
  47. return false
  48. }
  49. }
  50. // MARK: Private functions
  51. extension WCSessionManager {
  52. /**
  53. Add observers
  54. * batteryLevelDidChange
  55. * batteryStateDidChange
  56. * profileNicknameChanged
  57. * profilePictureChanged
  58. * blackListChanged
  59. */
  60. private func addObservers() {
  61. if observerAlreadySet == false {
  62. UIDevice.current.isBatteryMonitoringEnabled = true
  63. NotificationCenter.default.addObserver(self, selector: #selector(self.batteryLevelDidChange), name: UIDevice.batteryLevelDidChangeNotification , object: nil)
  64. NotificationCenter.default.addObserver(self, selector: #selector(self.batteryStateDidChange), name: UIDevice.batteryStateDidChangeNotification, object: nil)
  65. NotificationCenter.default.addObserver(self, selector: #selector(self.profileNicknameChanged), name: NSNotification.Name(rawValue: kNotificationProfileNicknameChanged), object: nil)
  66. NotificationCenter.default.addObserver(self, selector: #selector(self.profilePictureChanged), name: NSNotification.Name(rawValue: kNotificationProfilePictureChanged), object: nil)
  67. NotificationCenter.default.addObserver(self, selector: #selector(self.blackListChanged), name: NSNotification.Name(rawValue: kNotificationBlockedContact), object: nil)
  68. NotificationCenter.default.addObserver(self, selector: #selector(self.managedObjectContextDidChange), name: .NSManagedObjectContextObjectsDidChange, object: nil)
  69. NotificationCenter.default.addObserver(self, selector: #selector(self.refreshDirtyObjects), name: NSNotification.Name(rawValue: kNotificationDBRefreshedDirtyObject), object: nil)
  70. observerAlreadySet = true
  71. }
  72. }
  73. private func removeObservers() {
  74. UIDevice.current.isBatteryMonitoringEnabled = false
  75. NotificationCenter.default.removeObserver(self)
  76. observerAlreadySet = false
  77. }
  78. private func allSessionsSavePath() -> String {
  79. let documentDir = DocumentManager.documentsDirectory()
  80. return documentDir!.appendingPathComponent("AllWCSessions").path
  81. }
  82. private func runningSessionsSavePath() -> String {
  83. let documentDir = DocumentManager.documentsDirectory()
  84. return documentDir!.appendingPathComponent("RunningWCSessions").path
  85. }
  86. }
  87. // MARK: Public functions
  88. extension WCSessionManager {
  89. @objc public func saveSessionsToArchive() {
  90. let allSessionsSavePath = self.allSessionsSavePath()
  91. let runningSessionsSavePath = self.runningSessionsSavePath()
  92. do {
  93. try FileManager.default.removeItem(atPath: allSessionsSavePath)
  94. } catch { }
  95. NSKeyedArchiver.archiveRootObject(self.sessions, toFile: allSessionsSavePath)
  96. do {
  97. try FileManager.default.removeItem(atPath: runningSessionsSavePath)
  98. } catch { }
  99. NSKeyedArchiver.archiveRootObject(self.running, toFile: runningSessionsSavePath)
  100. }
  101. private func loadSessionsFromArchive() {
  102. let allSessionsSavePath = self.allSessionsSavePath()
  103. let runningSessionsSavePath = self.runningSessionsSavePath()
  104. if FileManager.default.fileExists(atPath: allSessionsSavePath) {
  105. if let allSessions = NSKeyedUnarchiver.unarchiveObject(withFile: allSessionsSavePath) as? [Data: WCSession] {
  106. self.sessions = allSessions
  107. do {
  108. try FileManager.default.removeItem(atPath: allSessionsSavePath)
  109. } catch {
  110. }
  111. }
  112. }
  113. if FileManager.default.fileExists(atPath: runningSessionsSavePath) {
  114. if let runningSessions = NSKeyedUnarchiver.unarchiveObject(withFile: runningSessionsSavePath) as? [Data] {
  115. self.running = runningSessions
  116. do {
  117. try FileManager.default.removeItem(atPath: runningSessionsSavePath)
  118. } catch {
  119. }
  120. }
  121. }
  122. }
  123. /**
  124. Connect a old or new session. Search for correct session or create a new one
  125. */
  126. @objc public func connect(authToken: Data?, wca: String?, publicKeyHash: String) {
  127. canConnectToWebClient(completionHandler: { (isValid) in
  128. if isValid == true {
  129. if let webClientSession = WebClientSessionStore.shared.webClientSessionForHash(publicKeyHash) {
  130. var session: WCSession? = self.sessions[webClientSession.initiatorPermanentPublicKey!]
  131. if LicenseStore.requiresLicenseKey() == true {
  132. let mdmSetup = MDMSetup(setup: false)!
  133. if let webHosts = mdmSetup.webHosts() {
  134. if WCSessionManager.isWebHostAllowed(scannedHostName: webClientSession.saltyRTCHost!, whiteList: webHosts) == false {
  135. ValidationLogger.shared().logString("Threema Web: Scanned qr code host is not white listed")
  136. if AppDelegate.shared().isAppInBackground() {
  137. Utils.sendErrorLocalNotification(NSLocalizedString("webClient_scan_error_mdm_host_title", comment: ""), body: NSLocalizedString("webClient_scan_error_mdm_host_message", comment: ""), userInfo: nil)
  138. } else {
  139. let rootVC = UIApplication.shared.keyWindow?.rootViewController
  140. UIAlertTemplate.showAlert(owner: rootVC!, title: BundleUtil.localizedString(forKey: "webClient_scan_error_mdm_host_title"), message: BundleUtil.localizedString(forKey: "webClient_scan_error_mdm_host_message"))
  141. }
  142. return
  143. }
  144. }
  145. }
  146. if session != nil && wca != nil {
  147. if let connectionWca = session?.connectionWca() {
  148. if wca!.elementsEqual(connectionWca) {
  149. // same wca, ignore this request
  150. ValidationLogger.shared()?.logString("Threema Web: Ignore connect, because it's the same wca")
  151. return
  152. }
  153. }
  154. }
  155. if session == nil {
  156. session = WCSession.init(webClientSession: webClientSession)
  157. self.sessions[webClientSession.initiatorPermanentPublicKey!] = session
  158. }
  159. if wca != nil {
  160. session!.setWcaForConnection(wca: wca!)
  161. }
  162. self.addWCSessionToRunning(webClientSession: webClientSession)
  163. self.addObservers()
  164. ServerConnector.shared().sendPushOverrideTimeout()
  165. session!.connect(authToken: authToken)
  166. } else {
  167. // session not found
  168. }
  169. }
  170. })
  171. }
  172. @objc public func connect(authToken: Data?, wca: String?, webClientSession: WebClientSession) {
  173. canConnectToWebClient(completionHandler: { (isValid) in
  174. if isValid == true {
  175. var session: WCSession? = self.sessions[webClientSession.initiatorPermanentPublicKey!]
  176. if session != nil && wca != nil {
  177. if let connectionWca = session?.connectionWca() {
  178. if wca!.elementsEqual(connectionWca) {
  179. // same wca, ignore this request
  180. ValidationLogger.shared()?.logString("Threema Web: Ignore connect, because it's the same wca")
  181. return
  182. }
  183. }
  184. }
  185. if session == nil {
  186. session = WCSession.init(webClientSession: webClientSession)
  187. self.sessions[webClientSession.initiatorPermanentPublicKey!] = session
  188. }
  189. if LicenseStore.requiresLicenseKey() == true {
  190. let mdmSetup = MDMSetup(setup: false)!
  191. if let webHosts = mdmSetup.webHosts() {
  192. if WCSessionManager.isWebHostAllowed(scannedHostName: webClientSession.saltyRTCHost!, whiteList: webHosts) == false {
  193. ValidationLogger.shared().logString("Threema Web: Scanned qr code host is not white listed")
  194. if AppDelegate.shared().isAppInBackground() {
  195. Utils.sendErrorLocalNotification(NSLocalizedString("webClient_scan_error_mdm_host_title", comment: ""), body: NSLocalizedString("webClient_scan_error_mdm_host_message", comment: ""), userInfo: nil)
  196. } else {
  197. let rootVC = UIApplication.shared.keyWindow?.rootViewController
  198. UIAlertTemplate.showAlert(owner: rootVC!, title: BundleUtil.localizedString(forKey: "webClient_scan_error_mdm_host_title"), message: BundleUtil.localizedString(forKey: "webClient_scan_error_mdm_host_message"))
  199. }
  200. return
  201. }
  202. }
  203. }
  204. if wca != nil {
  205. session!.setWcaForConnection(wca: wca!)
  206. }
  207. self.addWCSessionToRunning(webClientSession: webClientSession)
  208. self.addObservers()
  209. ServerConnector.shared().sendPushOverrideTimeout()
  210. session!.connect(authToken: authToken)
  211. }
  212. })
  213. }
  214. @objc public func connectAllRunningSessions() {
  215. if running.count == 0 {
  216. ValidationLogger.shared()?.logString("Threema Web: There is no active session")
  217. WebClientSessionStore.shared.setAllWebClientSessionsInactive()
  218. return
  219. }
  220. addObservers()
  221. ServerConnector.shared().sendPushOverrideTimeout()
  222. ValidationLogger.shared()?.logString("Threema Web: Connect active sessions (\(running.count))")
  223. for publicKey in running {
  224. if let session = sessions[publicKey] {
  225. if session.connectionStatus() == .disconnected {
  226. ValidationLogger.shared()?.logString("Threema Web: Connect active session")
  227. session.connect(authToken: nil)
  228. } else {
  229. if let connectionStatus = session.connectionStatus() {
  230. ValidationLogger.shared()?.logString("Threema Web: Can't connect active session, wrong state \(connectionStatus)")
  231. }
  232. else {
  233. ValidationLogger.shared()?.logString("Threema Web: Can't connect active session, connectionStatus is nil!")
  234. }
  235. }
  236. }
  237. }
  238. }
  239. /**
  240. Stop all running sessions.
  241. Clears the list of running sessions
  242. */
  243. @objc public func stopAllSessions() {
  244. // disconnect all active sessions and set all sessions on core data to inactive
  245. ValidationLogger.shared().logString("Threema Web: Stop all active sessions")
  246. for publicKey in running {
  247. if let session = sessions[publicKey] {
  248. session.stop(close: true, forget: false, sendDisconnect: true, reason: .stop)
  249. }
  250. }
  251. removeObservers()
  252. }
  253. /**
  254. Stop and delete all running sessions.
  255. Clears the list of running sessions
  256. */
  257. @objc public func stopAndForgetAllSessions() {
  258. // disconnect all active sessions and set all sessions on core data to inactive
  259. ValidationLogger.shared().logString("Threema Web: Stop and forget all active sessions")
  260. WebClientSessionStore.shared.setAllWebClientSessionsInactive()
  261. for publicKey in running {
  262. if let session = sessions[publicKey] {
  263. session.stop(close: true, forget: true, sendDisconnect: true, reason: .stop)
  264. }
  265. }
  266. }
  267. /**
  268. Stop specific session.
  269. */
  270. public func stopSession(_ webClientSession: WebClientSession) {
  271. ValidationLogger.shared().logString("Threema Web: Stop session")
  272. if let session: WCSession = sessions[webClientSession.initiatorPermanentPublicKey!] {
  273. session.stop(close: true, forget: false, sendDisconnect: true, reason: .stop)
  274. }
  275. }
  276. /**
  277. Stop and delete specific session.
  278. */
  279. public func stopAndDeleteSession(_ webClientSession: WebClientSession) {
  280. ValidationLogger.shared().logString("Threema Web: Stop and delete all active sessions")
  281. let publicKey = webClientSession.initiatorPermanentPublicKey!
  282. if let session: WCSession = sessions[publicKey] {
  283. session.stop(close: true, forget: true, sendDisconnect: true, reason: .delete)
  284. sessions.removeValue(forKey: publicKey)
  285. }
  286. WebClientSessionStore.shared.deleteWebClientSession(webClientSession)
  287. }
  288. /**
  289. Remove WebClientSession from running list.
  290. */
  291. public func removeWCSessionFromRunning(_ session: WCSession) {
  292. if let publicKey = session.webClientSession?.initiatorPermanentPublicKey {
  293. runningSessionsQueue.sync {
  294. if let index = running.firstIndex(of: publicKey) {
  295. running.remove(at: index)
  296. }
  297. WebClientSessionStore.shared.updateWebClientSession(session: session.webClientSession!, active: false)
  298. }
  299. }
  300. if running.count == 0 {
  301. ServerConnector.shared().resetPushOverrideTimeout()
  302. }
  303. }
  304. /**
  305. Remove all not permanent sessions.
  306. */
  307. public func removeAllNotPermanentSessions() {
  308. //Remove all not permanent sessions
  309. WebClientSessionStore.shared.removeAllNotPermanentSessions()
  310. }
  311. @objc public func isRunningWCSession() -> Bool {
  312. return running.count > 0
  313. }
  314. public func pauseAllRunningSessions() {
  315. ValidationLogger.shared().logString("Threema Web: Pause all sessions")
  316. sendConnectionAckToAllActiveSessions()
  317. for publicKey in running {
  318. if let session = sessions[publicKey] {
  319. session.stop(close: false, forget: false, sendDisconnect: false, reason: .pause)
  320. }
  321. }
  322. saveSessionsToArchive()
  323. }
  324. @objc public func updateConversationPushSetting(conversation: Conversation) {
  325. for publicKey in running {
  326. if let session = sessions[publicKey] {
  327. let conversationResponse = WebConversationUpdate.init(conversation: conversation, objectMode: .modified, session: session)
  328. session.messageQueue.enqueue(data: conversationResponse.messagePack(), blackListed: false)
  329. }
  330. }
  331. }
  332. }
  333. extension WCSessionManager {
  334. // MARK: Private functions
  335. private func addWCSessionToRunning(webClientSession: WebClientSession) {
  336. runningSessionsQueue.sync {
  337. if !running.contains(webClientSession.initiatorPermanentPublicKey!) {
  338. running.append(webClientSession.initiatorPermanentPublicKey!)
  339. WebClientSessionStore.shared.updateWebClientSession(session: webClientSession, active: true)
  340. }
  341. }
  342. }
  343. private func canConnectToWebClient(completionHandler: @escaping ((_ isValid: Bool) -> Void)) {
  344. if UserSettings.shared().threemaWeb {
  345. if LicenseStore.shared().isValid() == true {
  346. completionHandler(true)
  347. } else {
  348. LicenseStore.shared()?.performLicenseCheck(completion: { (success) in
  349. if success == true {
  350. completionHandler(true)
  351. } else {
  352. ValidationLogger.shared()?.logString("Threema Web: LicenseStore is invalid, stop all sessions")
  353. self.stopAllSessions()
  354. completionHandler(false)
  355. }
  356. })
  357. }
  358. } else {
  359. ValidationLogger.shared()?.logString("Threema Web: LicenseStore is invalid, stop all sessions")
  360. self.stopAllSessions()
  361. completionHandler(false)
  362. }
  363. }
  364. private func sendConnectionAckToAllActiveSessions() {
  365. for publicKey in running {
  366. if let session = sessions[publicKey] {
  367. if let context = session.connectionContext() {
  368. context.sendConnectionAck()
  369. }
  370. }
  371. }
  372. }
  373. private func sendMessagePackToAllActiveSessions(messagePack: Data, blackListed: Bool) {
  374. for publicKey in running {
  375. if let session = sessions[publicKey] {
  376. session.sendMessageToWeb(blacklisted: blackListed, msgpack: messagePack)
  377. }
  378. }
  379. }
  380. private func sendMessagePackToAllActiveSessions(with requestedConversationId: String, messagePack: Data, blackListed: Bool) {
  381. for publicKey in running {
  382. if let session = sessions[publicKey] {
  383. if session.requestedConversations(contains: requestedConversationId) == true {
  384. session.sendMessageToWeb(blacklisted: blackListed, msgpack: messagePack)
  385. }
  386. }
  387. }
  388. }
  389. private func sendMessagePackToRequestedSession(with requestId: String, messagePack: Data, blackListed: Bool) {
  390. for publicKey in running {
  391. if let session = sessions[publicKey] {
  392. if (session.requestMessage(for: requestId) != nil) {
  393. session.sendMessageToWeb(blacklisted: blackListed, msgpack: messagePack)
  394. }
  395. }
  396. }
  397. }
  398. private func responseUpdateContact(contact: Contact, objectMode: WebReceiverUpdate.ObjectMode) {
  399. let receiverUpdate = WebReceiverUpdate.init(updatedContact: contact, objectMode: objectMode)
  400. DDLogVerbose("Threema Web: MessagePack -> Send update/receiver")
  401. sendMessagePackToAllActiveSessions(messagePack: receiverUpdate.messagePack(), blackListed: false)
  402. }
  403. private func responseUpdateAvatar(contact: Contact?, groupProxy: GroupProxy?) {
  404. if contact != nil {
  405. let avatarUpdate = WebAvatarUpdate.init(contact: contact!)
  406. DDLogVerbose("Threema Web: MessagePack -> Send update/avatar")
  407. sendMessagePackToAllActiveSessions(messagePack: avatarUpdate.messagePack(), blackListed: false)
  408. }
  409. else if groupProxy != nil {
  410. let avatarUpdate = WebAvatarUpdate.init(groupProxy: groupProxy!)
  411. DDLogVerbose("Threema Web: MessagePack -> Send update/avatar")
  412. sendMessagePackToAllActiveSessions(messagePack: avatarUpdate.messagePack(), blackListed: false)
  413. }
  414. }
  415. private func responseUpdateMessage(with requestedConversationId: String, message: BaseMessage, conversation: Conversation, objectMode: WebMessagesUpdate.ObjectMode, exclude requestId: String) {
  416. for publicKey in running {
  417. if let session = sessions[publicKey] {
  418. if session.requestedConversations(contains: requestedConversationId) == true {
  419. if (session.requestMessage(for: requestId) == nil) {
  420. sendResponseUpdateMessage(message: message, conversation: conversation, objectMode: objectMode, session: session)
  421. }
  422. }
  423. }
  424. }
  425. }
  426. private func responseUpdateMessage(with requestedConversationId: String, message: BaseMessage, conversation: Conversation, objectMode: WebMessagesUpdate.ObjectMode) {
  427. for publicKey in running {
  428. if let session = sessions[publicKey] {
  429. if session.requestedConversations(contains: requestedConversationId) == true {
  430. sendResponseUpdateMessage(message: message, conversation: conversation, objectMode: objectMode, session: session)
  431. }
  432. }
  433. }
  434. }
  435. private func sendResponseUpdateMessage(message: BaseMessage, conversation: Conversation, objectMode: WebMessagesUpdate.ObjectMode, session: WCSession) {
  436. if objectMode == .removed {
  437. let messageUpdate: WebMessagesUpdate = WebMessagesUpdate.init(baseMessage: message, conversation: conversation, objectMode: objectMode, session: session)
  438. DDLogVerbose("Threema Web: MessagePack -> Send update/messages")
  439. session.sendMessageToWeb(blacklisted: false, msgpack: messageUpdate.messagePack())
  440. } else {
  441. let messageUpdate: WebMessagesUpdate = WebMessagesUpdate.init(baseMessage: message, conversation: conversation, objectMode: objectMode, session: session)
  442. DDLogVerbose("Threema Web: MessagePack -> Send update/messages")
  443. session.sendMessageToWeb(blacklisted: false, msgpack: messageUpdate.messagePack())
  444. }
  445. }
  446. private func responseUpdateConversation(conversation: Conversation, objectMode: WebConversationUpdate.ObjectMode) {
  447. for publicKey in running {
  448. if let session = sessions[publicKey] {
  449. let conversationResponse = WebConversationUpdate.init(conversation: conversation, objectMode: objectMode, session: session)
  450. DDLogVerbose("Threema Web: MessagePack -> Send update/conversation")
  451. session.sendMessageToWeb(blacklisted: false, msgpack: conversationResponse.messagePack())
  452. }
  453. }
  454. }
  455. private func responseUpdateGroup(group: GroupProxy, objectMode: WebReceiverUpdate.ObjectMode) {
  456. let receiverUpdate = WebReceiverUpdate.init(updatedGroup: group, objectMode: objectMode)
  457. DDLogVerbose("Threema Web: MessagePack -> Send update/receiver")
  458. sendMessagePackToAllActiveSessions(messagePack: receiverUpdate.messagePack(), blackListed: false)
  459. }
  460. private func responseUpdateTyping(identity: String, isTyping: Bool) {
  461. let typingUpdate = WebTypingUpdate.init(identity: identity, typing: isTyping)
  462. DDLogVerbose("Threema Web: MessagePack -> Send update/typing")
  463. sendMessagePackToAllActiveSessions(messagePack: typingUpdate.messagePack(), blackListed: true)
  464. }
  465. private func responseUpdateDeletedConversation(conversation: Conversation, contact: Contact?, objectMode: WebConversationUpdate.ObjectMode) {
  466. let conversationResponse = WebConversationUpdate.init(conversation: conversation, contact: contact, objectMode: objectMode)
  467. DDLogVerbose("Threema Web: MessagePack -> Send update/conversation")
  468. sendMessagePackToAllActiveSessions(messagePack: conversationResponse.messagePack(), blackListed: false)
  469. }
  470. private func webRequestMessage(for requestId: String) -> WebAbstractMessage? {
  471. for publicKey in running {
  472. if let session = sessions[publicKey] {
  473. if let webAbstractMessage = session.requestMessage(for: requestId) {
  474. return webAbstractMessage
  475. }
  476. }
  477. }
  478. return nil
  479. }
  480. private func removeWebRequestMessage(with requestId: String) {
  481. for publicKey in running {
  482. if let session = sessions[publicKey] {
  483. if (session.requestMessage(for: requestId) != nil) {
  484. session.removeRequestCreateMessage(requestId: requestId)
  485. }
  486. }
  487. }
  488. }
  489. }
  490. extension WCSessionManager {
  491. // MARK: Database Observer
  492. @objc func managedObjectContextDidChange(notification: NSNotification) {
  493. let managedObjectContext = notification.object as! NSManagedObjectContext
  494. // MARK: update
  495. self.handleUpdatedObjects(updatedObjects: managedObjectContext.updatedObjects)
  496. // MARK: inserted
  497. self.handleInsertedObjects(insertedObjects: managedObjectContext.insertedObjects)
  498. // MARK: deleted
  499. self.handleDeletedObjects(deletedObjects: managedObjectContext.deletedObjects)
  500. }
  501. private func handleUpdatedObjects(updatedObjects: Set<NSManagedObject>) {
  502. for managedObject in updatedObjects {
  503. switch managedObject {
  504. case is Contact:
  505. self.updateContact(managedObject as! Contact)
  506. case is BaseMessage:
  507. self.updateBaseMessage(managedObject as! BaseMessage)
  508. case is Conversation:
  509. self.updateConversation(managedObject as! Conversation)
  510. default:
  511. break
  512. }
  513. }
  514. }
  515. private func handleInsertedObjects(insertedObjects: Set<NSManagedObject>) {
  516. let backgroundKey = kAppAckBackgroundTask + SwiftUtils.pseudoRandomString(length: 10)
  517. BackgroundTaskManager.shared.newBackgroundTask(key: backgroundKey, timeout: Int(kAppCoreDataProcessMessageBackgroundTaskTime)) {
  518. for managedObject in insertedObjects {
  519. switch managedObject {
  520. case is Contact:
  521. let contact = managedObject as! Contact
  522. if contact.identity != nil && contact.publicKey != nil {
  523. let objectMode: WebReceiverUpdate.ObjectMode = .new
  524. self.responseUpdateContact(contact: contact, objectMode: objectMode)
  525. }
  526. case is BaseMessage:
  527. self.insertBaseMessage(managedObject as! BaseMessage)
  528. case is Conversation:
  529. self.insertConversation(managedObject as! Conversation)
  530. default:
  531. break
  532. }
  533. }
  534. BackgroundTaskManager.shared.cancelBackgroundTask(key: backgroundKey)
  535. }
  536. }
  537. private func updateContact(_ contact: Contact) {
  538. let changedValues = contact.changedValues()
  539. let backgroundKey = kAppAckBackgroundTask + SwiftUtils.pseudoRandomString(length: 10)
  540. BackgroundTaskManager.shared.newBackgroundTask(key: backgroundKey, timeout: Int(kAppCoreDataProcessMessageBackgroundTaskTime)) {
  541. if (contact.identity != nil) && (changedValues.keys.contains("firstName") || changedValues.keys.contains("lastName") || changedValues.keys.contains("displayName") || changedValues.keys.contains("publicNickname") || changedValues.keys.contains("verificationLevel") || changedValues.keys.contains("state") || changedValues.keys.contains("featureLevel") || changedValues.keys.contains("workContact") || changedValues.keys.contains("hidden")) {
  542. let objectMode: WebReceiverUpdate.ObjectMode = .modified
  543. self.responseUpdateContact(contact: contact, objectMode: objectMode)
  544. }
  545. if contact.changedValues().keys.contains("contactImage") || contact.changedValues().keys.contains("imageData") {
  546. self.responseUpdateAvatar(contact: contact, groupProxy: nil)
  547. }
  548. BackgroundTaskManager.shared.cancelBackgroundTask(key: backgroundKey)
  549. }
  550. }
  551. private func updateBaseMessage(_ baseMessage: BaseMessage) {
  552. let changedValues = baseMessage.changedValuesForCurrentEvent()
  553. let backgroundKey = kAppAckBackgroundTask + SwiftUtils.pseudoRandomString(length: 10)
  554. BackgroundTaskManager.shared.newBackgroundTask(key: backgroundKey, timeout: Int(kAppCoreDataProcessMessageBackgroundTaskTime)) {
  555. guard let conversation = baseMessage.conversation else {
  556. return
  557. }
  558. let identity = conversation.isGroup() ? conversation.groupId.hexEncodedString() : self.baseMessageIdentity(baseMessage)
  559. self.processBaseMessageUpdate(baseMessage: baseMessage, changedValues: changedValues, identity: identity)
  560. if let lastMessage = conversation.lastMessage, lastMessage.id == baseMessage.id {
  561. if self.shouldSendUpdate(changedValues: changedValues) {
  562. let objectMode: WebConversationUpdate.ObjectMode = .modified
  563. self.responseUpdateConversation(conversation: conversation, objectMode: objectMode)
  564. // background task to send ack to server
  565. let backgroundKey = kAppAckBackgroundTask + baseMessage.id.hexEncodedString()
  566. BackgroundTaskManager.shared.newBackgroundTask(key: backgroundKey, timeout: Int(kAppAckBackgroundTaskTime), completionHandler: nil)
  567. }
  568. }
  569. BackgroundTaskManager.shared.cancelBackgroundTask(key: backgroundKey)
  570. }
  571. }
  572. private func processBaseMessageUpdate(baseMessage: BaseMessage, changedValues: [String : Any], identity: String) {
  573. if shouldSendUpdate(changedValues: changedValues) {
  574. let objectMode: WebMessagesUpdate.ObjectMode = .modified
  575. self.responseUpdateMessage(with: identity, message: baseMessage, conversation: baseMessage.conversation, objectMode: objectMode)
  576. // background task to send ack to server
  577. let backgroundKey = kAppAckBackgroundTask + baseMessage.id.hexEncodedString()
  578. BackgroundTaskManager.shared.newBackgroundTask(key: backgroundKey, timeout: Int(kAppAckBackgroundTaskTime), completionHandler:nil)
  579. }
  580. }
  581. private func shouldSendUpdate(changedValues: [String: Any]) -> Bool {
  582. if changedValues.count == 1 {
  583. let changedValueDict = changedValues.first
  584. if changedValueDict?.key == "progress" {
  585. return false
  586. }
  587. }
  588. else if changedValues.count == 0 {
  589. return false
  590. }
  591. return true
  592. }
  593. private func updateConversation(_ conversation: Conversation) {
  594. let changedValues = conversation.changedValues()
  595. let changedValuesForCurrentEvent = conversation.changedValuesForCurrentEvent()
  596. let backgroundKey = kAppAckBackgroundTask + SwiftUtils.pseudoRandomString(length: 10)
  597. BackgroundTaskManager.shared.newBackgroundTask(key: backgroundKey, timeout: Int(kAppCoreDataProcessMessageBackgroundTaskTime)) {
  598. if conversation.isGroup() {
  599. if changedValues.keys.contains("groupName") || changedValues.keys.contains("members") {
  600. let objectMode: WebReceiverUpdate.ObjectMode = .modified
  601. let groupProxy = GroupProxy.init(for: conversation)
  602. self.responseUpdateGroup(group: groupProxy!, objectMode: objectMode)
  603. }
  604. else if changedValues.keys.contains("groupImage") {
  605. let groupProxy = GroupProxy.init(for: conversation)
  606. self.responseUpdateAvatar(contact: nil, groupProxy: groupProxy)
  607. }
  608. }
  609. else if changedValuesForCurrentEvent.keys.contains("typing") {
  610. self.responseUpdateTyping(identity: conversation.contact.identity, isTyping: conversation.typing.boolValue)
  611. }
  612. if (changedValues.keys.contains("lastMessage") || changedValues.keys.contains("marked") || changedValues.keys.contains("unreadMessageCount")) && conversation.lastMessage != nil {
  613. let objectMode: WebConversationUpdate.ObjectMode = .modified
  614. self.responseUpdateConversation(conversation: conversation, objectMode: objectMode)
  615. }
  616. BackgroundTaskManager.shared.cancelBackgroundTask(key: backgroundKey)
  617. }
  618. }
  619. private func insertConversation(_ conversation: Conversation) {
  620. if conversation.isGroup() {
  621. let receiverObjectMode: WebReceiverUpdate.ObjectMode = .new
  622. let groupProxy = GroupProxy.init(for: conversation)
  623. self.responseUpdateGroup(group: groupProxy!, objectMode: receiverObjectMode)
  624. }
  625. let objectMode: WebConversationUpdate.ObjectMode = .new
  626. self.responseUpdateConversation(conversation: conversation, objectMode: objectMode)
  627. }
  628. private func insertBaseMessage(_ baseMessage: BaseMessage) {
  629. if let conversation = baseMessage.conversation {
  630. var id: String
  631. if conversation.isGroup() {
  632. id = conversation.groupId.hexEncodedString()
  633. } else {
  634. if let sender = baseMessage.sender {
  635. id = sender.identity
  636. } else {
  637. if let contact = conversation.contact {
  638. id = contact.identity
  639. } else {
  640. id = MyIdentityStore.shared().identity
  641. }
  642. }
  643. }
  644. switch baseMessage {
  645. case is TextMessage:
  646. self.processTextMessageResponse(baseMessage, id)
  647. case is SystemMessage:
  648. self.processSystemMessageResponse(baseMessage, id)
  649. case is FileMessage, is ImageMessage, is VideoMessage, is AudioMessage:
  650. self.processFileMessageResponse(baseMessage, id)
  651. default:
  652. break
  653. }
  654. }
  655. }
  656. private func processTextMessageResponse(_ baseMessage: BaseMessage, _ id: String) {
  657. var createTextMessageResponse: WebCreateTextMessageResponse?
  658. if baseMessage.webRequestId != nil {
  659. if let createTextMessageRequest = self.webRequestMessage(for: baseMessage.webRequestId) as? WebCreateTextMessageRequest {
  660. createTextMessageResponse = WebCreateTextMessageResponse.init(message: baseMessage, request: createTextMessageRequest)
  661. }
  662. }
  663. let objectMode: WebMessagesUpdate.ObjectMode = .new
  664. if createTextMessageResponse != nil && baseMessage.webRequestId != nil {
  665. DDLogVerbose("Threema Web: MessagePack -> Send create/textMessage")
  666. self.sendMessagePackToRequestedSession(with: baseMessage.webRequestId!, messagePack: createTextMessageResponse!.messagePack(), blackListed: false)
  667. self.responseUpdateMessage(with: id, message: baseMessage, conversation: baseMessage.conversation, objectMode: objectMode, exclude: baseMessage.webRequestId!)
  668. self.removeWebRequestMessage(with: baseMessage.webRequestId!)
  669. } else {
  670. self.responseUpdateMessage(with: id, message: baseMessage, conversation: baseMessage.conversation, objectMode: objectMode)
  671. }
  672. // background task to send ack to server
  673. let backgroundKey = kAppAckBackgroundTask + baseMessage.id.hexEncodedString()
  674. BackgroundTaskManager.shared.newBackgroundTask(key: backgroundKey, timeout: Int(kAppAckBackgroundTaskTime), completionHandler: nil)
  675. }
  676. private func processSystemMessageResponse(_ baseMessage: BaseMessage, _ id: String) {
  677. let objectMode: WebMessagesUpdate.ObjectMode = .new
  678. self.responseUpdateMessage(with: id, message: baseMessage, conversation: baseMessage.conversation, objectMode: objectMode)
  679. // background task to send ack to server
  680. let backgroundKey = kAppAckBackgroundTask + baseMessage.id.hexEncodedString()
  681. BackgroundTaskManager.shared.newBackgroundTask(key: backgroundKey, timeout: Int(kAppAckBackgroundTaskTime), completionHandler: nil)
  682. }
  683. private func processFileMessageResponse(_ baseMessage: BaseMessage, _ id: String) {
  684. var createFileMessageResponse: WebCreateFileMessageResponse?
  685. var backgroundIdentifier: String?
  686. if baseMessage.webRequestId != nil {
  687. if let createFileMessageRequest = self.webRequestMessage(for: baseMessage.webRequestId) as? WebCreateFileMessageRequest {
  688. createFileMessageRequest.ack = WebAbstractMessageAcknowledgement.init(baseMessage.webRequestId, true, nil)
  689. createFileMessageResponse = WebCreateFileMessageResponse.init(message: baseMessage, request: createFileMessageRequest)
  690. if let bgIdentifier = createFileMessageRequest.backgroundIdentifier {
  691. backgroundIdentifier = bgIdentifier
  692. }
  693. }
  694. }
  695. let objectMode: WebMessagesUpdate.ObjectMode = .new
  696. if createFileMessageResponse != nil && baseMessage.webRequestId != nil {
  697. DDLogVerbose("Threema Web: MessagePack -> Send create/fileMessage")
  698. self.sendMessagePackToRequestedSession(with: baseMessage.webRequestId!, messagePack: createFileMessageResponse!.messagePack(), blackListed: false)
  699. self.responseUpdateMessage(with: id, message: baseMessage, conversation: baseMessage.conversation, objectMode: objectMode, exclude: baseMessage.webRequestId!)
  700. self.removeWebRequestMessage(with: baseMessage.webRequestId!)
  701. } else {
  702. self.responseUpdateMessage(with: id, message: baseMessage, conversation: baseMessage.conversation, objectMode: objectMode)
  703. }
  704. // background task to send ack to server
  705. let backgroundKey = kAppAckBackgroundTask + baseMessage.id.hexEncodedString()
  706. BackgroundTaskManager.shared.newBackgroundTask(key: backgroundKey, timeout: Int(kAppAckBackgroundTaskTime), completionHandler: nil)
  707. if backgroundIdentifier != nil {
  708. BackgroundTaskManager.shared.cancelBackgroundTask(key: backgroundIdentifier!)
  709. }
  710. }
  711. private func handleDeletedObjects(deletedObjects: Set<NSManagedObject>) {
  712. let backgroundKey = kAppAckBackgroundTask + SwiftUtils.pseudoRandomString(length: 10)
  713. BackgroundTaskManager.shared.newBackgroundTask(key: backgroundKey, timeout: Int(kAppCoreDataProcessMessageBackgroundTaskTime)) {
  714. for managedObject in deletedObjects {
  715. if managedObject is Conversation {
  716. let conversation = managedObject as! Conversation
  717. let objectMode: WebConversationUpdate.ObjectMode = .removed
  718. let contact: Contact? = conversation.changedValuesForCurrentEvent()["contact"] as? Contact
  719. self.responseUpdateDeletedConversation(conversation: conversation, contact: contact, objectMode: objectMode)
  720. }
  721. else if managedObject is Contact {
  722. let contact = managedObject as! Contact
  723. if contact.identity != nil && contact.publicKey != nil {
  724. let objectMode: WebReceiverUpdate.ObjectMode = .removed
  725. self.responseUpdateContact(contact: contact, objectMode: objectMode)
  726. }
  727. }
  728. else if managedObject is BaseMessage {
  729. let baseMessage = managedObject as! BaseMessage
  730. let identity: String?
  731. var conversation: Conversation? = nil
  732. if baseMessage.changedValues().keys.contains("conversation") && baseMessage.changedValuesForCurrentEvent()["conversation"] != nil {
  733. conversation = baseMessage.changedValuesForCurrentEvent()["conversation"] as? Conversation
  734. }
  735. if baseMessage.conversation != nil {
  736. conversation = baseMessage.conversation
  737. }
  738. if conversation != nil {
  739. if baseMessage.sender != nil {
  740. identity = baseMessage.sender.identity
  741. } else {
  742. if conversation!.contact != nil {
  743. identity = conversation!.contact.identity
  744. }
  745. else {
  746. identity = MyIdentityStore.shared().identity
  747. }
  748. }
  749. if conversation!.isGroup() {
  750. let objectMode: WebMessagesUpdate.ObjectMode = .removed
  751. self.responseUpdateMessage(with: conversation!.groupId.hexEncodedString(), message: baseMessage, conversation: conversation!, objectMode: objectMode)
  752. }
  753. if identity != nil {
  754. if !conversation!.isGroup() {
  755. let objectMode: WebMessagesUpdate.ObjectMode = .removed
  756. self.responseUpdateMessage(with: identity!, message: baseMessage, conversation: conversation!, objectMode: objectMode)
  757. }
  758. }
  759. }
  760. }
  761. }
  762. BackgroundTaskManager.shared.cancelBackgroundTask(key: backgroundKey)
  763. }
  764. }
  765. private func baseMessageIdentity(_ baseMessage: BaseMessage) -> String {
  766. if let sender = baseMessage.sender {
  767. return sender.identity
  768. }
  769. if let contact = baseMessage.conversation.contact {
  770. return contact.identity
  771. }
  772. return MyIdentityStore.shared().identity
  773. }
  774. }
  775. extension WCSessionManager {
  776. // MARK: BatteryNotifications
  777. @objc func batteryLevelDidChange(_ notification: Notification) {
  778. let batteryResponse = WebBatteryStatusUpdate.init()
  779. DDLogVerbose("Threema Web: MessagePack -> Send update/batteryStatus")
  780. sendMessagePackToAllActiveSessions(messagePack: batteryResponse.messagePack(), blackListed: true)
  781. }
  782. @objc func batteryStateDidChange(_ notification: Notification) {
  783. let batteryResponse = WebBatteryStatusUpdate.init()
  784. DDLogVerbose("Threema Web: MessagePack -> Send update/batteryStatus")
  785. sendMessagePackToAllActiveSessions(messagePack: batteryResponse.messagePack(), blackListed: true)
  786. }
  787. // MARK: ProfileNotifications
  788. @objc func profileNicknameChanged(_ notification: Notification) {
  789. let profileUpdate = WebProfileUpdate.init(nicknameChanged: true, newNickname: MyIdentityStore.shared().pushFromName, newAvatar: nil, deleteAvatar: false)
  790. DDLogVerbose("Threema Web: MessagePack -> Send update/profile")
  791. sendMessagePackToAllActiveSessions(messagePack: profileUpdate.messagePack(), blackListed: false)
  792. }
  793. @objc func profilePictureChanged(_ notification: Notification) {
  794. var profileUpdate: WebProfileUpdate?
  795. if let profileDict = MyIdentityStore.shared().profilePicture as? [AnyHashable: Any] {
  796. if let pictureData = profileDict["ProfilePicture"] {
  797. profileUpdate = WebProfileUpdate.init(nicknameChanged: false, newNickname: nil, newAvatar: pictureData as? Data, deleteAvatar: false)
  798. } else {
  799. profileUpdate = WebProfileUpdate.init(nicknameChanged: false, newNickname: nil, newAvatar: nil, deleteAvatar: true)
  800. }
  801. } else {
  802. profileUpdate = WebProfileUpdate.init(nicknameChanged: false, newNickname: nil, newAvatar: nil, deleteAvatar: true)
  803. }
  804. DDLogVerbose("Threema Web: MessagePack -> Send update/profile")
  805. sendMessagePackToAllActiveSessions(messagePack: profileUpdate!.messagePack(), blackListed: false)
  806. }
  807. @objc func blackListChanged(_ notification: Notification) {
  808. let identity = notification.object as! String
  809. var contact: Contact?
  810. let entityManager = EntityManager()
  811. contact = entityManager.entityFetcher.contact(forId: identity)
  812. if contact != nil {
  813. if contact!.identity != nil && contact!.publicKey != nil {
  814. let objectMode: WebReceiverUpdate.ObjectMode = .modified
  815. self.responseUpdateContact(contact: contact!, objectMode: objectMode)
  816. }
  817. }
  818. }
  819. @objc func refreshDirtyObjects(_ notification: Notification) {
  820. if let objectID = notification.userInfo?[kKeyObjectID] as? NSManagedObjectID {
  821. let entityManager = EntityManager()
  822. if let managedObject = entityManager.entityFetcher.getManagedObject(by: objectID) as? NSManagedObject {
  823. var insertedObjects = Set<NSManagedObject>()
  824. insertedObjects.insert(managedObject)
  825. self.handleInsertedObjects(insertedObjects: insertedObjects)
  826. }
  827. }
  828. }
  829. }