diff --git a/Podfile.lock b/Podfile.lock index 4fe2378f9..bada84397 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -320,7 +320,7 @@ SPEC CHECKSUMS: SessionServiceKit: c792d64fc86aaa0f7325beaa36e1a9211a887359 SQLCipher: e434ed542b24f38ea7b36468a13f9765e1b5c072 SSZipArchive: 62d4947b08730e4cda640473b0066d209ff033c9 - Starscream: 8aaf1a7feb805c816d0e7d3190ef23856f6665b9 + Starscream: ef3ece99d765eeccb67de105bfa143f929026cf5 SwiftProtobuf: 241400280f912735c1e1b9fe675fdd2c6c4d42e2 YapDatabase: b418a4baa6906e8028748938f9159807fd039af4 YYImage: 1e1b62a9997399593e4b9c4ecfbbabbf1d3f3b54 diff --git a/Pods b/Pods index 827a67a60..6077f71ac 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit 827a67a609b8a4166220440c911796f1a18614ee +Subproject commit 6077f71ac9595fa165558a398d296a6c97b5b143 diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 101206a17..799874948 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -1394,12 +1394,15 @@ static NSTimeInterval launchStartedAt; NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; if (userHexEncodedPublicKey == nil) { return; } self.lokiPoller = [[LKPoller alloc] initOnMessagesReceived:^(NSArray *messages) { + if (messages.count != 0) { + [LKLogger print:@"[Loki] Received new messages."]; + } for (SSKProtoEnvelope *message in messages) { NSData *data = [message serializedDataAndReturnError:nil]; if (data != nil) { [SSKEnvironment.shared.messageReceiver handleReceivedEnvelopeData:data]; } else { - NSLog(@"[Loki] Failed to deserialize envelope."); + [LKLogger print:@"[Loki] Failed to deserialize envelope."]; } } }]; diff --git a/Signal/src/Loki/Components/ConversationCell.swift b/Signal/src/Loki/Components/ConversationCell.swift index d6fdde318..ff00980d2 100644 --- a/Signal/src/Loki/Components/ConversationCell.swift +++ b/Signal/src/Loki/Components/ConversationCell.swift @@ -134,26 +134,29 @@ final class ConversationCell : UITableViewCell { // MARK: Updating private func update() { + AssertIsOnMainThread() MentionsManager.populateUserPublicKeyCacheIfNeeded(for: threadViewModel.threadRecord.uniqueId!) // FIXME: This is a terrible place to do this unreadMessagesIndicatorView.alpha = threadViewModel.hasUnreadMessages ? 1 : 0.0001 // Setting the alpha to exactly 0 causes an issue on iOS 12 + profilePictureView.openGroupProfilePicture = nil if threadViewModel.isGroupThread { - if threadViewModel.name == "Session Public Chat" { + if threadViewModel.name == "Loki Public Chat" + || threadViewModel.name == "Session Public Chat" { // Override the profile picture for the Loki Public Chat and the Session Public Chat profilePictureView.hexEncodedPublicKey = "" profilePictureView.isRSSFeed = true - } else { + } else if let openGroupProfilePicture = (threadViewModel.threadRecord as! TSGroupThread).groupModel.groupImage { // An open group with a profile picture + profilePictureView.openGroupProfilePicture = openGroupProfilePicture + } else if (threadViewModel.threadRecord as! TSGroupThread).groupModel.groupType == .openGroup + || (threadViewModel.threadRecord as! TSGroupThread).groupModel.groupType == .rssFeed { // An open group without a profile picture or an RSS feed + profilePictureView.hexEncodedPublicKey = "" + profilePictureView.isRSSFeed = true + } else { // A closed group var users = MentionsManager.userPublicKeyCache[threadViewModel.threadRecord.uniqueId!] ?? [] users.remove(getUserHexEncodedPublicKey()) let randomUsers = users.sorted().prefix(2) // Sort to provide a level of stability - if !randomUsers.isEmpty { - profilePictureView.hexEncodedPublicKey = randomUsers[0] - profilePictureView.additionalHexEncodedPublicKey = randomUsers.count >= 2 ? randomUsers[1] : "" - } else { - profilePictureView.hexEncodedPublicKey = "" - profilePictureView.additionalHexEncodedPublicKey = "" - } - profilePictureView.isRSSFeed = (threadViewModel.threadRecord as? TSGroupThread)?.isRSSFeed ?? false + profilePictureView.hexEncodedPublicKey = randomUsers.count >= 1 ? randomUsers[0] : "" + profilePictureView.additionalHexEncodedPublicKey = randomUsers.count >= 2 ? randomUsers[1] : "" } - } else { + } else { // A one-on-one chat profilePictureView.hexEncodedPublicKey = threadViewModel.contactIdentifier! profilePictureView.additionalHexEncodedPublicKey = nil profilePictureView.isRSSFeed = false diff --git a/Signal/src/Loki/View Controllers/JoinPublicChatVC.swift b/Signal/src/Loki/View Controllers/JoinPublicChatVC.swift index ae09f4cde..616e317cb 100644 --- a/Signal/src/Loki/View Controllers/JoinPublicChatVC.swift +++ b/Signal/src/Loki/View Controllers/JoinPublicChatVC.swift @@ -133,8 +133,11 @@ final class JoinPublicChatVC : BaseVC, UIPageViewControllerDataSource, UIPageVie isJoining = true let channelID: UInt64 = 1 let urlAsString = url.absoluteString - let displayName = OWSProfileManager.shared().localProfileName() - // TODO: Profile picture & profile key + let userPublicKey = UserDefaults.standard[.masterHexEncodedPublicKey] ?? getUserHexEncodedPublicKey() + let profileManager = OWSProfileManager.shared() + let displayName = profileManager.profileNameForRecipient(withID: userPublicKey) + let profilePictureURL = profileManager.profilePictureURL() + let profileKey = profileManager.localProfileKey().keyData try! Storage.writeSync { transaction in transaction.removeObject(forKey: "\(urlAsString).\(channelID)", inCollection: LokiPublicChatAPI.lastMessageServerIDCollection) transaction.removeObject(forKey: "\(urlAsString).\(channelID)", inCollection: LokiPublicChatAPI.lastDeletionServerIDCollection) @@ -143,6 +146,7 @@ final class JoinPublicChatVC : BaseVC, UIPageViewControllerDataSource, UIPageVie .done(on: .main) { [weak self] _ in let _ = LokiPublicChatAPI.getMessages(for: channelID, on: urlAsString) let _ = LokiPublicChatAPI.setDisplayName(to: displayName, on: urlAsString) + let _ = LokiPublicChatAPI.setProfilePictureURL(to: profilePictureURL, using: profileKey, on: urlAsString) let _ = LokiPublicChatAPI.join(channelID, on: urlAsString) let syncManager = SSKEnvironment.shared.syncManager let _ = syncManager.syncAllOpenGroups() diff --git a/Signal/src/Models/MessageActions.swift b/Signal/src/Models/MessageActions.swift index 5d99a6c91..7b9dc9d16 100644 --- a/Signal/src/Models/MessageActions.swift +++ b/Signal/src/Models/MessageActions.swift @@ -115,7 +115,8 @@ class ConversationViewItemActions: NSObject { actions.append(deleteAction) } - if isGroup && conversationViewItem.interaction.thread.name() == "Session Public Chat" { + if isGroup && conversationViewItem.interaction.thread.name() == "Loki Public Chat" + || conversationViewItem.interaction.thread.name() == "Session Public Chat" { let reportAction = MessageActionBuilder.report(conversationViewItem, delegate: delegate) actions.append(reportAction) } @@ -159,7 +160,8 @@ class ConversationViewItemActions: NSObject { actions.append(deleteAction) } - if isGroup && conversationViewItem.interaction.thread.name() == "Session Public Chat" { + if isGroup && conversationViewItem.interaction.thread.name() == "Loki Public Chat" + || conversationViewItem.interaction.thread.name() == "Session Public Chat" { let reportAction = MessageActionBuilder.report(conversationViewItem, delegate: delegate) actions.append(reportAction) } @@ -192,7 +194,8 @@ class ConversationViewItemActions: NSObject { actions.append(deleteAction) } - if isGroup && conversationViewItem.interaction.thread.name() == "Session Public Chat" { + if isGroup && conversationViewItem.interaction.thread.name() == "Loki Public Chat" + || conversationViewItem.interaction.thread.name() == "Session Public Chat" { let reportAction = MessageActionBuilder.report(conversationViewItem, delegate: delegate) actions.append(reportAction) } diff --git a/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m b/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m index 90801632c..56ab7e54f 100644 --- a/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m +++ b/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m @@ -957,8 +957,15 @@ const CGFloat kIconViewLength = 24; [stackView setLayoutMarginsRelativeArrangement:YES]; if (self.isGroupThread) { - profilePictureView.hexEncodedPublicKey = @""; - profilePictureView.isRSSFeed = true; // For now just always show the Session logo + TSGroupThread* groupThread = (TSGroupThread *)self.thread; + if (groupThread.isPublicChat && groupThread.groupModel.groupImage != nil + && ![groupThread.groupModel.groupName isEqual:@"Loki Public Chat"] && ![groupThread.groupModel.groupName isEqual:@"Session Public Chat"]) { + profilePictureView.openGroupProfilePicture = groupThread.groupModel.groupImage; + profilePictureView.isRSSFeed = false; + } else { + profilePictureView.hexEncodedPublicKey = @""; + profilePictureView.isRSSFeed = true; // For now just always show the Session logo + } } else { profilePictureView.hexEncodedPublicKey = self.thread.contactIdentifier; diff --git a/SignalMessaging/Loki/Redesign/Components/ProfilePictureView.swift b/SignalMessaging/Loki/Redesign/Components/ProfilePictureView.swift index 809fa8f24..3f5e90bd9 100644 --- a/SignalMessaging/Loki/Redesign/Components/ProfilePictureView.swift +++ b/SignalMessaging/Loki/Redesign/Components/ProfilePictureView.swift @@ -7,6 +7,7 @@ public final class ProfilePictureView : UIView { @objc public var isRSSFeed = false @objc public var hexEncodedPublicKey: String! @objc public var additionalHexEncodedPublicKey: String? + @objc public var openGroupProfilePicture: UIImage? // MARK: Components private lazy var imageView = getImageView() @@ -43,6 +44,7 @@ public final class ProfilePictureView : UIView { // MARK: Updating @objc public func update() { + AssertIsOnMainThread() func getProfilePicture(of size: CGFloat, for hexEncodedPublicKey: String) -> UIImage? { guard !hexEncodedPublicKey.isEmpty else { return nil } return OWSProfileManager.shared().profileAvatar(forRecipientId: hexEncodedPublicKey) ?? Identicon.generateIcon(string: hexEncodedPublicKey, size: size) @@ -61,8 +63,8 @@ public final class ProfilePictureView : UIView { additionalImageView.isHidden = true additionalImageView.image = nil } - guard hexEncodedPublicKey != nil else { return } // Can happen in rare cases - imageView.image = isRSSFeed ? nil : getProfilePicture(of: size, for: hexEncodedPublicKey) + guard hexEncodedPublicKey != nil || openGroupProfilePicture != nil else { return } + imageView.image = isRSSFeed ? nil : (openGroupProfilePicture ?? getProfilePicture(of: size, for: hexEncodedPublicKey)) imageView.backgroundColor = isRSSFeed ? UIColor(rgbHex: 0x353535) : UIColor(rgbHex: 0xD8D8D8) // UIColor(rgbHex: 0xD8D8D8) = Colors.unimportant imageView.layer.cornerRadius = size / 2 imageView.contentMode = isRSSFeed ? .center : .scaleAspectFit diff --git a/SignalMessaging/profiles/OWSProfileManager.m b/SignalMessaging/profiles/OWSProfileManager.m index d473b0ef2..d9308c977 100644 --- a/SignalMessaging/profiles/OWSProfileManager.m +++ b/SignalMessaging/profiles/OWSProfileManager.m @@ -239,7 +239,8 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); // Ensure that the success and failure blocks are called on the main thread. void (^failureBlock)(NSError *) = ^(NSError *error) { OWSLogError(@"Updating service with profile failed."); - + + /* // We use a "self-only" contact sync to indicate to desktop // that we've changed our profile and that it should do a // profile fetch for "self". @@ -249,6 +250,10 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); if (requiresSync) { [[self.syncManager syncLocalContact] retainUntilComplete]; } + */ + if (requiresSync) { + [LKSyncMessagesProtocol syncProfile]; + } dispatch_async(dispatch_get_main_queue(), ^{ failureBlockParameter(error); @@ -256,13 +261,18 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); }; void (^successBlock)(void) = ^{ OWSLogInfo(@"Successfully updated service with profile."); - + + /* // We use a "self-only" contact sync to indicate to desktop // that we've changed our profile and that it should do a // profile fetch for "self". if (requiresSync) { [[self.syncManager syncLocalContact] retainUntilComplete]; } + */ + if (requiresSync) { + [LKSyncMessagesProtocol syncProfile]; + } dispatch_async(dispatch_get_main_queue(), ^{ successBlockParameter(); diff --git a/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatAPI.swift b/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatAPI.swift index 3cad1c4bf..4bbd1dfc0 100644 --- a/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatAPI.swift +++ b/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatAPI.swift @@ -19,7 +19,8 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { // MARK: Convenience private static var userDisplayName: String { - return SSKEnvironment.shared.contactsManager.displayName(forPhoneIdentifier: getUserHexEncodedPublicKey()) ?? "Anonymous" + let userPublicKey = UserDefaults.standard[.masterHexEncodedPublicKey] ?? getUserHexEncodedPublicKey() + return SSKEnvironment.shared.profileManager.profileNameForRecipient(withID: userPublicKey) ?? "Anonymous" } // MARK: Database @@ -329,6 +330,51 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { } } + static func updateProfileIfNeeded(for channel: UInt64, on server: String, from info: LokiPublicChatInfo) { + let storage = OWSPrimaryStorage.shared() + let publicChatID = "\(server).\(channel)" + try! Storage.writeSync { 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) + // Update display name if needed + let groupModel = groupThread.groupModel + if groupModel.groupName != info.displayName { + let newGroupModel = TSGroupModel(title: info.displayName, memberIds: groupModel.groupMemberIds, image: groupModel.groupImage, groupId: groupModel.groupId, groupType: groupModel.groupType, adminIds: groupModel.groupAdminIds) + groupThread.groupModel = newGroupModel + groupThread.save(with: transaction) + } + // Download and update profile picture if needed + let oldProfilePictureURL = storage.getProfilePictureURL(forPublicChatWithID: publicChatID, in: transaction) + if oldProfilePictureURL != info.profilePictureURL || groupModel.groupImage == nil { + storage.setProfilePictureURL(info.profilePictureURL, forPublicChatWithID: publicChatID, in: transaction) + if let avatarURL = info.profilePictureURL { + let configuration = URLSessionConfiguration.default + let manager = AFURLSessionManager.init(sessionConfiguration: configuration) + let url = URL(string: "\(server)\(avatarURL)")! + let request = URLRequest(url: url) + let task = manager.downloadTask(with: request, progress: nil, + destination: { (targetPath: URL, response: URLResponse) -> URL in + let tempFilePath = URL(fileURLWithPath: OWSTemporaryDirectoryAccessibleAfterFirstAuth()).appendingPathComponent(UUID().uuidString) + return tempFilePath + }, + completionHandler: { (response: URLResponse, filePath: URL?, error: Error?) in + if let error = error { + print("[Loki] Couldn't download profile picture for public chat channel with ID: \(channel) on server: \(server).") + return + } + if let filePath = filePath, let avatarData = try? Data.init(contentsOf: filePath) { + let attachmentStream = TSAttachmentStream(contentType: OWSMimeTypeImageJpeg, byteCount: UInt32(avatarData.count), sourceFilename: nil, caption: nil, albumMessageId: nil) + try! attachmentStream.write(avatarData) + groupThread.updateAvatar(with: attachmentStream) + } + }) + task.resume() + } + } + } + } + // MARK: Joining & Leaving @objc(getInfoForChannelWithID:onServer:) public static func objc_getInfo(for channel: UInt64, on server: String) -> AnyPromise { @@ -348,6 +394,7 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { let annotation = annotations.first, let info = annotation["value"] as? JSON, let displayName = info["name"] as? String, + let profilePictureURL = info["avatar"] as? String, let countInfo = data["counts"] as? JSON, let memberCount = countInfo["subscribers"] as? Int else { print("[Loki] Couldn't parse info for public chat channel with ID: \(channel) on server: \(server) from: \(rawResponse).") @@ -357,8 +404,9 @@ public final class LokiPublicChatAPI : LokiDotNetAPI { try! Storage.writeSync { transaction in storage.setUserCount(memberCount, forPublicChatWithID: "\(server).\(channel)", in: transaction) } - // TODO: Use this to update open group names as needed - return LokiPublicChatInfo(displayName: displayName, memberCount: memberCount) + let publicChatInfo = LokiPublicChatInfo(displayName: displayName, profilePictureURL: profilePictureURL, memberCount: memberCount) + updateProfileIfNeeded(for: channel, on: server, from: publicChatInfo) + return publicChatInfo } }.handlingInvalidAuthTokenIfNeeded(for: server) } diff --git a/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatInfo.swift b/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatInfo.swift index 90c2a1685..607f06ba3 100644 --- a/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatInfo.swift +++ b/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatInfo.swift @@ -1,5 +1,6 @@ public struct LokiPublicChatInfo { public let displayName: String + public let profilePictureURL: String? public let memberCount: Int } diff --git a/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatPoller.swift b/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatPoller.swift index 0b98a325a..135ce1dfd 100644 --- a/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatPoller.swift +++ b/SignalServiceKit/src/Loki/API/Open Groups/LokiPublicChatPoller.swift @@ -150,6 +150,9 @@ public final class LokiPublicChatPoller : NSObject { if !wasSentByCurrentUser { content.setDataMessage(try! dataMessage.build()) } else { + // The line below is necessary to make it so that when a user sends a message in an open group and then + // deletes and re-joins the open group without closing the app in between, the message isn't ignored. + SyncMessagesProtocol.dropFromSyncMessageTimestampCache(message.timestamp, for: senderHexEncodedPublicKey) let syncMessageSentBuilder = SSKProtoSyncMessageSent.builder() syncMessageSentBuilder.setMessage(try! dataMessage.build()) syncMessageSentBuilder.setDestination(userHexEncodedPublicKey) diff --git a/SignalServiceKit/src/Loki/Database/OWSPrimaryStorage+Loki.swift b/SignalServiceKit/src/Loki/Database/OWSPrimaryStorage+Loki.swift index f4d07f4ab..060aeac66 100644 --- a/SignalServiceKit/src/Loki/Database/OWSPrimaryStorage+Loki.swift +++ b/SignalServiceKit/src/Loki/Database/OWSPrimaryStorage+Loki.swift @@ -139,4 +139,12 @@ public extension OWSPrimaryStorage { public func setUserCount(_ userCount: Int, forPublicChatWithID publicChatID: String, in transaction: YapDatabaseReadWriteTransaction) { transaction.setObject(userCount, forKey: publicChatID, inCollection: Storage.openGroupUserCountCollection) } + + public func getProfilePictureURL(forPublicChatWithID publicChatID: String, in transaction: YapDatabaseReadTransaction) -> String? { + return transaction.object(forKey: publicChatID, inCollection: Storage.openGroupProfilePictureURLCollection) as? String + } + + public func setProfilePictureURL(_ profilePictureURL: String?, forPublicChatWithID publicChatID: String, in transaction: YapDatabaseReadWriteTransaction) { + transaction.setObject(profilePictureURL, forKey: publicChatID, inCollection: Storage.openGroupProfilePictureURLCollection) + } } diff --git a/SignalServiceKit/src/Loki/Database/Storage+Collections.swift b/SignalServiceKit/src/Loki/Database/Storage+Collections.swift index 00b868328..7c89c1a8c 100644 --- a/SignalServiceKit/src/Loki/Database/Storage+Collections.swift +++ b/SignalServiceKit/src/Loki/Database/Storage+Collections.swift @@ -13,6 +13,7 @@ @objc public static let onionRequestPathCollection = "LokiOnionRequestPathCollection" @objc public static let openGroupCollection = "LokiPublicChatCollection" + @objc public static let openGroupProfilePictureURLCollection = "LokiPublicChatAvatarURLCollection" @objc public static let openGroupUserCountCollection = "LokiPublicChatUserCountCollection" @objc public static let sessionRequestTimestampCollection = "LokiSessionRequestTimestampCollection" @objc public static let snodePoolCollection = "LokiSnodePoolCollection" diff --git a/SignalServiceKit/src/Loki/Protocol/Friend Requests/LKFriendRequestMessage.m b/SignalServiceKit/src/Loki/Protocol/Friend Requests/LKFriendRequestMessage.m index 408d467e9..cca33718c 100644 --- a/SignalServiceKit/src/Loki/Protocol/Friend Requests/LKFriendRequestMessage.m +++ b/SignalServiceKit/src/Loki/Protocol/Friend Requests/LKFriendRequestMessage.m @@ -17,7 +17,7 @@ #pragma mark Building - (SSKProtoContentBuilder *)prepareCustomContentBuilder:(SignalRecipient *)recipient { - SSKProtoContentBuilder *contentBuilder = SSKProtoContent.builder; + SSKProtoContentBuilder *contentBuilder = [super prepareCustomContentBuilder:recipient]; // Attach the pre key bundle for the contact in question PreKeyBundle *preKeyBundle = [OWSPrimaryStorage.sharedManager generatePreKeyBundleForContact:recipient.recipientId]; SSKProtoPrekeyBundleMessageBuilder *preKeyBundleMessageBuilder = [SSKProtoPrekeyBundleMessage builderFromPreKeyBundle:preKeyBundle]; diff --git a/SignalServiceKit/src/Loki/Protocol/Meta/LKAddressMessage.m b/SignalServiceKit/src/Loki/Protocol/Meta/LKAddressMessage.m index bcfc005dc..cdfdb4516 100644 --- a/SignalServiceKit/src/Loki/Protocol/Meta/LKAddressMessage.m +++ b/SignalServiceKit/src/Loki/Protocol/Meta/LKAddressMessage.m @@ -27,7 +27,7 @@ #pragma mark Building - (SSKProtoContentBuilder *)prepareCustomContentBuilder:(SignalRecipient *)recipient { - SSKProtoContentBuilder *contentBuilder = SSKProtoContent.builder; + SSKProtoContentBuilder *contentBuilder = [super prepareCustomContentBuilder:recipient]; SSKProtoLokiAddressMessageBuilder *addressMessageBuilder = SSKProtoLokiAddressMessage.builder; [addressMessageBuilder setPtpAddress:self.address]; uint32_t portAsUInt32 = self.port; diff --git a/SignalServiceKit/src/Loki/Protocol/Multi Device/LKDeviceLinkMessage.m b/SignalServiceKit/src/Loki/Protocol/Multi Device/LKDeviceLinkMessage.m index 42949195d..ed2b6b180 100644 --- a/SignalServiceKit/src/Loki/Protocol/Multi Device/LKDeviceLinkMessage.m +++ b/SignalServiceKit/src/Loki/Protocol/Multi Device/LKDeviceLinkMessage.m @@ -35,7 +35,7 @@ #pragma mark Building - (SSKProtoContentBuilder *)prepareCustomContentBuilder:(SignalRecipient *)recipient { - SSKProtoContentBuilder *contentBuilder = SSKProtoContent.builder; + SSKProtoContentBuilder *contentBuilder = [super prepareCustomContentBuilder:recipient]; NSError *error; if (self.kind == LKDeviceLinkMessageKindRequest) { // The slave device attaches a pre key bundle with the request it sends, so that a diff --git a/SignalServiceKit/src/Loki/Protocol/Multi Device/MultiDeviceProtocol.swift b/SignalServiceKit/src/Loki/Protocol/Multi Device/MultiDeviceProtocol.swift index bceace01b..282275699 100644 --- a/SignalServiceKit/src/Loki/Protocol/Multi Device/MultiDeviceProtocol.swift +++ b/SignalServiceKit/src/Loki/Protocol/Multi Device/MultiDeviceProtocol.swift @@ -9,9 +9,6 @@ import PromiseKit // • Document the expected cases for everything. // • Express those cases in tests. -// FIXME: We're manually attaching the sender certificate and UD recipient access to message sends in a lot of places. It'd be great -// to clean this up and just do it in one spot. - @objc(LKMultiDeviceProtocol) public final class MultiDeviceProtocol : NSObject { @@ -45,15 +42,9 @@ public final class MultiDeviceProtocol : NSObject { storage.dbReadConnection.read { transaction in recipient = SignalRecipient.getOrBuildUnsavedRecipient(forRecipientId: destination.hexEncodedPublicKey, transaction: transaction) } - let udManager = SSKEnvironment.shared.udManager - let senderCertificate = udManager.getSenderCertificate() - var recipientUDAccess: OWSUDAccess? - if let senderCertificate = senderCertificate { - recipientUDAccess = udManager.udAccess(forRecipientId: destination.hexEncodedPublicKey, requireSyncAccess: true) // Starts a new write transaction internally - } - // TODO: Why is it okay that the thread doesn't get changed? + // TODO: Why is it okay that the thread, sender certificate, etc. don't get changed? return OWSMessageSend(message: messageSend.message, thread: messageSend.thread, recipient: recipient, - senderCertificate: senderCertificate, udAccess: recipientUDAccess, localNumber: messageSend.localNumber, success: { + senderCertificate: messageSend.senderCertificate, udAccess: messageSend.udAccess, localNumber: messageSend.localNumber, success: { seal.fulfill(()) }, failure: { error in seal.reject(error) @@ -73,6 +64,7 @@ public final class MultiDeviceProtocol : NSObject { } } return threadPromise.then2 { thread -> Promise in + return threadPromise.then(on: OWSDispatch.sendingQueue()) { thread -> Promise in let message = messageSend.message let messageSender = SSKEnvironment.shared.messageSender let (promise, seal) = Promise.pending() @@ -81,9 +73,7 @@ public final class MultiDeviceProtocol : NSObject { && message.shouldBeSaved() // shouldBeSaved indicates it isn't a transient message if !shouldSendAutoGeneratedFR { let messageSendCopy = copy(messageSend, for: destination, with: seal) - OWSDispatch.sendingQueue().async { - messageSender.sendMessage(messageSendCopy) - } + messageSender.sendMessage(messageSendCopy) } else { Storage.write { transaction in getAutoGeneratedMultiDeviceFRMessageSend(for: destination.hexEncodedPublicKey, in: transaction, seal: seal) @@ -141,17 +131,7 @@ public final class MultiDeviceProtocol : NSObject { }.catch2 { error in // Proceed even if updating the recipient's device links failed, so that message sending // is independent of whether the file server is online - let udManager = SSKEnvironment.shared.udManager - let senderCertificate = udManager.getSenderCertificate() - var recipientUDAccess: OWSUDAccess? - if let senderCertificate = senderCertificate { - recipientUDAccess = udManager.udAccess(forRecipientId: recipientID, requireSyncAccess: true) // Starts a new write transaction internally - } - messageSend.senderCertificate = senderCertificate - messageSend.udAccess = recipientUDAccess - OWSDispatch.sendingQueue().async { - messageSender.sendMessage(messageSend) - } + messageSender.sendMessage(messageSend) } } @@ -165,9 +145,7 @@ public final class MultiDeviceProtocol : NSObject { @objc(getAutoGeneratedMultiDeviceFRMessageForHexEncodedPublicKey:in:) public static func getAutoGeneratedMultiDeviceFRMessage(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadWriteTransaction) -> FriendRequestMessage { let thread = TSContactThread.getOrCreateThread(withContactId: hexEncodedPublicKey, transaction: transaction) - let result = FriendRequestMessage(timestamp: NSDate.ows_millisecondTimeStamp(), thread: thread, body: "Please accept to enable messages to be synced across devices") - result.skipSave = true // TODO: Why is this necessary again? - return result + return FriendRequestMessage(timestamp: NSDate.ows_millisecondTimeStamp(), thread: thread, body: "Please accept to enable messages to be synced across devices") } /// See [Auto-Generated Friend Requests](https://github.com/loki-project/session-protocol-docs/wiki/Auto-Generated-Friend-Requests) for more information. diff --git a/SignalServiceKit/src/Loki/Protocol/Sync Messages/SyncMessagesProtocol.swift b/SignalServiceKit/src/Loki/Protocol/Sync Messages/SyncMessagesProtocol.swift index 29f7edc08..e7089e7cb 100644 --- a/SignalServiceKit/src/Loki/Protocol/Sync Messages/SyncMessagesProtocol.swift +++ b/SignalServiceKit/src/Loki/Protocol/Sync Messages/SyncMessagesProtocol.swift @@ -23,6 +23,22 @@ public final class SyncMessagesProtocol : NSObject { // FIXME: We added this check to avoid a crash, but we should really figure out why that crash was happening in the first place return !UserDefaults.standard[.hasLaunchedOnce] } + + @objc(syncProfile) + public static func syncProfile() { + try! Storage.writeSync { transaction in + let userPublicKey = getUserHexEncodedPublicKey() + let linkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: userPublicKey, in: transaction) + for publicKey in linkedDevices { + guard publicKey != userPublicKey else { continue } + let thread = TSContactThread.getOrCreateThread(withContactId: publicKey, transaction: transaction) + let syncMessage = OWSOutgoingSyncMessage.init(in: thread, messageBody: "", attachmentId: nil) + syncMessage.save(with: transaction) + let messageSenderJobQueue = SSKEnvironment.shared.messageSenderJobQueue + messageSenderJobQueue.add(message: syncMessage, transaction: transaction) + } + } + } @objc(syncContactWithHexEncodedPublicKey:in:) public static func syncContact(_ hexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction) -> AnyPromise { @@ -97,6 +113,12 @@ public final class SyncMessagesProtocol : NSObject { return LokiDatabaseUtilities.isUserLinkedDevice(hexEncodedPublicKey, transaction: transaction) } + public static func dropFromSyncMessageTimestampCache(_ timestamp: UInt64, for hexEncodedPublicKey: String) { + var timestamps: Set = syncMessageTimestamps[hexEncodedPublicKey] ?? [] + if timestamps.contains(timestamp) { timestamps.remove(timestamp) } + syncMessageTimestamps[hexEncodedPublicKey] = timestamps + } + // TODO: We should probably look at why sync messages are being duplicated rather than doing this @objc(isDuplicateSyncMessage:fromHexEncodedPublicKey:) public static func isDuplicateSyncMessage(_ protoContent: SSKProtoContent, from hexEncodedPublicKey: String) -> Bool { @@ -166,15 +188,17 @@ public final class SyncMessagesProtocol : NSObject { let parser = ContactParser(data: data) let hexEncodedPublicKeys = parser.parseHexEncodedPublicKeys() let userHexEncodedPublicKey = getUserHexEncodedPublicKey() + let linkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: userHexEncodedPublicKey, in: transaction) // Try to establish sessions for hexEncodedPublicKey in hexEncodedPublicKeys { - guard hexEncodedPublicKey != userHexEncodedPublicKey else { continue } // Skip self + guard !linkedDevices.contains(hexEncodedPublicKey) else { continue } // Skip self and linked devices // We don't update the friend request status; that's done in OWSMessageSender.sendMessage(_:) let friendRequestStatus = storage.getFriendRequestStatus(for: hexEncodedPublicKey, transaction: transaction) switch friendRequestStatus { case .none, .requestExpired: // We need to send the FR message to all of the user's devices as the contact sync message excludes slave devices let autoGeneratedFRMessage = MultiDeviceProtocol.getAutoGeneratedMultiDeviceFRMessage(for: hexEncodedPublicKey, in: transaction) + autoGeneratedFRMessage.save(with: transaction) // Use the message sender job queue for this to ensure that these messages get sent // AFTER session requests (it's asssumed that the master device first syncs closed // groups first and contacts after that). @@ -228,6 +252,9 @@ public final class SyncMessagesProtocol : NSObject { for openGroup in groups { let openGroupManager = LokiPublicChatManager.shared guard openGroupManager.getChat(server: openGroup.url, channel: openGroup.channel) == nil else { return } + let userPublicKey = UserDefaults.standard[.masterHexEncodedPublicKey] ?? getUserHexEncodedPublicKey() + let displayName = SSKEnvironment.shared.profileManager.profileNameForRecipient(withID: userPublicKey) + LokiPublicChatAPI.setDisplayName(to: displayName, on: openGroup.url) openGroupManager.addChat(server: openGroup.url, channel: openGroup.channel) } } diff --git a/SignalServiceKit/src/Messages/Interactions/OWSEndSessionMessage.m b/SignalServiceKit/src/Messages/Interactions/OWSEndSessionMessage.m index 821cc60bb..3a5420776 100644 --- a/SignalServiceKit/src/Messages/Interactions/OWSEndSessionMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/OWSEndSessionMessage.m @@ -51,7 +51,7 @@ NS_ASSUME_NONNULL_BEGIN } - (SSKProtoContentBuilder *)prepareCustomContentBuilder:(SignalRecipient *)recipient { - SSKProtoContentBuilder *builder = SSKProtoContent.builder; + SSKProtoContentBuilder *builder = [super prepareCustomContentBuilder:recipient]; PreKeyBundle *bundle = [OWSPrimaryStorage.sharedManager generatePreKeyBundleForContact:recipient.recipientId]; SSKProtoPrekeyBundleMessageBuilder *preKeyBuilder = [SSKProtoPrekeyBundleMessage builderFromPreKeyBundle:bundle]; diff --git a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m index 90e5d6929..8e8c1b077 100644 --- a/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m +++ b/SignalServiceKit/src/Messages/Interactions/TSOutgoingMessage.m @@ -1125,24 +1125,27 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt } - (SSKProtoContentBuilder *)prepareCustomContentBuilder:(SignalRecipient *)recipient { - return SSKProtoContent.builder; + SSKProtoDataMessage *_Nullable dataMessage = [self buildDataMessage:recipient.recipientId]; + + if (!dataMessage) { + OWSFailDebug(@"Couldn't build protobuf."); + return nil; + } + + SSKProtoContentBuilder *contentBuilder = SSKProtoContent.builder; + [contentBuilder setDataMessage:dataMessage]; + + return contentBuilder; } - (nullable NSData *)buildPlainTextData:(SignalRecipient *)recipient { - NSError *error; - SSKProtoDataMessage *_Nullable dataMessage = [self buildDataMessage:recipient.recipientId]; - if (error || !dataMessage) { - OWSFailDebug(@"could not build protobuf: %@", error); - return nil; - } - SSKProtoContentBuilder *contentBuilder = [self prepareCustomContentBuilder:recipient]; - - [contentBuilder setDataMessage:dataMessage]; + + NSError *error; NSData *_Nullable contentData = [contentBuilder buildSerializedDataAndReturnError:&error]; if (error || !contentData) { - OWSFailDebug(@"could not serialize protobuf: %@", error); + OWSFailDebug(@"Couldn't serialize protobuf due to error: %@.", error); return nil; } diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index 12fd71a34..70dc85085 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -928,7 +928,6 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException"; { OWSAssertDebug(messageSend); OWSAssertDebug(messageSend.thread || [messageSend.message isKindOfClass:[OWSOutgoingSyncMessage class]]); - OWSAssertDebug(messageSend.isUDSend); TSOutgoingMessage *message = messageSend.message; SignalRecipient *recipient = messageSend.recipient;