diff --git a/Session/AppDelegate+OpenGroupAPI.swift b/Session/AppDelegate+OpenGroupAPI.swift index 669c5880e..3681ccba0 100644 --- a/Session/AppDelegate+OpenGroupAPI.swift +++ b/Session/AppDelegate+OpenGroupAPI.swift @@ -4,7 +4,7 @@ extension AppDelegate : OpenGroupAPIDelegate { public func updateProfileIfNeeded(for channel: UInt64, on server: String, from info: OpenGroupInfo) { let storage = OWSPrimaryStorage.shared() let publicChatID = "\(server).\(channel)" - Storage.writeSync { transaction in + Storage.write { transaction in // Update user count storage.setUserCount(info.memberCount, forPublicChatWithID: publicChatID, in: transaction) let groupThread = TSGroupThread.getOrCreateThread(withGroupId: publicChatID.data(using: .utf8)!, groupType: .openGroup, transaction: transaction) diff --git a/Session/Configuration.swift b/Session/Configuration.swift index 4982d88a1..743658c20 100644 --- a/Session/Configuration.swift +++ b/Session/Configuration.swift @@ -11,6 +11,7 @@ final class Configuration : NSObject { @objc static func performMainSetup() { SNMessagingKit.configure( storage: Storage.shared, + messageReceiverDelegate: MessageReceiverDelegate.shared, signalStorage: OWSPrimaryStorage.shared(), identityKeyStore: OWSIdentityManager.shared(), sessionRestorationImplementation: SessionRestorationImplementation(), diff --git a/Session/Database/Storage+SessionMessagingKit.swift b/Session/Database/Storage+SessionMessagingKit.swift index 7911e3c12..1e3cc5818 100644 --- a/Session/Database/Storage+SessionMessagingKit.swift +++ b/Session/Database/Storage+SessionMessagingKit.swift @@ -201,124 +201,20 @@ extension Storage : SessionMessagingKitStorageProtocol { // MARK: - Message Handling - public func isBlocked(_ publicKey: String) -> Bool { - return SSKEnvironment.shared.blockingManager.isRecipientIdBlocked(publicKey) - } - - public func updateProfile(for publicKey: String, from profile: VisibleMessage.Profile, using transaction: Any) { - let transaction = transaction as! YapDatabaseReadWriteTransaction - let profileManager = SSKEnvironment.shared.profileManager - if let displayName = profile.displayName { - profileManager.updateProfileForContact(withID: publicKey, displayName: displayName, with: transaction) - } - if let profileKey = profile.profileKey, let profilePictureURL = profile.profilePictureURL, profileKey.count == kAES256_KeyByteLength { - profileManager.setProfileKeyData(profileKey, forRecipientId: publicKey, avatarURL: profilePictureURL) - } - } - /// Returns the ID of the thread the message was stored under along with the `TSIncomingMessage` that was constructed. - public func persist(_ message: VisibleMessage, using transaction: Any) -> (String, Any) { + public func persist(_ message: VisibleMessage, groupPublicKey: String?, using transaction: Any) -> (String, Any)? { let transaction = transaction as! YapDatabaseReadWriteTransaction - let thread = TSContactThread.getOrCreateThread(withContactId: message.sender!, transaction: transaction) - let message = TSIncomingMessage.from(message, associatedWith: thread, using: transaction) - message.save(with: transaction) - return (thread.uniqueId!, message) - } - - public func showTypingIndicatorIfNeeded(for senderPublicKey: String) { - var threadOrNil: TSContactThread? - Storage.read { transaction in - threadOrNil = TSContactThread.getWithContactId(senderPublicKey, transaction: transaction) - } - guard let thread = threadOrNil else { return } - func showTypingIndicatorsIfNeeded() { - SSKEnvironment.shared.typingIndicators.didReceiveTypingStartedMessage(inThread: thread, recipientId: senderPublicKey, deviceId: 1) - } - if Thread.current.isMainThread { - showTypingIndicatorsIfNeeded() + var threadOrNil: TSThread? + if let groupPublicKey = groupPublicKey { + guard Storage.shared.isClosedGroup(groupPublicKey) else { return nil } + let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) + threadOrNil = TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupID), transaction: transaction) } else { - DispatchQueue.main.async { - showTypingIndicatorsIfNeeded() - } - } - } - - public func hideTypingIndicatorIfNeeded(for senderPublicKey: String) { - var threadOrNil: TSContactThread? - Storage.read { transaction in - threadOrNil = TSContactThread.getWithContactId(senderPublicKey, transaction: transaction) + threadOrNil = TSContactThread.getOrCreateThread(withContactId: message.sender!, transaction: transaction) } - guard let thread = threadOrNil else { return } - func hideTypingIndicatorsIfNeeded() { - SSKEnvironment.shared.typingIndicators.didReceiveTypingStoppedMessage(inThread: thread, recipientId: senderPublicKey, deviceId: 1) - } - if Thread.current.isMainThread { - hideTypingIndicatorsIfNeeded() - } else { - DispatchQueue.main.async { - hideTypingIndicatorsIfNeeded() - } - } - } - - public func cancelTypingIndicatorsIfNeeded(for senderPublicKey: String) { - var threadOrNil: TSContactThread? - Storage.read { transaction in - threadOrNil = TSContactThread.getWithContactId(senderPublicKey, transaction: transaction) - } - guard let thread = threadOrNil else { return } - func cancelTypingIndicatorsIfNeeded() { - SSKEnvironment.shared.typingIndicators.didReceiveIncomingMessage(inThread: thread, recipientId: senderPublicKey, deviceId: 1) - } - if Thread.current.isMainThread { - cancelTypingIndicatorsIfNeeded() - } else { - DispatchQueue.main.async { - cancelTypingIndicatorsIfNeeded() - } - } - } - - public func notifyUserIfNeeded(for message: Any, threadID: String) { - guard let thread = TSThread.fetch(uniqueId: threadID) else { return } - Storage.read { transaction in - SSKEnvironment.shared.notificationsManager!.notifyUser(for: (message as! TSIncomingMessage), in: thread, transaction: transaction) - } - } - - public func markMessagesAsRead(_ timestamps: [UInt64], from senderPublicKey: String, at timestamp: UInt64) { - SSKEnvironment.shared.readReceiptManager.processReadReceipts(fromRecipientId: senderPublicKey, sentTimestamps: timestamps.map { NSNumber(value: $0) }, readTimestamp: timestamp) - } - - public func setExpirationTimer(to duration: UInt32, for senderPublicKey: String, using transaction: Any) { - let transaction = transaction as! YapDatabaseReadWriteTransaction - var threadOrNil: TSContactThread? - Storage.read { transaction in - threadOrNil = TSContactThread.getWithContactId(senderPublicKey, transaction: transaction) - } - guard let thread = threadOrNil else { return } - let configuration = OWSDisappearingMessagesConfiguration(threadId: thread.uniqueId!, enabled: true, durationSeconds: duration) - configuration.save(with: transaction) - let senderDisplayName = SSKEnvironment.shared.profileManager.profileNameForRecipient(withID: senderPublicKey, transaction: transaction) - let message = OWSDisappearingConfigurationUpdateInfoMessage(timestamp: NSDate.millisecondTimestamp(), thread: thread, - configuration: configuration, createdByRemoteName: senderDisplayName, createdInExistingGroup: false) - message.save(with: transaction) - SSKEnvironment.shared.disappearingMessagesJob.startIfNecessary() - } - - public func disableExpirationTimer(for senderPublicKey: String, using transaction: Any) { - let transaction = transaction as! YapDatabaseReadWriteTransaction - var threadOrNil: TSContactThread? - Storage.read { transaction in - threadOrNil = TSContactThread.getWithContactId(senderPublicKey, transaction: transaction) - } - guard let thread = threadOrNil else { return } - let configuration = OWSDisappearingMessagesConfiguration(threadId: thread.uniqueId!, enabled: false, durationSeconds: 24 * 60 * 60) - configuration.save(with: transaction) - let senderDisplayName = SSKEnvironment.shared.profileManager.profileNameForRecipient(withID: senderPublicKey, transaction: transaction) - let message = OWSDisappearingConfigurationUpdateInfoMessage(timestamp: NSDate.millisecondTimestamp(), thread: thread, - configuration: configuration, createdByRemoteName: senderDisplayName, createdInExistingGroup: false) + guard let thread = threadOrNil else { return nil } + let message = TSIncomingMessage.from(message, associatedWith: thread, using: transaction) message.save(with: transaction) - SSKEnvironment.shared.disappearingMessagesJob.startIfNecessary() + return (thread.uniqueId!, message) } } diff --git a/Session/MessageHandler.swift b/Session/MessageHandler.swift new file mode 100644 index 000000000..9d4ed5f85 --- /dev/null +++ b/Session/MessageHandler.swift @@ -0,0 +1,315 @@ +import SessionMessagingKit + +final class MessageReceiverDelegate : SessionMessagingKit.MessageReceiverDelegate { + + static let shared = MessageReceiverDelegate() + + + + // MARK: - Blocking + + public func isBlocked(_ publicKey: String) -> Bool { + return SSKEnvironment.shared.blockingManager.isRecipientIdBlocked(publicKey) + } + + + + // MARK: - Profiles + + public func updateProfile(for publicKey: String, from profile: VisibleMessage.Profile, using transaction: Any) { + let transaction = transaction as! YapDatabaseReadWriteTransaction + let profileManager = SSKEnvironment.shared.profileManager + if let displayName = profile.displayName { + profileManager.updateProfileForContact(withID: publicKey, displayName: displayName, with: transaction) + } + if let profileKey = profile.profileKey, let profilePictureURL = profile.profilePictureURL, profileKey.count == kAES256_KeyByteLength { + profileManager.setProfileKeyData(profileKey, forRecipientId: publicKey, avatarURL: profilePictureURL) + } + } + + + + // MARK: - Typing Indicators + + public func showTypingIndicatorIfNeeded(for senderPublicKey: String) { + var threadOrNil: TSContactThread? + Storage.read { transaction in + threadOrNil = TSContactThread.getWithContactId(senderPublicKey, transaction: transaction) + } + guard let thread = threadOrNil else { return } + func showTypingIndicatorsIfNeeded() { + SSKEnvironment.shared.typingIndicators.didReceiveTypingStartedMessage(inThread: thread, recipientId: senderPublicKey, deviceId: 1) + } + if Thread.current.isMainThread { + showTypingIndicatorsIfNeeded() + } else { + DispatchQueue.main.async { + showTypingIndicatorsIfNeeded() + } + } + } + + public func hideTypingIndicatorIfNeeded(for senderPublicKey: String) { + var threadOrNil: TSContactThread? + Storage.read { transaction in + threadOrNil = TSContactThread.getWithContactId(senderPublicKey, transaction: transaction) + } + guard let thread = threadOrNil else { return } + func hideTypingIndicatorsIfNeeded() { + SSKEnvironment.shared.typingIndicators.didReceiveTypingStoppedMessage(inThread: thread, recipientId: senderPublicKey, deviceId: 1) + } + if Thread.current.isMainThread { + hideTypingIndicatorsIfNeeded() + } else { + DispatchQueue.main.async { + hideTypingIndicatorsIfNeeded() + } + } + } + + public func cancelTypingIndicatorsIfNeeded(for senderPublicKey: String) { + var threadOrNil: TSContactThread? + Storage.read { transaction in + threadOrNil = TSContactThread.getWithContactId(senderPublicKey, transaction: transaction) + } + guard let thread = threadOrNil else { return } + func cancelTypingIndicatorsIfNeeded() { + SSKEnvironment.shared.typingIndicators.didReceiveIncomingMessage(inThread: thread, recipientId: senderPublicKey, deviceId: 1) + } + if Thread.current.isMainThread { + cancelTypingIndicatorsIfNeeded() + } else { + DispatchQueue.main.async { + cancelTypingIndicatorsIfNeeded() + } + } + } + + + + // MARK: - Notifications + + public func notifyUserIfNeeded(for message: Any, threadID: String) { + guard let thread = TSThread.fetch(uniqueId: threadID) else { return } + Storage.read { transaction in + SSKEnvironment.shared.notificationsManager!.notifyUser(for: (message as! TSIncomingMessage), in: thread, transaction: transaction) + } + } + + + + // MARK: - Read Receipts + + public func markMessagesAsRead(_ timestamps: [UInt64], from senderPublicKey: String, at timestamp: UInt64) { + SSKEnvironment.shared.readReceiptManager.processReadReceipts(fromRecipientId: senderPublicKey, sentTimestamps: timestamps.map { NSNumber(value: $0) }, readTimestamp: timestamp) + } + + + + // MARK: - Expiration + + public func setExpirationTimer(to duration: UInt32, for senderPublicKey: String, groupPublicKey: String?, using transaction: Any) { + let transaction = transaction as! YapDatabaseReadWriteTransaction + var threadOrNil: TSThread? + Storage.read { transaction in + if let groupPublicKey = groupPublicKey { + guard Storage.shared.isClosedGroup(groupPublicKey) else { return } + let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) + threadOrNil = TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupID), transaction: transaction) + } else { + threadOrNil = TSContactThread.getWithContactId(senderPublicKey, transaction: transaction) + } + } + guard let thread = threadOrNil else { return } + let configuration = OWSDisappearingMessagesConfiguration(threadId: thread.uniqueId!, enabled: true, durationSeconds: duration) + configuration.save(with: transaction) + let senderDisplayName = SSKEnvironment.shared.profileManager.profileNameForRecipient(withID: senderPublicKey, transaction: transaction) + let message = OWSDisappearingConfigurationUpdateInfoMessage(timestamp: NSDate.millisecondTimestamp(), thread: thread, + configuration: configuration, createdByRemoteName: senderDisplayName, createdInExistingGroup: false) + message.save(with: transaction) + SSKEnvironment.shared.disappearingMessagesJob.startIfNecessary() + } + + public func disableExpirationTimer(for senderPublicKey: String, groupPublicKey: String?, using transaction: Any) { + let transaction = transaction as! YapDatabaseReadWriteTransaction + var threadOrNil: TSThread? + Storage.read { transaction in + if let groupPublicKey = groupPublicKey { + guard Storage.shared.isClosedGroup(groupPublicKey) else { return } + let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) + threadOrNil = TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupID), transaction: transaction) + } else { + threadOrNil = TSContactThread.getWithContactId(senderPublicKey, transaction: transaction) + } + } + guard let thread = threadOrNil else { return } + let configuration = OWSDisappearingMessagesConfiguration(threadId: thread.uniqueId!, enabled: false, durationSeconds: 24 * 60 * 60) + configuration.save(with: transaction) + let senderDisplayName = SSKEnvironment.shared.profileManager.profileNameForRecipient(withID: senderPublicKey, transaction: transaction) + let message = OWSDisappearingConfigurationUpdateInfoMessage(timestamp: NSDate.millisecondTimestamp(), thread: thread, + configuration: configuration, createdByRemoteName: senderDisplayName, createdInExistingGroup: false) + message.save(with: transaction) + SSKEnvironment.shared.disappearingMessagesJob.startIfNecessary() + } + + + + // MARK: - Closed Groups + + func handleNewGroup(_ message: ClosedGroupUpdate, using transaction: Any) { + guard case let .new(groupPublicKeyAsData, name, groupPrivateKey, senderKeys, membersAsData, adminsAsData) = message.kind else { return } + let transaction = transaction as! YapDatabaseReadWriteTransaction + let groupPublicKey = groupPublicKeyAsData.toHexString() + let members = membersAsData.map { $0.toHexString() } + let admins = adminsAsData.map { $0.toHexString() } + // Persist the ratchets + senderKeys.forEach { senderKey in + guard members.contains(senderKey.publicKey.toHexString()) else { return } + let ratchet = ClosedGroupRatchet(chainKey: senderKey.chainKey.toHexString(), keyIndex: UInt(senderKey.keyIndex), messageKeys: []) + Storage.shared.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderKey.publicKey.toHexString(), ratchet: ratchet, using: transaction) + } + // Sort out any discrepancies between the provided sender keys and what's required + let missingSenderKeys = Set(members).subtracting(senderKeys.map { $0.publicKey.toHexString() }) + let userPublicKey = getUserHexEncodedPublicKey() + if missingSenderKeys.contains(userPublicKey) { + let userRatchet = SharedSenderKeys.generateRatchet(for: groupPublicKey, senderPublicKey: userPublicKey, using: transaction) + let userSenderKey = ClosedGroupSenderKey(chainKey: Data(hex: userRatchet.chainKey), keyIndex: userRatchet.keyIndex, publicKey: Data(hex: userPublicKey)) + members.forEach { member in + guard member != userPublicKey else { return } + let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) + thread.save(with: transaction) + let closedGroupUpdateKind = ClosedGroupUpdate.Kind.senderKey(groupPublicKey: Data(hex: groupPublicKey), senderKey: userSenderKey) + let closedGroupUpdate = ClosedGroupUpdate() + closedGroupUpdate.kind = closedGroupUpdateKind + MessageSender.send(closedGroupUpdate, in: thread, using: transaction) + } + } + missingSenderKeys.subtracting([ userPublicKey ]).forEach { publicKey in + (UIApplication.shared.delegate as! AppDelegate).requestSenderKey(for: groupPublicKey, senderPublicKey: publicKey, using: transaction) + } + // Create the group + let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) + let group = TSGroupModel(title: name, memberIds: members, image: nil, groupId: groupID, groupType: .closedGroup, adminIds: admins) + let thread: TSGroupThread + if let t = TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupID), transaction: transaction) { + thread = t + thread.setGroupModel(group, with: transaction) + } else { + thread = TSGroupThread.getOrCreateThread(with: group, transaction: transaction) + thread.usesSharedSenderKeys = true + thread.save(with: transaction) + } + // Add the group to the user's set of public keys to poll for + Storage.setClosedGroupPrivateKey(groupPrivateKey.toHexString(), for: groupPublicKey, using: transaction) + // Notify the PN server + let _ = LokiPushNotificationManager.performOperation(.subscribe, for: groupPublicKey, publicKey: getUserHexEncodedPublicKey()) + // Notify the user + let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: .typeGroupUpdate) + infoMessage.save(with: transaction) + } + + func handleGroupUpdate(_ message: ClosedGroupUpdate, using transaction: Any) { + guard case let .info(groupPublicKeyAsData, name, senderKeys, membersAsData, adminsAsData) = message.kind else { return } + let transaction = transaction as! YapDatabaseReadWriteTransaction + let groupPublicKey = groupPublicKeyAsData.toHexString() + let members = membersAsData.map { $0.toHexString() } + let admins = adminsAsData.map { $0.toHexString() } + // Get the group + let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) + guard let thread = TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupID), transaction: transaction) else { + return print("[Loki] Ignoring closed group info message for nonexistent group.") + } + let group = thread.groupModel + // Check that the sender is a member of the group (before the update) + guard Set(group.groupMemberIds).contains(message.sender!) else { + return print("[Loki] Ignoring closed group info message from non-member.") + } + // Store the ratchets for any new members (it's important that this happens before the code below) + senderKeys.forEach { senderKey in + let ratchet = ClosedGroupRatchet(chainKey: senderKey.chainKey.toHexString(), keyIndex: UInt(senderKey.keyIndex), messageKeys: []) + Storage.shared.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderKey.publicKey.toHexString(), ratchet: ratchet, using: transaction) + } + // Delete all ratchets and either: + // • Send out the user's new ratchet using established channels if other members of the group left or were removed + // • Remove the group from the user's set of public keys to poll for if the current user was among the members that were removed + let oldMembers = group.groupMemberIds + let userPublicKey = getUserHexEncodedPublicKey() + let wasUserRemoved = !members.contains(userPublicKey) + if Set(members).intersection(oldMembers) != Set(oldMembers) { + let allOldRatchets = Storage.getAllClosedGroupRatchets(for: groupPublicKey) + for (senderPublicKey, oldRatchet) in allOldRatchets { + let collection = ClosedGroupRatchetCollectionType.old + Storage.shared.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: oldRatchet, in: collection, using: transaction) + } + Storage.removeAllClosedGroupRatchets(for: groupPublicKey, using: transaction) + if wasUserRemoved { + Storage.removeClosedGroupPrivateKey(for: groupPublicKey, using: transaction) + // Notify the PN server + let _ = LokiPushNotificationManager.performOperation(.unsubscribe, for: groupPublicKey, publicKey: userPublicKey) + } else { + let userRatchet = SharedSenderKeys.generateRatchet(for: groupPublicKey, senderPublicKey: userPublicKey, using: transaction) + let userSenderKey = ClosedGroupSenderKey(chainKey: Data(hex: userRatchet.chainKey), keyIndex: userRatchet.keyIndex, publicKey: Data(hex: userPublicKey)) + members.forEach { member in + guard member != userPublicKey else { return } + let thread = TSContactThread.getOrCreateThread(withContactId: member, transaction: transaction) + thread.save(with: transaction) + let closedGroupUpdateKind = ClosedGroupUpdate.Kind.senderKey(groupPublicKey: Data(hex: groupPublicKey), senderKey: userSenderKey) + let closedGroupUpdate = ClosedGroupUpdate() + closedGroupUpdate.kind = closedGroupUpdateKind + MessageSender.send(closedGroupUpdate, in: thread, using: transaction) + } + } + } + // Update the group + let newGroupModel = TSGroupModel(title: name, memberIds: members, image: nil, groupId: groupID, groupType: .closedGroup, adminIds: admins) + thread.setGroupModel(newGroupModel, with: transaction) + // Notify the user if needed + if Set(members) != Set(oldMembers) || Set(admins) != Set(group.groupAdminIds) || name != group.groupName { + let infoMessageType: TSInfoMessageType = wasUserRemoved ? .typeGroupQuit : .typeGroupUpdate + let updateInfo = group.getInfoStringAboutUpdate(to: newGroupModel) + let infoMessage = TSInfoMessage(timestamp: NSDate.ows_millisecondTimeStamp(), in: thread, messageType: infoMessageType, customMessage: updateInfo) + infoMessage.save(with: transaction) + } + } + + func handleSenderKeyRequest(_ message: ClosedGroupUpdate, using transaction: Any) { + guard case let .senderKeyRequest(groupPublicKeyAsData) = message.kind else { return } + let transaction = transaction as! YapDatabaseReadWriteTransaction + let userPublicKey = getUserHexEncodedPublicKey() + let groupPublicKey = groupPublicKeyAsData.toHexString() + let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(groupPublicKey) + guard let groupThread = TSGroupThread.fetch(uniqueId: TSGroupThread.threadId(fromGroupId: groupID), transaction: transaction) else { + return print("[Loki] Ignoring closed group sender key request for nonexistent group.") + } + let group = groupThread.groupModel + // Check that the requesting user is a member of the group + let members = Set(group.groupMemberIds) + guard members.contains(message.sender!) else { + return print("[Loki] Ignoring closed group sender key request from non-member.") + } + // Respond to the request + print("[Loki] Responding to sender key request from: \(message.sender!).") + SessionManagementProtocol.sendSessionRequestIfNeeded(to: message.sender!, using: transaction) + let userRatchet = Storage.shared.getClosedGroupRatchet(for: groupPublicKey, senderPublicKey: userPublicKey) + ?? SharedSenderKeys.generateRatchet(for: groupPublicKey, senderPublicKey: userPublicKey, using: transaction) + let userSenderKey = ClosedGroupSenderKey(chainKey: Data(hex: userRatchet.chainKey), keyIndex: userRatchet.keyIndex, publicKey: Data(hex: userPublicKey)) + let thread = TSContactThread.getOrCreateThread(withContactId: message.sender!, transaction: transaction) + thread.save(with: transaction) + let closedGroupUpdateKind = ClosedGroupUpdate.Kind.senderKey(groupPublicKey: Data(hex: groupPublicKey), senderKey: userSenderKey) + let closedGroupUpdate = ClosedGroupUpdate() + closedGroupUpdate.kind = closedGroupUpdateKind + MessageSender.send(closedGroupUpdate, in: thread, using: transaction) + } + + func handleSenderKey(_ message: ClosedGroupUpdate, using transaction: Any) { + guard case let .senderKey(groupPublicKeyAsData, senderKey) = message.kind else { return } + let groupPublicKey = groupPublicKeyAsData.toHexString() + guard senderKey.publicKey.toHexString() == message.sender! else { + return print("[Loki] Ignoring invalid closed group sender key.") + } + // Store the sender key + print("[Loki] Received a sender key from: \(message.sender!).") + let ratchet = ClosedGroupRatchet(chainKey: senderKey.chainKey.toHexString(), keyIndex: UInt(senderKey.keyIndex), messageKeys: []) + Storage.shared.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: message.sender!, ratchet: ratchet, using: transaction) + } +} diff --git a/SessionMessagingKit/Configuration.swift b/SessionMessagingKit/Configuration.swift index 525b9f9bb..0994ad56c 100644 --- a/SessionMessagingKit/Configuration.swift +++ b/SessionMessagingKit/Configuration.swift @@ -2,6 +2,7 @@ import SessionProtocolKit public struct Configuration { public let storage: SessionMessagingKitStorageProtocol + public let messageReceiverDelegate: MessageReceiverDelegate public let signalStorage: SessionStore & PreKeyStore & SignedPreKeyStore public let identityKeyStore: IdentityKeyStore public let sessionRestorationImplementation: SessionRestorationProtocol @@ -17,6 +18,7 @@ public enum SNMessagingKit { // Just to make the external API nice public static func configure( storage: SessionMessagingKitStorageProtocol, + messageReceiverDelegate: MessageReceiverDelegate, signalStorage: SessionStore & PreKeyStore & SignedPreKeyStore, identityKeyStore: IdentityKeyStore, sessionRestorationImplementation: SessionRestorationProtocol, @@ -27,6 +29,7 @@ public enum SNMessagingKit { // Just to make the external API nice ) { Configuration.shared = Configuration( storage: storage, + messageReceiverDelegate: messageReceiverDelegate, signalStorage: signalStorage, identityKeyStore: identityKeyStore, sessionRestorationImplementation: sessionRestorationImplementation, diff --git a/SessionMessagingKit/Jobs/MessageReceiveJob.swift b/SessionMessagingKit/Jobs/MessageReceiveJob.swift index bd3ed8ae9..13d8ee713 100644 --- a/SessionMessagingKit/Jobs/MessageReceiveJob.swift +++ b/SessionMessagingKit/Jobs/MessageReceiveJob.swift @@ -39,8 +39,8 @@ public final class MessageReceiveJob : NSObject, Job, NSCoding { // NSObject/NSC Configuration.shared.storage.withAsync({ transaction in // Intentionally capture self Threading.workQueue.async { do { - let message = try MessageReceiver.parse(self.data, using: transaction) - MessageReceiver.handle(message, messageServerID: self.messageServerID, using: transaction) + let message = try MessageReceiver.parse(self.data, messageServerID: self.messageServerID, using: transaction) + try MessageReceiver.handle(message, using: transaction) self.handleSuccess() } catch { SNLog("Couldn't parse message due to error: \(error).") diff --git a/SessionMessagingKit/Messages/Control Message/ClosedGroupUpdate.swift b/SessionMessagingKit/Messages/Control Message/ClosedGroupUpdate.swift index 1e012b060..b0d343520 100644 --- a/SessionMessagingKit/Messages/Control Message/ClosedGroupUpdate.swift +++ b/SessionMessagingKit/Messages/Control Message/ClosedGroupUpdate.swift @@ -23,8 +23,17 @@ public final class ClosedGroupUpdate : ControlMessage { // MARK: Validation public override var isValid: Bool { - guard super.isValid else { return false } - return kind != nil + guard super.isValid, let kind = kind else { return false } + switch kind { + case .new(let groupPublicKey, let name, let groupPrivateKey, _, let members, let admins): + return !groupPublicKey.isEmpty && !name.isEmpty && !groupPrivateKey.isEmpty && !members.isEmpty && !admins.isEmpty // senderKeys may be empty + case .info(let groupPublicKey, let name, _, let members, let admins): + return !groupPublicKey.isEmpty && !name.isEmpty && !members.isEmpty && !admins.isEmpty // senderKeys may be empty + case .senderKeyRequest(let groupPublicKey): + return !groupPublicKey.isEmpty + case .senderKey(let groupPublicKey, _): + return !groupPublicKey.isEmpty + } } // MARK: Coding @@ -86,7 +95,26 @@ public final class ClosedGroupUpdate : ControlMessage { // MARK: Proto Conversion public override class func fromProto(_ proto: SNProtoContent) -> ClosedGroupUpdate? { - return nil + guard let closedGroupUpdateProto = proto.dataMessage?.closedGroupUpdate else { return nil } + let groupPublicKey = closedGroupUpdateProto.groupPublicKey + let kind: Kind + switch closedGroupUpdateProto.type { + case .new: + guard let name = closedGroupUpdateProto.name, let groupPrivateKey = closedGroupUpdateProto.groupPrivateKey else { return nil } + let senderKeys = closedGroupUpdateProto.senderKeys.map { ClosedGroupSenderKey.fromProto($0) } + kind = .new(groupPublicKey: groupPublicKey, name: name, groupPrivateKey: groupPrivateKey, + senderKeys: senderKeys, members: closedGroupUpdateProto.members, admins: closedGroupUpdateProto.admins) + case .info: + guard let name = closedGroupUpdateProto.name else { return nil } + let senderKeys = closedGroupUpdateProto.senderKeys.map { ClosedGroupSenderKey.fromProto($0) } + kind = .info(groupPublicKey: groupPublicKey, name: name, senderKeys: senderKeys, members: closedGroupUpdateProto.members, admins: closedGroupUpdateProto.admins) + case .senderKeyRequest: + kind = .senderKeyRequest(groupPublicKey: groupPublicKey) + case .senderKey: + guard let senderKeyProto = closedGroupUpdateProto.senderKeys.first else { return nil } + kind = .senderKey(groupPublicKey: groupPublicKey, senderKey: ClosedGroupSenderKey.fromProto(senderKeyProto)) + } + return ClosedGroupUpdate(kind: kind) } public override func toProto() -> SNProtoContent? { @@ -130,6 +158,10 @@ public final class ClosedGroupUpdate : ControlMessage { private extension ClosedGroupSenderKey { + static func fromProto(_ proto: SNProtoDataMessageClosedGroupUpdateSenderKey) -> ClosedGroupSenderKey { + return ClosedGroupSenderKey(chainKey: proto.chainKey, keyIndex: UInt(proto.keyIndex), publicKey: proto.publicKey) + } + func toProto() throws -> SNProtoDataMessageClosedGroupUpdateSenderKey { return try SNProtoDataMessageClosedGroupUpdateSenderKey.builder(chainKey: chainKey, keyIndex: UInt32(keyIndex), publicKey: publicKey).build() } diff --git a/SessionMessagingKit/Messages/Message.swift b/SessionMessagingKit/Messages/Message.swift index 496238a86..59a8acd17 100644 --- a/SessionMessagingKit/Messages/Message.swift +++ b/SessionMessagingKit/Messages/Message.swift @@ -8,6 +8,8 @@ public class Message : NSObject, NSCoding { // NSObject/NSCoding conformance is public var receivedTimestamp: UInt64? public var recipient: String? public var sender: String? + public var groupPublicKey: String? + public var openGroupServerMessageID: UInt64? public class var ttl: UInt64 { 2 * 24 * 60 * 60 * 1000 } diff --git a/SessionMessagingKit/Sending & Receiving/MessageHandler.swift b/SessionMessagingKit/Sending & Receiving/MessageHandler.swift new file mode 100644 index 000000000..cba87224d --- /dev/null +++ b/SessionMessagingKit/Sending & Receiving/MessageHandler.swift @@ -0,0 +1,17 @@ + +public protocol MessageReceiverDelegate { + + func isBlocked(_ publicKey: String) -> Bool + func updateProfile(for publicKey: String, from profile: VisibleMessage.Profile, using transaction: Any) + func showTypingIndicatorIfNeeded(for senderPublicKey: String) + func hideTypingIndicatorIfNeeded(for senderPublicKey: String) + func cancelTypingIndicatorsIfNeeded(for senderPublicKey: String) + func notifyUserIfNeeded(for message: Any, threadID: String) + func markMessagesAsRead(_ timestamps: [UInt64], from senderPublicKey: String, at timestamp: UInt64) + func setExpirationTimer(to duration: UInt32, for senderPublicKey: String, groupPublicKey: String?, using transaction: Any) + func disableExpirationTimer(for senderPublicKey: String, groupPublicKey: String?, using transaction: Any) + func handleNewGroup(_ message: ClosedGroupUpdate, using transaction: Any) + func handleGroupUpdate(_ message: ClosedGroupUpdate, using transaction: Any) + func handleSenderKeyRequest(_ message: ClosedGroupUpdate, using transaction: Any) + func handleSenderKey(_ message: ClosedGroupUpdate, using transaction: Any) +} diff --git a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift index 9bb04e4ef..ec4b75ae5 100644 --- a/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift +++ b/SessionMessagingKit/Sending & Receiving/MessageReceiver.swift @@ -3,6 +3,7 @@ import SessionUtilitiesKit // TODO: // • Threads don't show up on the first message; only on the second. // • Profile pictures aren't showing up. +// • Check that message expiration works. internal enum MessageReceiver { @@ -13,6 +14,7 @@ internal enum MessageReceiver { case noUserPublicKey case noData case senderBlocked + case noThread // Shared sender keys case invalidGroupPublicKey case noGroupPrivateKey @@ -34,6 +36,7 @@ internal enum MessageReceiver { case .noUserPublicKey: return "Couldn't find user key pair." case .noData: return "Received an empty envelope." case .senderBlocked: return "Received a message from a blocked user." + case .noThread: return "Couldn't find thread for message." // Shared sender keys case .invalidGroupPublicKey: return "Invalid group public key." case .noGroupPrivateKey: return "Missing group private key." @@ -43,19 +46,22 @@ internal enum MessageReceiver { } } - internal static func parse(_ data: Data, using transaction: Any) throws -> Message { + internal static func parse(_ data: Data, messageServerID: UInt64?, using transaction: Any) throws -> Message { // Parse the envelope let envelope = try SNProtoEnvelope.parseData(data) // Decrypt the contents let plaintext: Data let sender: String + var groupPublicKey: String? = nil switch envelope.type { case .unidentifiedSender: (plaintext, sender) = try decryptWithSignalProtocol(envelope: envelope, using: transaction) - case .closedGroupCiphertext: (plaintext, sender) = try decryptWithSharedSenderKeys(envelope: envelope, using: transaction) + case .closedGroupCiphertext: + (plaintext, sender) = try decryptWithSharedSenderKeys(envelope: envelope, using: transaction) + groupPublicKey = envelope.source default: throw Error.unknownEnvelopeType } // Don't process the envelope any further if the sender is blocked - guard !Configuration.shared.storage.isBlocked(sender) else { throw Error.senderBlocked } + guard !Configuration.shared.messageReceiverDelegate.isBlocked(sender) else { throw Error.senderBlocked } // Parse the proto let proto: SNProtoContent do { @@ -79,6 +85,8 @@ internal enum MessageReceiver { message.sender = sender message.recipient = Configuration.shared.storage.getUserPublicKey() message.receivedTimestamp = NSDate.millisecondTimestamp() + message.groupPublicKey = groupPublicKey + message.openGroupServerMessageID = messageServerID guard message.isValid else { throw Error.invalidMessage } return message } else { @@ -86,7 +94,7 @@ internal enum MessageReceiver { } } - internal static func handle(_ message: Message, messageServerID: UInt64?, using transaction: Any) { + internal static func handle(_ message: Message, using transaction: Any) throws { switch message { case let message as ReadReceipt: handleReadReceipt(message, using: transaction) case let message as SessionRequest: handleSessionRequest(message, using: transaction) @@ -94,56 +102,62 @@ internal enum MessageReceiver { case let message as TypingIndicator: handleTypingIndicator(message, using: transaction) case let message as ClosedGroupUpdate: handleClosedGroupUpdate(message, using: transaction) case let message as ExpirationTimerUpdate: handleExpirationTimerUpdate(message, using: transaction) - case let message as VisibleMessage: handleVisibleMessage(message, using: transaction) + case let message as VisibleMessage: try handleVisibleMessage(message, using: transaction) default: fatalError() } } private static func handleReadReceipt(_ message: ReadReceipt, using transaction: Any) { - Configuration.shared.storage.markMessagesAsRead(message.timestamps!, from: message.sender!, at: message.receivedTimestamp!) + Configuration.shared.messageReceiverDelegate.markMessagesAsRead(message.timestamps!, from: message.sender!, at: message.receivedTimestamp!) } private static func handleSessionRequest(_ message: SessionRequest, using transaction: Any) { - // TODO: Implement + // We might not need this anymore } private static func handleNullMessage(_ message: NullMessage, using transaction: Any) { - // TODO: Implement + // We might not need this anymore } private static func handleTypingIndicator(_ message: TypingIndicator, using transaction: Any) { - let storage = Configuration.shared.storage + let delegate = Configuration.shared.messageReceiverDelegate switch message.kind! { - case .started: storage.showTypingIndicatorIfNeeded(for: message.sender!) - case .stopped: storage.hideTypingIndicatorIfNeeded(for: message.sender!) + case .started: delegate.showTypingIndicatorIfNeeded(for: message.sender!) + case .stopped: delegate.hideTypingIndicatorIfNeeded(for: message.sender!) } } private static func handleClosedGroupUpdate(_ message: ClosedGroupUpdate, using transaction: Any) { - + let delegate = Configuration.shared.messageReceiverDelegate + switch message.kind! { + case .new: delegate.handleNewGroup(message, using: transaction) + case .info: delegate.handleGroupUpdate(message, using: transaction) + case .senderKeyRequest: delegate.handleSenderKeyRequest(message, using: transaction) + case .senderKey: delegate.handleSenderKey(message, using: transaction) + } } private static func handleExpirationTimerUpdate(_ message: ExpirationTimerUpdate, using transaction: Any) { - let storage = Configuration.shared.storage + let delegate = Configuration.shared.messageReceiverDelegate if message.duration! > 0 { - storage.setExpirationTimer(to: message.duration!, for: message.sender!, using: transaction) + delegate.setExpirationTimer(to: message.duration!, for: message.sender!, groupPublicKey: message.groupPublicKey, using: transaction) } else { - storage.disableExpirationTimer(for: message.sender!, using: transaction) + delegate.disableExpirationTimer(for: message.sender!, groupPublicKey: message.groupPublicKey, using: transaction) } } - private static func handleVisibleMessage(_ message: VisibleMessage, using transaction: Any) { - let storage = Configuration.shared.storage + private static func handleVisibleMessage(_ message: VisibleMessage, using transaction: Any) throws { + let delegate = Configuration.shared.messageReceiverDelegate // Update profile if needed if let profile = message.profile { - storage.updateProfile(for: message.sender!, from: profile, using: transaction) + delegate.updateProfile(for: message.sender!, from: profile, using: transaction) } // Persist the message - let (threadID, tsIncomingMessage) = storage.persist(message, using: transaction) + guard let (threadID, tsIncomingMessage) = Configuration.shared.storage.persist(message, groupPublicKey: message.groupPublicKey, using: transaction) else { throw Error.noThread } message.threadID = threadID // Cancel any typing indicators - storage.cancelTypingIndicatorsIfNeeded(for: message.sender!) + delegate.cancelTypingIndicatorsIfNeeded(for: message.sender!) // Notify the user if needed - storage.notifyUserIfNeeded(for: tsIncomingMessage, threadID: threadID) + delegate.notifyUserIfNeeded(for: tsIncomingMessage, threadID: threadID) } } diff --git a/SessionMessagingKit/Storage.swift b/SessionMessagingKit/Storage.swift index 12ac5fd3e..42ce0351f 100644 --- a/SessionMessagingKit/Storage.swift +++ b/SessionMessagingKit/Storage.swift @@ -62,15 +62,6 @@ public protocol SessionMessagingKitStorageProtocol { // MARK: - Message Handling - func isBlocked(_ publicKey: String) -> Bool - func updateProfile(for publicKey: String, from profile: VisibleMessage.Profile, using transaction: Any) /// Returns the ID of the thread the message was stored under along with the `TSIncomingMessage` that was constructed. - func persist(_ message: VisibleMessage, using transaction: Any) -> (String, Any) - func showTypingIndicatorIfNeeded(for senderPublicKey: String) - func hideTypingIndicatorIfNeeded(for senderPublicKey: String) - func cancelTypingIndicatorsIfNeeded(for senderPublicKey: String) - func notifyUserIfNeeded(for message: Any, threadID: String) - func markMessagesAsRead(_ timestamps: [UInt64], from senderPublicKey: String, at timestamp: UInt64) - func setExpirationTimer(to duration: UInt32, for senderPublicKey: String, using transaction: Any) - func disableExpirationTimer(for senderPublicKey: String, using transaction: Any) + func persist(_ message: VisibleMessage, groupPublicKey: String?, using transaction: Any) -> (String, Any)? } diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index b0fec9ae5..f23f06892 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -922,6 +922,8 @@ C3CA3B1D255CF3C800F4C6D4 /* MessageSender+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3CA3B1C255CF3C800F4C6D4 /* MessageSender+Utilities.swift */; }; C3CA3B2F255CF84E00F4C6D4 /* NullMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3CA3B2E255CF84E00F4C6D4 /* NullMessage.swift */; }; C3D0972B2510499C00F6E3E4 /* BackgroundPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3D0972A2510499C00F6E3E4 /* BackgroundPoller.swift */; }; + C3D697382564DCE6004AF766 /* MessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3D697372564DCE6004AF766 /* MessageHandler.swift */; }; + C3D6974A2564DEDC004AF766 /* MessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3D697492564DEDC004AF766 /* MessageHandler.swift */; }; C3DAB3242480CB2B00725F25 /* SRCopyableLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DAB3232480CB2A00725F25 /* SRCopyableLabel.swift */; }; C3DFFAC623E96F0D0058DAF8 /* Sheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */; }; C3E5C2FA251DBABB0040DFFC /* EditClosedGroupVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.swift */; }; @@ -2042,6 +2044,8 @@ C3CA3B1C255CF3C800F4C6D4 /* MessageSender+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MessageSender+Utilities.swift"; sourceTree = ""; }; C3CA3B2E255CF84E00F4C6D4 /* NullMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NullMessage.swift; sourceTree = ""; }; C3D0972A2510499C00F6E3E4 /* BackgroundPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundPoller.swift; sourceTree = ""; }; + C3D697372564DCE6004AF766 /* MessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageHandler.swift; sourceTree = ""; }; + C3D697492564DEDC004AF766 /* MessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageHandler.swift; sourceTree = ""; }; C3DAB3232480CB2A00725F25 /* SRCopyableLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SRCopyableLabel.swift; sourceTree = ""; }; C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sheet.swift; sourceTree = ""; }; C3E5C2F9251DBABB0040DFFC /* EditClosedGroupVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditClosedGroupVC.swift; sourceTree = ""; }; @@ -2705,6 +2709,7 @@ C3471ECA2555356A00297E91 /* MessageSender+Encryption.swift */, C300A5FB2554B0A000555489 /* MessageReceiver.swift */, C3471F4B25553AB000297E91 /* MessageReceiver+Decryption.swift */, + C3D697372564DCE6004AF766 /* MessageHandler.swift */, ); path = "Sending & Receiving"; sourceTree = ""; @@ -3811,6 +3816,7 @@ C3550A02255DD6D900194B6A /* AppDelegate+OpenGroupAPI.swift */, C3F0A62B255C9937007BE2A3 /* AppDelegate+SharedSenderKeys.swift */, C3F0A5EB255C970D007BE2A3 /* Configuration.swift */, + C3D697492564DEDC004AF766 /* MessageHandler.swift */, B8CCF63B239757C10091D419 /* Components */, C31F812425258F9C00DD9FD9 /* Database */, C32B405424A961E1001117B5 /* Dependencies */, @@ -5208,6 +5214,7 @@ C352A349255781F400338F3E /* AttachmentDownloadJob.swift in Sources */, C352A30925574D8500338F3E /* Message+Destination.swift in Sources */, C300A5E72554B07300555489 /* ExpirationTimerUpdate.swift in Sources */, + C3D697382564DCE6004AF766 /* MessageHandler.swift in Sources */, C352A2FF25574B6300338F3E /* MessageSendJob.swift in Sources */, C3C2A75F2553A3C500C340D1 /* VisibleMessage+LinkPreview.swift in Sources */, C3C2A74425539EB700C340D1 /* Message.swift in Sources */, @@ -5380,6 +5387,7 @@ 458E38371D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m in Sources */, 34B6A905218B4C91007C4606 /* TypingIndicatorInteraction.swift in Sources */, 2400888E239F30A600305217 /* SessionRestorationView.swift in Sources */, + C3D6974A2564DEDC004AF766 /* MessageHandler.swift in Sources */, B886B4A72398B23E00211ABE /* QRCodeVC.swift in Sources */, 34EA69402194933900702471 /* MediaDownloadView.swift in Sources */, B8544E3323D50E4900299F14 /* AppearanceUtilities.swift in Sources */, diff --git a/SignalUtilitiesKit/ClosedGroupsProtocol.swift b/SignalUtilitiesKit/ClosedGroupsProtocol.swift index 43d76609d..2bf1e0566 100644 --- a/SignalUtilitiesKit/ClosedGroupsProtocol.swift +++ b/SignalUtilitiesKit/ClosedGroupsProtocol.swift @@ -128,7 +128,7 @@ public final class ClosedGroupsProtocol : NSObject { Storage.writeSync { transaction in let allOldRatchets = Storage.getAllClosedGroupRatchets(for: groupPublicKey) for (senderPublicKey, oldRatchet) in allOldRatchets { - let collection = Storage.ClosedGroupRatchetCollectionType.old + let collection = ClosedGroupRatchetCollectionType.old Storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: oldRatchet, in: collection, using: transaction) } // Delete all ratchets (it's important that this happens * after * sending out the update) @@ -362,7 +362,7 @@ public final class ClosedGroupsProtocol : NSObject { if Set(members).intersection(oldMembers) != Set(oldMembers) { let allOldRatchets = Storage.getAllClosedGroupRatchets(for: groupPublicKey) for (senderPublicKey, oldRatchet) in allOldRatchets { - let collection = Storage.ClosedGroupRatchetCollectionType.old + let collection = ClosedGroupRatchetCollectionType.old Storage.setClosedGroupRatchet(for: groupPublicKey, senderPublicKey: senderPublicKey, ratchet: oldRatchet, in: collection, using: transaction) } Storage.removeAllClosedGroupRatchets(for: groupPublicKey, using: transaction) diff --git a/SignalUtilitiesKit/LokiPushNotificationManager.swift b/SignalUtilitiesKit/LokiPushNotificationManager.swift index 50b172d6f..bccf00b4c 100644 --- a/SignalUtilitiesKit/LokiPushNotificationManager.swift +++ b/SignalUtilitiesKit/LokiPushNotificationManager.swift @@ -102,7 +102,7 @@ public final class LokiPushNotificationManager : NSObject { } @discardableResult - static func performOperation(_ operation: ClosedGroupOperation, for closedGroupPublicKey: String, publicKey: String) -> Promise { + public static func performOperation(_ operation: ClosedGroupOperation, for closedGroupPublicKey: String, publicKey: String) -> Promise { let isUsingFullAPNs = UserDefaults.standard[.isUsingFullAPNs] guard isUsingFullAPNs else { return Promise { $0.fulfill(()) } } let parameters = [ "closedGroupPublicKey" : closedGroupPublicKey, "pubKey" : publicKey] diff --git a/SignalUtilitiesKit/Move to main app/Storage+ClosedGroups.swift b/SignalUtilitiesKit/Move to main app/Storage+ClosedGroups.swift index f4e031dd3..32095d6ca 100644 --- a/SignalUtilitiesKit/Move to main app/Storage+ClosedGroups.swift +++ b/SignalUtilitiesKit/Move to main app/Storage+ClosedGroups.swift @@ -1,10 +1,6 @@ public extension Storage { - internal enum ClosedGroupRatchetCollectionType { - case old, current - } - // MARK: Ratchets internal static func getClosedGroupRatchetCollection(_ collection: ClosedGroupRatchetCollectionType, for groupPublicKey: String) -> String { switch collection { @@ -27,7 +23,7 @@ public extension Storage { transaction.setObject(ratchet, forKey: senderPublicKey, inCollection: collection) } - internal static func getAllClosedGroupRatchets(for groupPublicKey: String, from collection: ClosedGroupRatchetCollectionType = .current) -> [(senderPublicKey: String, ratchet: ClosedGroupRatchet)] { + public static func getAllClosedGroupRatchets(for groupPublicKey: String, from collection: ClosedGroupRatchetCollectionType = .current) -> [(senderPublicKey: String, ratchet: ClosedGroupRatchet)] { let collection = getClosedGroupRatchetCollection(collection, for: groupPublicKey) var result: [(senderPublicKey: String, ratchet: ClosedGroupRatchet)] = [] read { transaction in @@ -45,7 +41,7 @@ public extension Storage { }) } - internal static func removeAllClosedGroupRatchets(for groupPublicKey: String, from collection: ClosedGroupRatchetCollectionType = .current, using transaction: YapDatabaseReadWriteTransaction) { + public static func removeAllClosedGroupRatchets(for groupPublicKey: String, from collection: ClosedGroupRatchetCollectionType = .current, using transaction: YapDatabaseReadWriteTransaction) { let collection = getClosedGroupRatchetCollection(collection, for: groupPublicKey) transaction.removeAllObjects(inCollection: collection) } diff --git a/SignalUtilitiesKit/Utilities/TSIncomingMessage+Conversion.swift b/SignalUtilitiesKit/Utilities/TSIncomingMessage+Conversion.swift index 59ca58cc7..bd21269d6 100644 --- a/SignalUtilitiesKit/Utilities/TSIncomingMessage+Conversion.swift +++ b/SignalUtilitiesKit/Utilities/TSIncomingMessage+Conversion.swift @@ -3,7 +3,7 @@ public extension TSIncomingMessage { static func from(_ visibleMessage: VisibleMessage, associatedWith thread: TSThread, using transaction: YapDatabaseReadWriteTransaction) -> TSIncomingMessage { let sender = visibleMessage.sender! - return TSIncomingMessage( + let result = TSIncomingMessage( timestamp: visibleMessage.receivedTimestamp!, in: thread, authorId: sender, @@ -16,5 +16,7 @@ public extension TSIncomingMessage { serverTimestamp: nil, wasReceivedByUD: true ) + result.openGroupServerMessageID = visibleMessage.openGroupServerMessageID ?? 0 + return result } }