123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940 |
- // _____ _
- // |_ _| |_ _ _ ___ ___ _ __ __ _
- // | | | ' \| '_/ -_) -_) ' \/ _` |_
- // |_| |_||_|_| \___\___|_|_|_\__,_(_)
- //
- // Threema iOS Client
- // Copyright (c) 2019-2020 Threema GmbH
- //
- // This program is free software: you can redistribute it and/or modify
- // it under the terms of the GNU Affero General Public License, version 3,
- // as published by the Free Software Foundation.
- //
- // This program is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Affero General Public License for more details.
- //
- // You should have received a copy of the GNU Affero General Public License
- // along with this program. If not, see <https://www.gnu.org/licenses/>.
- import Foundation
- import CocoaLumberjackSwift
- @objc class WCSessionManager: NSObject {
-
- @objc static let shared = WCSessionManager()
-
- private var sessions: [Data: WCSession] = [Data: WCSession]()
- private(set) var running: [Data] = [Data]()
- private var runningSessionsQueue:DispatchQueue = DispatchQueue(label: "ch.threema.runningSessionsQueue", attributes: [])
- private var observerAlreadySet: Bool = false
-
- private override init() {
- super.init()
- loadSessionsFromArchive()
- }
-
- public class func isWebHostAllowed(scannedHostName: String, whiteList: String) -> Bool {
- if whiteList.count == 0 {
- return false
- }
- let arr = whiteList.components(separatedBy: ",")
- for host in arr {
- let pattern = host.trimmingCharacters(in: .whitespaces)
- if pattern == scannedHostName {
- return true
- }
- let slicedPattern = String(pattern.dropFirst())
- if pattern.hasPrefix("*") && scannedHostName.hasSuffix(slicedPattern) {
- return true;
- }
- }
- return false
- }
- }
- // MARK: Private functions
- extension WCSessionManager {
-
- /**
- Add observers
- * batteryLevelDidChange
- * batteryStateDidChange
- * profileNicknameChanged
- * profilePictureChanged
- * blackListChanged
- */
- private func addObservers() {
- if observerAlreadySet == false {
- UIDevice.current.isBatteryMonitoringEnabled = true
- NotificationCenter.default.addObserver(self, selector: #selector(self.batteryLevelDidChange), name: UIDevice.batteryLevelDidChangeNotification , object: nil)
- NotificationCenter.default.addObserver(self, selector: #selector(self.batteryStateDidChange), name: UIDevice.batteryStateDidChangeNotification, object: nil)
- NotificationCenter.default.addObserver(self, selector: #selector(self.profileNicknameChanged), name: NSNotification.Name(rawValue: kNotificationProfileNicknameChanged), object: nil)
- NotificationCenter.default.addObserver(self, selector: #selector(self.profilePictureChanged), name: NSNotification.Name(rawValue: kNotificationProfilePictureChanged), object: nil)
- NotificationCenter.default.addObserver(self, selector: #selector(self.blackListChanged), name: NSNotification.Name(rawValue: kNotificationBlockedContact), object: nil)
- NotificationCenter.default.addObserver(self, selector: #selector(self.managedObjectContextDidChange), name: .NSManagedObjectContextObjectsDidChange, object: nil)
- NotificationCenter.default.addObserver(self, selector: #selector(self.refreshDirtyObjects), name: NSNotification.Name(rawValue: kNotificationDBRefreshedDirtyObject), object: nil)
- observerAlreadySet = true
- }
- }
-
- private func removeObservers() {
- UIDevice.current.isBatteryMonitoringEnabled = false
- NotificationCenter.default.removeObserver(self)
-
- observerAlreadySet = false
- }
-
- private func allSessionsSavePath() -> String {
- let documentDir = DocumentManager.documentsDirectory()
- return documentDir!.appendingPathComponent("AllWCSessions").path
- }
-
- private func runningSessionsSavePath() -> String {
- let documentDir = DocumentManager.documentsDirectory()
- return documentDir!.appendingPathComponent("RunningWCSessions").path
- }
- }
- // MARK: Public functions
- extension WCSessionManager {
- @objc public func saveSessionsToArchive() {
- let allSessionsSavePath = self.allSessionsSavePath()
- let runningSessionsSavePath = self.runningSessionsSavePath()
- do {
- try FileManager.default.removeItem(atPath: allSessionsSavePath)
- } catch { }
- NSKeyedArchiver.archiveRootObject(self.sessions, toFile: allSessionsSavePath)
-
- do {
- try FileManager.default.removeItem(atPath: runningSessionsSavePath)
- } catch { }
- NSKeyedArchiver.archiveRootObject(self.running, toFile: runningSessionsSavePath)
- }
-
- private func loadSessionsFromArchive() {
- let allSessionsSavePath = self.allSessionsSavePath()
- let runningSessionsSavePath = self.runningSessionsSavePath()
-
- if FileManager.default.fileExists(atPath: allSessionsSavePath) {
- if let allSessions = NSKeyedUnarchiver.unarchiveObject(withFile: allSessionsSavePath) as? [Data: WCSession] {
- self.sessions = allSessions
- do {
- try FileManager.default.removeItem(atPath: allSessionsSavePath)
- } catch {
-
- }
- }
- }
-
- if FileManager.default.fileExists(atPath: runningSessionsSavePath) {
- if let runningSessions = NSKeyedUnarchiver.unarchiveObject(withFile: runningSessionsSavePath) as? [Data] {
- self.running = runningSessions
- do {
- try FileManager.default.removeItem(atPath: runningSessionsSavePath)
- } catch {
-
- }
- }
- }
- }
-
- /**
- Connect a old or new session. Search for correct session or create a new one
- */
- @objc public func connect(authToken: Data?, wca: String?, publicKeyHash: String) {
- canConnectToWebClient(completionHandler: { (isValid) in
- if isValid == true {
- if let webClientSession = WebClientSessionStore.shared.webClientSessionForHash(publicKeyHash) {
- var session: WCSession? = self.sessions[webClientSession.initiatorPermanentPublicKey!]
-
- if LicenseStore.requiresLicenseKey() == true {
- let mdmSetup = MDMSetup(setup: false)!
- if let webHosts = mdmSetup.webHosts() {
- if WCSessionManager.isWebHostAllowed(scannedHostName: webClientSession.saltyRTCHost!, whiteList: webHosts) == false {
- ValidationLogger.shared().logString("Threema Web: Scanned qr code host is not white listed")
- if AppDelegate.shared().isAppInBackground() {
- Utils.sendErrorLocalNotification(NSLocalizedString("webClient_scan_error_mdm_host_title", comment: ""), body: NSLocalizedString("webClient_scan_error_mdm_host_message", comment: ""), userInfo: nil)
- } else {
- let rootVC = UIApplication.shared.keyWindow?.rootViewController
- UIAlertTemplate.showAlert(owner: rootVC!, title: BundleUtil.localizedString(forKey: "webClient_scan_error_mdm_host_title"), message: BundleUtil.localizedString(forKey: "webClient_scan_error_mdm_host_message"))
- }
- return
-
- }
- }
- }
-
- if session != nil && wca != nil {
- if let connectionWca = session?.connectionWca() {
- if wca!.elementsEqual(connectionWca) {
- // same wca, ignore this request
- ValidationLogger.shared()?.logString("Threema Web: Ignore connect, because it's the same wca")
- return
- }
- }
- }
-
- if session == nil {
- session = WCSession.init(webClientSession: webClientSession)
- self.sessions[webClientSession.initiatorPermanentPublicKey!] = session
- }
- if wca != nil {
- session!.setWcaForConnection(wca: wca!)
- }
- self.addWCSessionToRunning(webClientSession: webClientSession)
- self.addObservers()
- ServerConnector.shared().sendPushOverrideTimeout()
- session!.connect(authToken: authToken)
- } else {
- // session not found
- }
- }
- })
- }
-
- @objc public func connect(authToken: Data?, wca: String?, webClientSession: WebClientSession) {
- canConnectToWebClient(completionHandler: { (isValid) in
- if isValid == true {
- var session: WCSession? = self.sessions[webClientSession.initiatorPermanentPublicKey!]
-
- if session != nil && wca != nil {
- if let connectionWca = session?.connectionWca() {
- if wca!.elementsEqual(connectionWca) {
- // same wca, ignore this request
- ValidationLogger.shared()?.logString("Threema Web: Ignore connect, because it's the same wca")
- return
- }
- }
- }
- if session == nil {
- session = WCSession.init(webClientSession: webClientSession)
- self.sessions[webClientSession.initiatorPermanentPublicKey!] = session
- }
-
- if LicenseStore.requiresLicenseKey() == true {
- let mdmSetup = MDMSetup(setup: false)!
- if let webHosts = mdmSetup.webHosts() {
- if WCSessionManager.isWebHostAllowed(scannedHostName: webClientSession.saltyRTCHost!, whiteList: webHosts) == false {
- ValidationLogger.shared().logString("Threema Web: Scanned qr code host is not white listed")
- if AppDelegate.shared().isAppInBackground() {
- Utils.sendErrorLocalNotification(NSLocalizedString("webClient_scan_error_mdm_host_title", comment: ""), body: NSLocalizedString("webClient_scan_error_mdm_host_message", comment: ""), userInfo: nil)
- } else {
- let rootVC = UIApplication.shared.keyWindow?.rootViewController
- UIAlertTemplate.showAlert(owner: rootVC!, title: BundleUtil.localizedString(forKey: "webClient_scan_error_mdm_host_title"), message: BundleUtil.localizedString(forKey: "webClient_scan_error_mdm_host_message"))
- }
- return
- }
- }
- }
- if wca != nil {
- session!.setWcaForConnection(wca: wca!)
- }
- self.addWCSessionToRunning(webClientSession: webClientSession)
- self.addObservers()
- ServerConnector.shared().sendPushOverrideTimeout()
- session!.connect(authToken: authToken)
- }
- })
- }
-
- @objc public func connectAllRunningSessions() {
- if running.count == 0 {
- ValidationLogger.shared()?.logString("Threema Web: There is no active session")
- WebClientSessionStore.shared.setAllWebClientSessionsInactive()
- return
- }
- addObservers()
- ServerConnector.shared().sendPushOverrideTimeout()
- ValidationLogger.shared()?.logString("Threema Web: Connect active sessions (\(running.count))")
- for publicKey in running {
- if let session = sessions[publicKey] {
- if session.connectionStatus() == .disconnected {
- ValidationLogger.shared()?.logString("Threema Web: Connect active session")
- session.connect(authToken: nil)
- } else {
- if let connectionStatus = session.connectionStatus() {
- ValidationLogger.shared()?.logString("Threema Web: Can't connect active session, wrong state \(connectionStatus)")
- }
- else {
- ValidationLogger.shared()?.logString("Threema Web: Can't connect active session, connectionStatus is nil!")
- }
- }
- }
- }
- }
-
- /**
- Stop all running sessions.
- Clears the list of running sessions
- */
- @objc public func stopAllSessions() {
- // disconnect all active sessions and set all sessions on core data to inactive
- ValidationLogger.shared().logString("Threema Web: Stop all active sessions")
- for publicKey in running {
- if let session = sessions[publicKey] {
- session.stop(close: true, forget: false, sendDisconnect: true, reason: .stop)
- }
- }
- removeObservers()
- }
-
- /**
- Stop and delete all running sessions.
- Clears the list of running sessions
- */
- @objc public func stopAndForgetAllSessions() {
- // disconnect all active sessions and set all sessions on core data to inactive
- ValidationLogger.shared().logString("Threema Web: Stop and forget all active sessions")
- WebClientSessionStore.shared.setAllWebClientSessionsInactive()
- for publicKey in running {
- if let session = sessions[publicKey] {
- session.stop(close: true, forget: true, sendDisconnect: true, reason: .stop)
- }
- }
- }
-
- /**
- Stop specific session.
- */
- public func stopSession(_ webClientSession: WebClientSession) {
- ValidationLogger.shared().logString("Threema Web: Stop session")
- if let session: WCSession = sessions[webClientSession.initiatorPermanentPublicKey!] {
- session.stop(close: true, forget: false, sendDisconnect: true, reason: .stop)
- }
- }
-
- /**
- Stop and delete specific session.
- */
- public func stopAndDeleteSession(_ webClientSession: WebClientSession) {
- ValidationLogger.shared().logString("Threema Web: Stop and delete all active sessions")
- let publicKey = webClientSession.initiatorPermanentPublicKey!
- if let session: WCSession = sessions[publicKey] {
- session.stop(close: true, forget: true, sendDisconnect: true, reason: .delete)
- sessions.removeValue(forKey: publicKey)
- }
-
- WebClientSessionStore.shared.deleteWebClientSession(webClientSession)
- }
-
- /**
- Remove WebClientSession from running list.
- */
- public func removeWCSessionFromRunning(_ session: WCSession) {
- if let publicKey = session.webClientSession?.initiatorPermanentPublicKey {
- runningSessionsQueue.sync {
- if let index = running.firstIndex(of: publicKey) {
- running.remove(at: index)
- }
- WebClientSessionStore.shared.updateWebClientSession(session: session.webClientSession!, active: false)
- }
- }
- if running.count == 0 {
- ServerConnector.shared().resetPushOverrideTimeout()
- }
- }
-
- /**
- Remove all not permanent sessions.
- */
- public func removeAllNotPermanentSessions() {
- //Remove all not permanent sessions
- WebClientSessionStore.shared.removeAllNotPermanentSessions()
- }
-
- @objc public func isRunningWCSession() -> Bool {
- return running.count > 0
- }
-
- public func pauseAllRunningSessions() {
- ValidationLogger.shared().logString("Threema Web: Pause all sessions")
- sendConnectionAckToAllActiveSessions()
- for publicKey in running {
- if let session = sessions[publicKey] {
- session.stop(close: false, forget: false, sendDisconnect: false, reason: .pause)
- }
- }
- saveSessionsToArchive()
- }
-
-
- @objc public func updateConversationPushSetting(conversation: Conversation) {
- for publicKey in running {
- if let session = sessions[publicKey] {
- let conversationResponse = WebConversationUpdate.init(conversation: conversation, objectMode: .modified, session: session)
- session.messageQueue.enqueue(data: conversationResponse.messagePack(), blackListed: false)
- }
- }
-
- }
- }
- extension WCSessionManager {
- // MARK: Private functions
-
- private func addWCSessionToRunning(webClientSession: WebClientSession) {
- runningSessionsQueue.sync {
- if !running.contains(webClientSession.initiatorPermanentPublicKey!) {
- running.append(webClientSession.initiatorPermanentPublicKey!)
- WebClientSessionStore.shared.updateWebClientSession(session: webClientSession, active: true)
- }
- }
- }
-
- private func canConnectToWebClient(completionHandler: @escaping ((_ isValid: Bool) -> Void)) {
- if UserSettings.shared().threemaWeb {
- if LicenseStore.shared().isValid() == true {
- completionHandler(true)
- } else {
- LicenseStore.shared()?.performLicenseCheck(completion: { (success) in
- if success == true {
- completionHandler(true)
- } else {
- ValidationLogger.shared()?.logString("Threema Web: LicenseStore is invalid, stop all sessions")
- self.stopAllSessions()
- completionHandler(false)
- }
- })
- }
- } else {
- ValidationLogger.shared()?.logString("Threema Web: LicenseStore is invalid, stop all sessions")
- self.stopAllSessions()
- completionHandler(false)
- }
- }
-
- private func sendConnectionAckToAllActiveSessions() {
- for publicKey in running {
- if let session = sessions[publicKey] {
- if let context = session.connectionContext() {
- context.sendConnectionAck()
- }
- }
- }
- }
-
- private func sendMessagePackToAllActiveSessions(messagePack: Data, blackListed: Bool) {
- for publicKey in running {
- if let session = sessions[publicKey] {
- session.sendMessageToWeb(blacklisted: blackListed, msgpack: messagePack)
- }
- }
- }
-
- private func sendMessagePackToAllActiveSessions(with requestedConversationId: String, messagePack: Data, blackListed: Bool) {
- for publicKey in running {
- if let session = sessions[publicKey] {
- if session.requestedConversations(contains: requestedConversationId) == true {
- session.sendMessageToWeb(blacklisted: blackListed, msgpack: messagePack)
- }
-
- }
- }
- }
-
- private func sendMessagePackToRequestedSession(with requestId: String, messagePack: Data, blackListed: Bool) {
- for publicKey in running {
- if let session = sessions[publicKey] {
- if (session.requestMessage(for: requestId) != nil) {
- session.sendMessageToWeb(blacklisted: blackListed, msgpack: messagePack)
- }
- }
- }
- }
-
- private func responseUpdateContact(contact: Contact, objectMode: WebReceiverUpdate.ObjectMode) {
- let receiverUpdate = WebReceiverUpdate.init(updatedContact: contact, objectMode: objectMode)
- DDLogVerbose("Threema Web: MessagePack -> Send update/receiver")
- sendMessagePackToAllActiveSessions(messagePack: receiverUpdate.messagePack(), blackListed: false)
- }
-
- private func responseUpdateAvatar(contact: Contact?, groupProxy: GroupProxy?) {
- if contact != nil {
- let avatarUpdate = WebAvatarUpdate.init(contact: contact!)
- DDLogVerbose("Threema Web: MessagePack -> Send update/avatar")
- sendMessagePackToAllActiveSessions(messagePack: avatarUpdate.messagePack(), blackListed: false)
- }
- else if groupProxy != nil {
- let avatarUpdate = WebAvatarUpdate.init(groupProxy: groupProxy!)
- DDLogVerbose("Threema Web: MessagePack -> Send update/avatar")
- sendMessagePackToAllActiveSessions(messagePack: avatarUpdate.messagePack(), blackListed: false)
- }
- }
-
- private func responseUpdateMessage(with requestedConversationId: String, message: BaseMessage, conversation: Conversation, objectMode: WebMessagesUpdate.ObjectMode, exclude requestId: String) {
- for publicKey in running {
- if let session = sessions[publicKey] {
- if session.requestedConversations(contains: requestedConversationId) == true {
- if (session.requestMessage(for: requestId) == nil) {
- sendResponseUpdateMessage(message: message, conversation: conversation, objectMode: objectMode, session: session)
- }
- }
- }
- }
- }
-
- private func responseUpdateMessage(with requestedConversationId: String, message: BaseMessage, conversation: Conversation, objectMode: WebMessagesUpdate.ObjectMode) {
- for publicKey in running {
- if let session = sessions[publicKey] {
- if session.requestedConversations(contains: requestedConversationId) == true {
- sendResponseUpdateMessage(message: message, conversation: conversation, objectMode: objectMode, session: session)
- }
- }
- }
- }
-
- private func sendResponseUpdateMessage(message: BaseMessage, conversation: Conversation, objectMode: WebMessagesUpdate.ObjectMode, session: WCSession) {
- if objectMode == .removed {
- let messageUpdate: WebMessagesUpdate = WebMessagesUpdate.init(baseMessage: message, conversation: conversation, objectMode: objectMode, session: session)
- DDLogVerbose("Threema Web: MessagePack -> Send update/messages")
- session.sendMessageToWeb(blacklisted: false, msgpack: messageUpdate.messagePack())
- } else {
- let messageUpdate: WebMessagesUpdate = WebMessagesUpdate.init(baseMessage: message, conversation: conversation, objectMode: objectMode, session: session)
- DDLogVerbose("Threema Web: MessagePack -> Send update/messages")
- session.sendMessageToWeb(blacklisted: false, msgpack: messageUpdate.messagePack())
- }
- }
-
- private func responseUpdateConversation(conversation: Conversation, objectMode: WebConversationUpdate.ObjectMode) {
- for publicKey in running {
- if let session = sessions[publicKey] {
- let conversationResponse = WebConversationUpdate.init(conversation: conversation, objectMode: objectMode, session: session)
- DDLogVerbose("Threema Web: MessagePack -> Send update/conversation")
- session.sendMessageToWeb(blacklisted: false, msgpack: conversationResponse.messagePack())
- }
- }
- }
-
- private func responseUpdateGroup(group: GroupProxy, objectMode: WebReceiverUpdate.ObjectMode) {
- let receiverUpdate = WebReceiverUpdate.init(updatedGroup: group, objectMode: objectMode)
- DDLogVerbose("Threema Web: MessagePack -> Send update/receiver")
- sendMessagePackToAllActiveSessions(messagePack: receiverUpdate.messagePack(), blackListed: false)
- }
- private func responseUpdateTyping(identity: String, isTyping: Bool) {
- let typingUpdate = WebTypingUpdate.init(identity: identity, typing: isTyping)
- DDLogVerbose("Threema Web: MessagePack -> Send update/typing")
- sendMessagePackToAllActiveSessions(messagePack: typingUpdate.messagePack(), blackListed: true)
- }
-
- private func responseUpdateDeletedConversation(conversation: Conversation, contact: Contact?, objectMode: WebConversationUpdate.ObjectMode) {
- let conversationResponse = WebConversationUpdate.init(conversation: conversation, contact: contact, objectMode: objectMode)
- DDLogVerbose("Threema Web: MessagePack -> Send update/conversation")
- sendMessagePackToAllActiveSessions(messagePack: conversationResponse.messagePack(), blackListed: false)
- }
- private func webRequestMessage(for requestId: String) -> WebAbstractMessage? {
- for publicKey in running {
- if let session = sessions[publicKey] {
- if let webAbstractMessage = session.requestMessage(for: requestId) {
- return webAbstractMessage
- }
- }
- }
- return nil
- }
-
- private func removeWebRequestMessage(with requestId: String) {
- for publicKey in running {
- if let session = sessions[publicKey] {
- if (session.requestMessage(for: requestId) != nil) {
- session.removeRequestCreateMessage(requestId: requestId)
- }
- }
- }
- }
- }
- extension WCSessionManager {
- // MARK: Database Observer
-
- @objc func managedObjectContextDidChange(notification: NSNotification) {
- let managedObjectContext = notification.object as! NSManagedObjectContext
-
- // MARK: update
- self.handleUpdatedObjects(updatedObjects: managedObjectContext.updatedObjects)
-
- // MARK: inserted
- self.handleInsertedObjects(insertedObjects: managedObjectContext.insertedObjects)
-
- // MARK: deleted
- self.handleDeletedObjects(deletedObjects: managedObjectContext.deletedObjects)
- }
-
- private func handleUpdatedObjects(updatedObjects: Set<NSManagedObject>) {
- for managedObject in updatedObjects {
- switch managedObject {
- case is Contact:
- self.updateContact(managedObject as! Contact)
- case is BaseMessage:
- self.updateBaseMessage(managedObject as! BaseMessage)
- case is Conversation:
- self.updateConversation(managedObject as! Conversation)
- default:
- break
- }
- }
- }
-
- private func handleInsertedObjects(insertedObjects: Set<NSManagedObject>) {
- let backgroundKey = kAppAckBackgroundTask + SwiftUtils.pseudoRandomString(length: 10)
- BackgroundTaskManager.shared.newBackgroundTask(key: backgroundKey, timeout: Int(kAppCoreDataProcessMessageBackgroundTaskTime)) {
- for managedObject in insertedObjects {
- switch managedObject {
- case is Contact:
- let contact = managedObject as! Contact
- if contact.identity != nil && contact.publicKey != nil {
- let objectMode: WebReceiverUpdate.ObjectMode = .new
- self.responseUpdateContact(contact: contact, objectMode: objectMode)
- }
- case is BaseMessage:
- self.insertBaseMessage(managedObject as! BaseMessage)
- case is Conversation:
- self.insertConversation(managedObject as! Conversation)
- default:
- break
- }
- }
-
- BackgroundTaskManager.shared.cancelBackgroundTask(key: backgroundKey)
- }
- }
-
- private func updateContact(_ contact: Contact) {
- let changedValues = contact.changedValues()
-
- let backgroundKey = kAppAckBackgroundTask + SwiftUtils.pseudoRandomString(length: 10)
- BackgroundTaskManager.shared.newBackgroundTask(key: backgroundKey, timeout: Int(kAppCoreDataProcessMessageBackgroundTaskTime)) {
- 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")) {
- let objectMode: WebReceiverUpdate.ObjectMode = .modified
- self.responseUpdateContact(contact: contact, objectMode: objectMode)
- }
- if contact.changedValues().keys.contains("contactImage") || contact.changedValues().keys.contains("imageData") {
- self.responseUpdateAvatar(contact: contact, groupProxy: nil)
- }
- BackgroundTaskManager.shared.cancelBackgroundTask(key: backgroundKey)
- }
- }
-
- private func updateBaseMessage(_ baseMessage: BaseMessage) {
- let changedValues = baseMessage.changedValuesForCurrentEvent()
-
- let backgroundKey = kAppAckBackgroundTask + SwiftUtils.pseudoRandomString(length: 10)
- BackgroundTaskManager.shared.newBackgroundTask(key: backgroundKey, timeout: Int(kAppCoreDataProcessMessageBackgroundTaskTime)) {
-
- guard let conversation = baseMessage.conversation else {
- return
- }
-
- let identity = conversation.isGroup() ? conversation.groupId.hexEncodedString() : self.baseMessageIdentity(baseMessage)
- self.processBaseMessageUpdate(baseMessage: baseMessage, changedValues: changedValues, identity: identity)
-
- if let lastMessage = conversation.lastMessage, lastMessage.id == baseMessage.id {
- if self.shouldSendUpdate(changedValues: changedValues) {
- let objectMode: WebConversationUpdate.ObjectMode = .modified
- self.responseUpdateConversation(conversation: conversation, objectMode: objectMode)
- // background task to send ack to server
- let backgroundKey = kAppAckBackgroundTask + baseMessage.id.hexEncodedString()
- BackgroundTaskManager.shared.newBackgroundTask(key: backgroundKey, timeout: Int(kAppAckBackgroundTaskTime), completionHandler: nil)
- }
- }
- BackgroundTaskManager.shared.cancelBackgroundTask(key: backgroundKey)
- }
- }
-
- private func processBaseMessageUpdate(baseMessage: BaseMessage, changedValues: [String : Any], identity: String) {
- if shouldSendUpdate(changedValues: changedValues) {
- let objectMode: WebMessagesUpdate.ObjectMode = .modified
- self.responseUpdateMessage(with: identity, message: baseMessage, conversation: baseMessage.conversation, objectMode: objectMode)
- // background task to send ack to server
- let backgroundKey = kAppAckBackgroundTask + baseMessage.id.hexEncodedString()
- BackgroundTaskManager.shared.newBackgroundTask(key: backgroundKey, timeout: Int(kAppAckBackgroundTaskTime), completionHandler:nil)
- }
- }
-
- private func shouldSendUpdate(changedValues: [String: Any]) -> Bool {
- if changedValues.count == 1 {
- let changedValueDict = changedValues.first
- if changedValueDict?.key == "progress" {
- return false
- }
- }
- else if changedValues.count == 0 {
- return false
- }
- return true
- }
-
- private func updateConversation(_ conversation: Conversation) {
- let changedValues = conversation.changedValues()
- let changedValuesForCurrentEvent = conversation.changedValuesForCurrentEvent()
- let backgroundKey = kAppAckBackgroundTask + SwiftUtils.pseudoRandomString(length: 10)
- BackgroundTaskManager.shared.newBackgroundTask(key: backgroundKey, timeout: Int(kAppCoreDataProcessMessageBackgroundTaskTime)) {
- if conversation.isGroup() {
- if changedValues.keys.contains("groupName") || changedValues.keys.contains("members") {
- let objectMode: WebReceiverUpdate.ObjectMode = .modified
- let groupProxy = GroupProxy.init(for: conversation)
- self.responseUpdateGroup(group: groupProxy!, objectMode: objectMode)
- }
- else if changedValues.keys.contains("groupImage") {
- let groupProxy = GroupProxy.init(for: conversation)
- self.responseUpdateAvatar(contact: nil, groupProxy: groupProxy)
- }
- }
- else if changedValuesForCurrentEvent.keys.contains("typing") {
- self.responseUpdateTyping(identity: conversation.contact.identity, isTyping: conversation.typing.boolValue)
- }
-
- if (changedValues.keys.contains("lastMessage") || changedValues.keys.contains("marked") || changedValues.keys.contains("unreadMessageCount")) && conversation.lastMessage != nil {
- let objectMode: WebConversationUpdate.ObjectMode = .modified
- self.responseUpdateConversation(conversation: conversation, objectMode: objectMode)
- }
- BackgroundTaskManager.shared.cancelBackgroundTask(key: backgroundKey)
- }
- }
-
- private func insertConversation(_ conversation: Conversation) {
- if conversation.isGroup() {
- let receiverObjectMode: WebReceiverUpdate.ObjectMode = .new
- let groupProxy = GroupProxy.init(for: conversation)
- self.responseUpdateGroup(group: groupProxy!, objectMode: receiverObjectMode)
- }
-
- let objectMode: WebConversationUpdate.ObjectMode = .new
- self.responseUpdateConversation(conversation: conversation, objectMode: objectMode)
- }
-
- private func insertBaseMessage(_ baseMessage: BaseMessage) {
- if let conversation = baseMessage.conversation {
- var id: String
- if conversation.isGroup() {
- id = conversation.groupId.hexEncodedString()
- } else {
- if let sender = baseMessage.sender {
- id = sender.identity
- } else {
- if let contact = conversation.contact {
- id = contact.identity
- } else {
- id = MyIdentityStore.shared().identity
- }
- }
- }
-
- switch baseMessage {
- case is TextMessage:
- self.processTextMessageResponse(baseMessage, id)
- case is SystemMessage:
- self.processSystemMessageResponse(baseMessage, id)
- case is FileMessage, is ImageMessage, is VideoMessage, is AudioMessage:
- self.processFileMessageResponse(baseMessage, id)
- default:
- break
- }
- }
- }
-
- private func processTextMessageResponse(_ baseMessage: BaseMessage, _ id: String) {
- var createTextMessageResponse: WebCreateTextMessageResponse?
- if baseMessage.webRequestId != nil {
- if let createTextMessageRequest = self.webRequestMessage(for: baseMessage.webRequestId) as? WebCreateTextMessageRequest {
- createTextMessageResponse = WebCreateTextMessageResponse.init(message: baseMessage, request: createTextMessageRequest)
- }
- }
-
- let objectMode: WebMessagesUpdate.ObjectMode = .new
- if createTextMessageResponse != nil && baseMessage.webRequestId != nil {
- DDLogVerbose("Threema Web: MessagePack -> Send create/textMessage")
- self.sendMessagePackToRequestedSession(with: baseMessage.webRequestId!, messagePack: createTextMessageResponse!.messagePack(), blackListed: false)
- self.responseUpdateMessage(with: id, message: baseMessage, conversation: baseMessage.conversation, objectMode: objectMode, exclude: baseMessage.webRequestId!)
- self.removeWebRequestMessage(with: baseMessage.webRequestId!)
- } else {
- self.responseUpdateMessage(with: id, message: baseMessage, conversation: baseMessage.conversation, objectMode: objectMode)
- }
-
- // background task to send ack to server
- let backgroundKey = kAppAckBackgroundTask + baseMessage.id.hexEncodedString()
- BackgroundTaskManager.shared.newBackgroundTask(key: backgroundKey, timeout: Int(kAppAckBackgroundTaskTime), completionHandler: nil)
- }
-
- private func processSystemMessageResponse(_ baseMessage: BaseMessage, _ id: String) {
- let objectMode: WebMessagesUpdate.ObjectMode = .new
- self.responseUpdateMessage(with: id, message: baseMessage, conversation: baseMessage.conversation, objectMode: objectMode)
- // background task to send ack to server
- let backgroundKey = kAppAckBackgroundTask + baseMessage.id.hexEncodedString()
- BackgroundTaskManager.shared.newBackgroundTask(key: backgroundKey, timeout: Int(kAppAckBackgroundTaskTime), completionHandler: nil)
- }
-
- private func processFileMessageResponse(_ baseMessage: BaseMessage, _ id: String) {
- var createFileMessageResponse: WebCreateFileMessageResponse?
- var backgroundIdentifier: String?
-
- if baseMessage.webRequestId != nil {
- if let createFileMessageRequest = self.webRequestMessage(for: baseMessage.webRequestId) as? WebCreateFileMessageRequest {
- createFileMessageRequest.ack = WebAbstractMessageAcknowledgement.init(baseMessage.webRequestId, true, nil)
- createFileMessageResponse = WebCreateFileMessageResponse.init(message: baseMessage, request: createFileMessageRequest)
- if let bgIdentifier = createFileMessageRequest.backgroundIdentifier {
- backgroundIdentifier = bgIdentifier
- }
- }
- }
-
- let objectMode: WebMessagesUpdate.ObjectMode = .new
- if createFileMessageResponse != nil && baseMessage.webRequestId != nil {
- DDLogVerbose("Threema Web: MessagePack -> Send create/fileMessage")
- self.sendMessagePackToRequestedSession(with: baseMessage.webRequestId!, messagePack: createFileMessageResponse!.messagePack(), blackListed: false)
- self.responseUpdateMessage(with: id, message: baseMessage, conversation: baseMessage.conversation, objectMode: objectMode, exclude: baseMessage.webRequestId!)
- self.removeWebRequestMessage(with: baseMessage.webRequestId!)
- } else {
- self.responseUpdateMessage(with: id, message: baseMessage, conversation: baseMessage.conversation, objectMode: objectMode)
- }
-
- // background task to send ack to server
- let backgroundKey = kAppAckBackgroundTask + baseMessage.id.hexEncodedString()
- BackgroundTaskManager.shared.newBackgroundTask(key: backgroundKey, timeout: Int(kAppAckBackgroundTaskTime), completionHandler: nil)
-
- if backgroundIdentifier != nil {
- BackgroundTaskManager.shared.cancelBackgroundTask(key: backgroundIdentifier!)
- }
- }
-
- private func handleDeletedObjects(deletedObjects: Set<NSManagedObject>) {
- let backgroundKey = kAppAckBackgroundTask + SwiftUtils.pseudoRandomString(length: 10)
- BackgroundTaskManager.shared.newBackgroundTask(key: backgroundKey, timeout: Int(kAppCoreDataProcessMessageBackgroundTaskTime)) {
- for managedObject in deletedObjects {
- if managedObject is Conversation {
- let conversation = managedObject as! Conversation
- let objectMode: WebConversationUpdate.ObjectMode = .removed
- let contact: Contact? = conversation.changedValuesForCurrentEvent()["contact"] as? Contact
- self.responseUpdateDeletedConversation(conversation: conversation, contact: contact, objectMode: objectMode)
- }
- else if managedObject is Contact {
- let contact = managedObject as! Contact
- if contact.identity != nil && contact.publicKey != nil {
- let objectMode: WebReceiverUpdate.ObjectMode = .removed
- self.responseUpdateContact(contact: contact, objectMode: objectMode)
- }
- }
- else if managedObject is BaseMessage {
- let baseMessage = managedObject as! BaseMessage
- let identity: String?
- var conversation: Conversation? = nil
-
- if baseMessage.changedValues().keys.contains("conversation") && baseMessage.changedValuesForCurrentEvent()["conversation"] != nil {
- conversation = baseMessage.changedValuesForCurrentEvent()["conversation"] as? Conversation
- }
- if baseMessage.conversation != nil {
- conversation = baseMessage.conversation
- }
-
- if conversation != nil {
- if baseMessage.sender != nil {
- identity = baseMessage.sender.identity
- } else {
- if conversation!.contact != nil {
- identity = conversation!.contact.identity
- }
- else {
- identity = MyIdentityStore.shared().identity
- }
- }
-
- if conversation!.isGroup() {
- let objectMode: WebMessagesUpdate.ObjectMode = .removed
- self.responseUpdateMessage(with: conversation!.groupId.hexEncodedString(), message: baseMessage, conversation: conversation!, objectMode: objectMode)
- }
- if identity != nil {
- if !conversation!.isGroup() {
- let objectMode: WebMessagesUpdate.ObjectMode = .removed
- self.responseUpdateMessage(with: identity!, message: baseMessage, conversation: conversation!, objectMode: objectMode)
- }
- }
- }
- }
- }
- BackgroundTaskManager.shared.cancelBackgroundTask(key: backgroundKey)
- }
- }
-
- private func baseMessageIdentity(_ baseMessage: BaseMessage) -> String {
- if let sender = baseMessage.sender {
- return sender.identity
- }
-
- if let contact = baseMessage.conversation.contact {
- return contact.identity
- }
-
- return MyIdentityStore.shared().identity
- }
- }
- extension WCSessionManager {
- // MARK: BatteryNotifications
-
- @objc func batteryLevelDidChange(_ notification: Notification) {
- let batteryResponse = WebBatteryStatusUpdate.init()
- DDLogVerbose("Threema Web: MessagePack -> Send update/batteryStatus")
- sendMessagePackToAllActiveSessions(messagePack: batteryResponse.messagePack(), blackListed: true)
- }
-
- @objc func batteryStateDidChange(_ notification: Notification) {
- let batteryResponse = WebBatteryStatusUpdate.init()
- DDLogVerbose("Threema Web: MessagePack -> Send update/batteryStatus")
- sendMessagePackToAllActiveSessions(messagePack: batteryResponse.messagePack(), blackListed: true)
- }
-
- // MARK: ProfileNotifications
-
- @objc func profileNicknameChanged(_ notification: Notification) {
- let profileUpdate = WebProfileUpdate.init(nicknameChanged: true, newNickname: MyIdentityStore.shared().pushFromName, newAvatar: nil, deleteAvatar: false)
- DDLogVerbose("Threema Web: MessagePack -> Send update/profile")
- sendMessagePackToAllActiveSessions(messagePack: profileUpdate.messagePack(), blackListed: false)
- }
-
- @objc func profilePictureChanged(_ notification: Notification) {
- var profileUpdate: WebProfileUpdate?
- if let profileDict = MyIdentityStore.shared().profilePicture as? [AnyHashable: Any] {
- if let pictureData = profileDict["ProfilePicture"] {
- profileUpdate = WebProfileUpdate.init(nicknameChanged: false, newNickname: nil, newAvatar: pictureData as? Data, deleteAvatar: false)
- } else {
- profileUpdate = WebProfileUpdate.init(nicknameChanged: false, newNickname: nil, newAvatar: nil, deleteAvatar: true)
- }
- } else {
- profileUpdate = WebProfileUpdate.init(nicknameChanged: false, newNickname: nil, newAvatar: nil, deleteAvatar: true)
- }
- DDLogVerbose("Threema Web: MessagePack -> Send update/profile")
- sendMessagePackToAllActiveSessions(messagePack: profileUpdate!.messagePack(), blackListed: false)
- }
-
- @objc func blackListChanged(_ notification: Notification) {
- let identity = notification.object as! String
- var contact: Contact?
-
- let entityManager = EntityManager()
- contact = entityManager.entityFetcher.contact(forId: identity)
-
- if contact != nil {
- if contact!.identity != nil && contact!.publicKey != nil {
- let objectMode: WebReceiverUpdate.ObjectMode = .modified
- self.responseUpdateContact(contact: contact!, objectMode: objectMode)
- }
- }
- }
-
- @objc func refreshDirtyObjects(_ notification: Notification) {
- if let objectID = notification.userInfo?[kKeyObjectID] as? NSManagedObjectID {
- let entityManager = EntityManager()
- if let managedObject = entityManager.entityFetcher.getManagedObject(by: objectID) as? NSManagedObject {
- var insertedObjects = Set<NSManagedObject>()
- insertedObjects.insert(managedObject)
- self.handleInsertedObjects(insertedObjects: insertedObjects)
- }
- }
- }
- }
|