Merge branch 'dev' into threading

pull/214/head
nielsandriesse 5 years ago
commit 6cc28426d6

@ -320,7 +320,7 @@ SPEC CHECKSUMS:
SessionServiceKit: c792d64fc86aaa0f7325beaa36e1a9211a887359 SessionServiceKit: c792d64fc86aaa0f7325beaa36e1a9211a887359
SQLCipher: e434ed542b24f38ea7b36468a13f9765e1b5c072 SQLCipher: e434ed542b24f38ea7b36468a13f9765e1b5c072
SSZipArchive: 62d4947b08730e4cda640473b0066d209ff033c9 SSZipArchive: 62d4947b08730e4cda640473b0066d209ff033c9
Starscream: 8aaf1a7feb805c816d0e7d3190ef23856f6665b9 Starscream: ef3ece99d765eeccb67de105bfa143f929026cf5
SwiftProtobuf: 241400280f912735c1e1b9fe675fdd2c6c4d42e2 SwiftProtobuf: 241400280f912735c1e1b9fe675fdd2c6c4d42e2
YapDatabase: b418a4baa6906e8028748938f9159807fd039af4 YapDatabase: b418a4baa6906e8028748938f9159807fd039af4
YYImage: 1e1b62a9997399593e4b9c4ecfbbabbf1d3f3b54 YYImage: 1e1b62a9997399593e4b9c4ecfbbabbf1d3f3b54

@ -1 +1 @@
Subproject commit 827a67a609b8a4166220440c911796f1a18614ee Subproject commit 6077f71ac9595fa165558a398d296a6c97b5b143

@ -1394,12 +1394,15 @@ static NSTimeInterval launchStartedAt;
NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
if (userHexEncodedPublicKey == nil) { return; } if (userHexEncodedPublicKey == nil) { return; }
self.lokiPoller = [[LKPoller alloc] initOnMessagesReceived:^(NSArray<SSKProtoEnvelope *> *messages) { self.lokiPoller = [[LKPoller alloc] initOnMessagesReceived:^(NSArray<SSKProtoEnvelope *> *messages) {
if (messages.count != 0) {
[LKLogger print:@"[Loki] Received new messages."];
}
for (SSKProtoEnvelope *message in messages) { for (SSKProtoEnvelope *message in messages) {
NSData *data = [message serializedDataAndReturnError:nil]; NSData *data = [message serializedDataAndReturnError:nil];
if (data != nil) { if (data != nil) {
[SSKEnvironment.shared.messageReceiver handleReceivedEnvelopeData:data]; [SSKEnvironment.shared.messageReceiver handleReceivedEnvelopeData:data];
} else { } else {
NSLog(@"[Loki] Failed to deserialize envelope."); [LKLogger print:@"[Loki] Failed to deserialize envelope."];
} }
} }
}]; }];

@ -134,26 +134,29 @@ final class ConversationCell : UITableViewCell {
// MARK: Updating // MARK: Updating
private func update() { private func update() {
AssertIsOnMainThread()
MentionsManager.populateUserPublicKeyCacheIfNeeded(for: threadViewModel.threadRecord.uniqueId!) // FIXME: This is a terrible place to do this 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 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.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.hexEncodedPublicKey = ""
profilePictureView.isRSSFeed = true 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!] ?? [] var users = MentionsManager.userPublicKeyCache[threadViewModel.threadRecord.uniqueId!] ?? []
users.remove(getUserHexEncodedPublicKey()) users.remove(getUserHexEncodedPublicKey())
let randomUsers = users.sorted().prefix(2) // Sort to provide a level of stability let randomUsers = users.sorted().prefix(2) // Sort to provide a level of stability
if !randomUsers.isEmpty { profilePictureView.hexEncodedPublicKey = randomUsers.count >= 1 ? randomUsers[0] : ""
profilePictureView.hexEncodedPublicKey = randomUsers[0]
profilePictureView.additionalHexEncodedPublicKey = randomUsers.count >= 2 ? randomUsers[1] : "" profilePictureView.additionalHexEncodedPublicKey = randomUsers.count >= 2 ? randomUsers[1] : ""
} else {
profilePictureView.hexEncodedPublicKey = ""
profilePictureView.additionalHexEncodedPublicKey = ""
}
profilePictureView.isRSSFeed = (threadViewModel.threadRecord as? TSGroupThread)?.isRSSFeed ?? false
} }
} else { } else { // A one-on-one chat
profilePictureView.hexEncodedPublicKey = threadViewModel.contactIdentifier! profilePictureView.hexEncodedPublicKey = threadViewModel.contactIdentifier!
profilePictureView.additionalHexEncodedPublicKey = nil profilePictureView.additionalHexEncodedPublicKey = nil
profilePictureView.isRSSFeed = false profilePictureView.isRSSFeed = false

@ -133,8 +133,11 @@ final class JoinPublicChatVC : BaseVC, UIPageViewControllerDataSource, UIPageVie
isJoining = true isJoining = true
let channelID: UInt64 = 1 let channelID: UInt64 = 1
let urlAsString = url.absoluteString let urlAsString = url.absoluteString
let displayName = OWSProfileManager.shared().localProfileName() let userPublicKey = UserDefaults.standard[.masterHexEncodedPublicKey] ?? getUserHexEncodedPublicKey()
// TODO: Profile picture & profile key let profileManager = OWSProfileManager.shared()
let displayName = profileManager.profileNameForRecipient(withID: userPublicKey)
let profilePictureURL = profileManager.profilePictureURL()
let profileKey = profileManager.localProfileKey().keyData
try! Storage.writeSync { transaction in try! Storage.writeSync { transaction in
transaction.removeObject(forKey: "\(urlAsString).\(channelID)", inCollection: LokiPublicChatAPI.lastMessageServerIDCollection) transaction.removeObject(forKey: "\(urlAsString).\(channelID)", inCollection: LokiPublicChatAPI.lastMessageServerIDCollection)
transaction.removeObject(forKey: "\(urlAsString).\(channelID)", inCollection: LokiPublicChatAPI.lastDeletionServerIDCollection) transaction.removeObject(forKey: "\(urlAsString).\(channelID)", inCollection: LokiPublicChatAPI.lastDeletionServerIDCollection)
@ -143,6 +146,7 @@ final class JoinPublicChatVC : BaseVC, UIPageViewControllerDataSource, UIPageVie
.done(on: .main) { [weak self] _ in .done(on: .main) { [weak self] _ in
let _ = LokiPublicChatAPI.getMessages(for: channelID, on: urlAsString) let _ = LokiPublicChatAPI.getMessages(for: channelID, on: urlAsString)
let _ = LokiPublicChatAPI.setDisplayName(to: displayName, 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 _ = LokiPublicChatAPI.join(channelID, on: urlAsString)
let syncManager = SSKEnvironment.shared.syncManager let syncManager = SSKEnvironment.shared.syncManager
let _ = syncManager.syncAllOpenGroups() let _ = syncManager.syncAllOpenGroups()

@ -115,7 +115,8 @@ class ConversationViewItemActions: NSObject {
actions.append(deleteAction) 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) let reportAction = MessageActionBuilder.report(conversationViewItem, delegate: delegate)
actions.append(reportAction) actions.append(reportAction)
} }
@ -159,7 +160,8 @@ class ConversationViewItemActions: NSObject {
actions.append(deleteAction) 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) let reportAction = MessageActionBuilder.report(conversationViewItem, delegate: delegate)
actions.append(reportAction) actions.append(reportAction)
} }
@ -192,7 +194,8 @@ class ConversationViewItemActions: NSObject {
actions.append(deleteAction) 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) let reportAction = MessageActionBuilder.report(conversationViewItem, delegate: delegate)
actions.append(reportAction) actions.append(reportAction)
} }

@ -957,8 +957,15 @@ const CGFloat kIconViewLength = 24;
[stackView setLayoutMarginsRelativeArrangement:YES]; [stackView setLayoutMarginsRelativeArrangement:YES];
if (self.isGroupThread) { if (self.isGroupThread) {
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.hexEncodedPublicKey = @"";
profilePictureView.isRSSFeed = true; // For now just always show the Session logo profilePictureView.isRSSFeed = true; // For now just always show the Session logo
}
} else { } else {
profilePictureView.hexEncodedPublicKey = self.thread.contactIdentifier; profilePictureView.hexEncodedPublicKey = self.thread.contactIdentifier;

@ -7,6 +7,7 @@ public final class ProfilePictureView : UIView {
@objc public var isRSSFeed = false @objc public var isRSSFeed = false
@objc public var hexEncodedPublicKey: String! @objc public var hexEncodedPublicKey: String!
@objc public var additionalHexEncodedPublicKey: String? @objc public var additionalHexEncodedPublicKey: String?
@objc public var openGroupProfilePicture: UIImage?
// MARK: Components // MARK: Components
private lazy var imageView = getImageView() private lazy var imageView = getImageView()
@ -43,6 +44,7 @@ public final class ProfilePictureView : UIView {
// MARK: Updating // MARK: Updating
@objc public func update() { @objc public func update() {
AssertIsOnMainThread()
func getProfilePicture(of size: CGFloat, for hexEncodedPublicKey: String) -> UIImage? { func getProfilePicture(of size: CGFloat, for hexEncodedPublicKey: String) -> UIImage? {
guard !hexEncodedPublicKey.isEmpty else { return nil } guard !hexEncodedPublicKey.isEmpty else { return nil }
return OWSProfileManager.shared().profileAvatar(forRecipientId: hexEncodedPublicKey) ?? Identicon.generateIcon(string: hexEncodedPublicKey, size: size) 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.isHidden = true
additionalImageView.image = nil additionalImageView.image = nil
} }
guard hexEncodedPublicKey != nil else { return } // Can happen in rare cases guard hexEncodedPublicKey != nil || openGroupProfilePicture != nil else { return }
imageView.image = isRSSFeed ? nil : getProfilePicture(of: size, for: hexEncodedPublicKey) 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.backgroundColor = isRSSFeed ? UIColor(rgbHex: 0x353535) : UIColor(rgbHex: 0xD8D8D8) // UIColor(rgbHex: 0xD8D8D8) = Colors.unimportant
imageView.layer.cornerRadius = size / 2 imageView.layer.cornerRadius = size / 2
imageView.contentMode = isRSSFeed ? .center : .scaleAspectFit imageView.contentMode = isRSSFeed ? .center : .scaleAspectFit

@ -240,6 +240,7 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
void (^failureBlock)(NSError *) = ^(NSError *error) { void (^failureBlock)(NSError *) = ^(NSError *error) {
OWSLogError(@"Updating service with profile failed."); OWSLogError(@"Updating service with profile failed.");
/*
// We use a "self-only" contact sync to indicate to desktop // We use a "self-only" contact sync to indicate to desktop
// that we've changed our profile and that it should do a // that we've changed our profile and that it should do a
// profile fetch for "self". // profile fetch for "self".
@ -249,6 +250,10 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
if (requiresSync) { if (requiresSync) {
[[self.syncManager syncLocalContact] retainUntilComplete]; [[self.syncManager syncLocalContact] retainUntilComplete];
} }
*/
if (requiresSync) {
[LKSyncMessagesProtocol syncProfile];
}
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
failureBlockParameter(error); failureBlockParameter(error);
@ -257,12 +262,17 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
void (^successBlock)(void) = ^{ void (^successBlock)(void) = ^{
OWSLogInfo(@"Successfully updated service with profile."); OWSLogInfo(@"Successfully updated service with profile.");
/*
// We use a "self-only" contact sync to indicate to desktop // We use a "self-only" contact sync to indicate to desktop
// that we've changed our profile and that it should do a // that we've changed our profile and that it should do a
// profile fetch for "self". // profile fetch for "self".
if (requiresSync) { if (requiresSync) {
[[self.syncManager syncLocalContact] retainUntilComplete]; [[self.syncManager syncLocalContact] retainUntilComplete];
} }
*/
if (requiresSync) {
[LKSyncMessagesProtocol syncProfile];
}
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
successBlockParameter(); successBlockParameter();

@ -19,7 +19,8 @@ public final class LokiPublicChatAPI : LokiDotNetAPI {
// MARK: Convenience // MARK: Convenience
private static var userDisplayName: String { 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 // 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 // MARK: Joining & Leaving
@objc(getInfoForChannelWithID:onServer:) @objc(getInfoForChannelWithID:onServer:)
public static func objc_getInfo(for channel: UInt64, on server: String) -> AnyPromise { 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 annotation = annotations.first,
let info = annotation["value"] as? JSON, let info = annotation["value"] as? JSON,
let displayName = info["name"] as? String, let displayName = info["name"] as? String,
let profilePictureURL = info["avatar"] as? String,
let countInfo = data["counts"] as? JSON, let countInfo = data["counts"] as? JSON,
let memberCount = countInfo["subscribers"] as? Int else { 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).") 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 try! Storage.writeSync { transaction in
storage.setUserCount(memberCount, forPublicChatWithID: "\(server).\(channel)", in: transaction) storage.setUserCount(memberCount, forPublicChatWithID: "\(server).\(channel)", in: transaction)
} }
// TODO: Use this to update open group names as needed let publicChatInfo = LokiPublicChatInfo(displayName: displayName, profilePictureURL: profilePictureURL, memberCount: memberCount)
return LokiPublicChatInfo(displayName: displayName, memberCount: memberCount) updateProfileIfNeeded(for: channel, on: server, from: publicChatInfo)
return publicChatInfo
} }
}.handlingInvalidAuthTokenIfNeeded(for: server) }.handlingInvalidAuthTokenIfNeeded(for: server)
} }

@ -1,5 +1,6 @@
public struct LokiPublicChatInfo { public struct LokiPublicChatInfo {
public let displayName: String public let displayName: String
public let profilePictureURL: String?
public let memberCount: Int public let memberCount: Int
} }

@ -150,6 +150,9 @@ public final class LokiPublicChatPoller : NSObject {
if !wasSentByCurrentUser { if !wasSentByCurrentUser {
content.setDataMessage(try! dataMessage.build()) content.setDataMessage(try! dataMessage.build())
} else { } 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() let syncMessageSentBuilder = SSKProtoSyncMessageSent.builder()
syncMessageSentBuilder.setMessage(try! dataMessage.build()) syncMessageSentBuilder.setMessage(try! dataMessage.build())
syncMessageSentBuilder.setDestination(userHexEncodedPublicKey) syncMessageSentBuilder.setDestination(userHexEncodedPublicKey)

@ -139,4 +139,12 @@ public extension OWSPrimaryStorage {
public func setUserCount(_ userCount: Int, forPublicChatWithID publicChatID: String, in transaction: YapDatabaseReadWriteTransaction) { public func setUserCount(_ userCount: Int, forPublicChatWithID publicChatID: String, in transaction: YapDatabaseReadWriteTransaction) {
transaction.setObject(userCount, forKey: publicChatID, inCollection: Storage.openGroupUserCountCollection) 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)
}
} }

@ -13,6 +13,7 @@
@objc public static let onionRequestPathCollection = "LokiOnionRequestPathCollection" @objc public static let onionRequestPathCollection = "LokiOnionRequestPathCollection"
@objc public static let openGroupCollection = "LokiPublicChatCollection" @objc public static let openGroupCollection = "LokiPublicChatCollection"
@objc public static let openGroupProfilePictureURLCollection = "LokiPublicChatAvatarURLCollection"
@objc public static let openGroupUserCountCollection = "LokiPublicChatUserCountCollection" @objc public static let openGroupUserCountCollection = "LokiPublicChatUserCountCollection"
@objc public static let sessionRequestTimestampCollection = "LokiSessionRequestTimestampCollection" @objc public static let sessionRequestTimestampCollection = "LokiSessionRequestTimestampCollection"
@objc public static let snodePoolCollection = "LokiSnodePoolCollection" @objc public static let snodePoolCollection = "LokiSnodePoolCollection"

@ -17,7 +17,7 @@
#pragma mark Building #pragma mark Building
- (SSKProtoContentBuilder *)prepareCustomContentBuilder:(SignalRecipient *)recipient { - (SSKProtoContentBuilder *)prepareCustomContentBuilder:(SignalRecipient *)recipient {
SSKProtoContentBuilder *contentBuilder = SSKProtoContent.builder; SSKProtoContentBuilder *contentBuilder = [super prepareCustomContentBuilder:recipient];
// Attach the pre key bundle for the contact in question // Attach the pre key bundle for the contact in question
PreKeyBundle *preKeyBundle = [OWSPrimaryStorage.sharedManager generatePreKeyBundleForContact:recipient.recipientId]; PreKeyBundle *preKeyBundle = [OWSPrimaryStorage.sharedManager generatePreKeyBundleForContact:recipient.recipientId];
SSKProtoPrekeyBundleMessageBuilder *preKeyBundleMessageBuilder = [SSKProtoPrekeyBundleMessage builderFromPreKeyBundle:preKeyBundle]; SSKProtoPrekeyBundleMessageBuilder *preKeyBundleMessageBuilder = [SSKProtoPrekeyBundleMessage builderFromPreKeyBundle:preKeyBundle];

@ -27,7 +27,7 @@
#pragma mark Building #pragma mark Building
- (SSKProtoContentBuilder *)prepareCustomContentBuilder:(SignalRecipient *)recipient { - (SSKProtoContentBuilder *)prepareCustomContentBuilder:(SignalRecipient *)recipient {
SSKProtoContentBuilder *contentBuilder = SSKProtoContent.builder; SSKProtoContentBuilder *contentBuilder = [super prepareCustomContentBuilder:recipient];
SSKProtoLokiAddressMessageBuilder *addressMessageBuilder = SSKProtoLokiAddressMessage.builder; SSKProtoLokiAddressMessageBuilder *addressMessageBuilder = SSKProtoLokiAddressMessage.builder;
[addressMessageBuilder setPtpAddress:self.address]; [addressMessageBuilder setPtpAddress:self.address];
uint32_t portAsUInt32 = self.port; uint32_t portAsUInt32 = self.port;

@ -35,7 +35,7 @@
#pragma mark Building #pragma mark Building
- (SSKProtoContentBuilder *)prepareCustomContentBuilder:(SignalRecipient *)recipient { - (SSKProtoContentBuilder *)prepareCustomContentBuilder:(SignalRecipient *)recipient {
SSKProtoContentBuilder *contentBuilder = SSKProtoContent.builder; SSKProtoContentBuilder *contentBuilder = [super prepareCustomContentBuilder:recipient];
NSError *error; NSError *error;
if (self.kind == LKDeviceLinkMessageKindRequest) { if (self.kind == LKDeviceLinkMessageKindRequest) {
// The slave device attaches a pre key bundle with the request it sends, so that a // The slave device attaches a pre key bundle with the request it sends, so that a

@ -9,9 +9,6 @@ import PromiseKit
// Document the expected cases for everything. // Document the expected cases for everything.
// Express those cases in tests. // 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) @objc(LKMultiDeviceProtocol)
public final class MultiDeviceProtocol : NSObject { public final class MultiDeviceProtocol : NSObject {
@ -45,15 +42,9 @@ public final class MultiDeviceProtocol : NSObject {
storage.dbReadConnection.read { transaction in storage.dbReadConnection.read { transaction in
recipient = SignalRecipient.getOrBuildUnsavedRecipient(forRecipientId: destination.hexEncodedPublicKey, transaction: transaction) recipient = SignalRecipient.getOrBuildUnsavedRecipient(forRecipientId: destination.hexEncodedPublicKey, transaction: transaction)
} }
let udManager = SSKEnvironment.shared.udManager // TODO: Why is it okay that the thread, sender certificate, etc. don't get changed?
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?
return OWSMessageSend(message: messageSend.message, thread: messageSend.thread, recipient: recipient, 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(()) seal.fulfill(())
}, failure: { error in }, failure: { error in
seal.reject(error) seal.reject(error)
@ -73,6 +64,7 @@ public final class MultiDeviceProtocol : NSObject {
} }
} }
return threadPromise.then2 { thread -> Promise<Void> in return threadPromise.then2 { thread -> Promise<Void> in
return threadPromise.then(on: OWSDispatch.sendingQueue()) { thread -> Promise<Void> in
let message = messageSend.message let message = messageSend.message
let messageSender = SSKEnvironment.shared.messageSender let messageSender = SSKEnvironment.shared.messageSender
let (promise, seal) = Promise<Void>.pending() let (promise, seal) = Promise<Void>.pending()
@ -81,9 +73,7 @@ public final class MultiDeviceProtocol : NSObject {
&& message.shouldBeSaved() // shouldBeSaved indicates it isn't a transient message && message.shouldBeSaved() // shouldBeSaved indicates it isn't a transient message
if !shouldSendAutoGeneratedFR { if !shouldSendAutoGeneratedFR {
let messageSendCopy = copy(messageSend, for: destination, with: seal) let messageSendCopy = copy(messageSend, for: destination, with: seal)
OWSDispatch.sendingQueue().async {
messageSender.sendMessage(messageSendCopy) messageSender.sendMessage(messageSendCopy)
}
} else { } else {
Storage.write { transaction in Storage.write { transaction in
getAutoGeneratedMultiDeviceFRMessageSend(for: destination.hexEncodedPublicKey, in: transaction, seal: seal) getAutoGeneratedMultiDeviceFRMessageSend(for: destination.hexEncodedPublicKey, in: transaction, seal: seal)
@ -141,19 +131,9 @@ public final class MultiDeviceProtocol : NSObject {
}.catch2 { error in }.catch2 { error in
// Proceed even if updating the recipient's device links failed, so that message sending // Proceed even if updating the recipient's device links failed, so that message sending
// is independent of whether the file server is online // 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)
} }
} }
}
@objc(updateDeviceLinksIfNeededForHexEncodedPublicKey:in:) @objc(updateDeviceLinksIfNeededForHexEncodedPublicKey:in:)
public static func updateDeviceLinksIfNeeded(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction) -> AnyPromise { public static func updateDeviceLinksIfNeeded(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction) -> AnyPromise {
@ -165,9 +145,7 @@ public final class MultiDeviceProtocol : NSObject {
@objc(getAutoGeneratedMultiDeviceFRMessageForHexEncodedPublicKey:in:) @objc(getAutoGeneratedMultiDeviceFRMessageForHexEncodedPublicKey:in:)
public static func getAutoGeneratedMultiDeviceFRMessage(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadWriteTransaction) -> FriendRequestMessage { public static func getAutoGeneratedMultiDeviceFRMessage(for hexEncodedPublicKey: String, in transaction: YapDatabaseReadWriteTransaction) -> FriendRequestMessage {
let thread = TSContactThread.getOrCreateThread(withContactId: hexEncodedPublicKey, transaction: transaction) 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") return 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
} }
/// See [Auto-Generated Friend Requests](https://github.com/loki-project/session-protocol-docs/wiki/Auto-Generated-Friend-Requests) for more information. /// See [Auto-Generated Friend Requests](https://github.com/loki-project/session-protocol-docs/wiki/Auto-Generated-Friend-Requests) for more information.

@ -24,6 +24,22 @@ public final class SyncMessagesProtocol : NSObject {
return !UserDefaults.standard[.hasLaunchedOnce] 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:) @objc(syncContactWithHexEncodedPublicKey:in:)
public static func syncContact(_ hexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction) -> AnyPromise { public static func syncContact(_ hexEncodedPublicKey: String, in transaction: YapDatabaseReadTransaction) -> AnyPromise {
let syncManager = SSKEnvironment.shared.syncManager let syncManager = SSKEnvironment.shared.syncManager
@ -97,6 +113,12 @@ public final class SyncMessagesProtocol : NSObject {
return LokiDatabaseUtilities.isUserLinkedDevice(hexEncodedPublicKey, transaction: transaction) return LokiDatabaseUtilities.isUserLinkedDevice(hexEncodedPublicKey, transaction: transaction)
} }
public static func dropFromSyncMessageTimestampCache(_ timestamp: UInt64, for hexEncodedPublicKey: String) {
var timestamps: Set<UInt64> = 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 // TODO: We should probably look at why sync messages are being duplicated rather than doing this
@objc(isDuplicateSyncMessage:fromHexEncodedPublicKey:) @objc(isDuplicateSyncMessage:fromHexEncodedPublicKey:)
public static func isDuplicateSyncMessage(_ protoContent: SSKProtoContent, from hexEncodedPublicKey: String) -> Bool { 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 parser = ContactParser(data: data)
let hexEncodedPublicKeys = parser.parseHexEncodedPublicKeys() let hexEncodedPublicKeys = parser.parseHexEncodedPublicKeys()
let userHexEncodedPublicKey = getUserHexEncodedPublicKey() let userHexEncodedPublicKey = getUserHexEncodedPublicKey()
let linkedDevices = LokiDatabaseUtilities.getLinkedDeviceHexEncodedPublicKeys(for: userHexEncodedPublicKey, in: transaction)
// Try to establish sessions // Try to establish sessions
for hexEncodedPublicKey in hexEncodedPublicKeys { 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(_:) // We don't update the friend request status; that's done in OWSMessageSender.sendMessage(_:)
let friendRequestStatus = storage.getFriendRequestStatus(for: hexEncodedPublicKey, transaction: transaction) let friendRequestStatus = storage.getFriendRequestStatus(for: hexEncodedPublicKey, transaction: transaction)
switch friendRequestStatus { switch friendRequestStatus {
case .none, .requestExpired: 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 // 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) 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 // 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 // AFTER session requests (it's asssumed that the master device first syncs closed
// groups first and contacts after that). // groups first and contacts after that).
@ -228,6 +252,9 @@ public final class SyncMessagesProtocol : NSObject {
for openGroup in groups { for openGroup in groups {
let openGroupManager = LokiPublicChatManager.shared let openGroupManager = LokiPublicChatManager.shared
guard openGroupManager.getChat(server: openGroup.url, channel: openGroup.channel) == nil else { return } 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) openGroupManager.addChat(server: openGroup.url, channel: openGroup.channel)
} }
} }

@ -51,7 +51,7 @@ NS_ASSUME_NONNULL_BEGIN
} }
- (SSKProtoContentBuilder *)prepareCustomContentBuilder:(SignalRecipient *)recipient { - (SSKProtoContentBuilder *)prepareCustomContentBuilder:(SignalRecipient *)recipient {
SSKProtoContentBuilder *builder = SSKProtoContent.builder; SSKProtoContentBuilder *builder = [super prepareCustomContentBuilder:recipient];
PreKeyBundle *bundle = [OWSPrimaryStorage.sharedManager generatePreKeyBundleForContact:recipient.recipientId]; PreKeyBundle *bundle = [OWSPrimaryStorage.sharedManager generatePreKeyBundleForContact:recipient.recipientId];
SSKProtoPrekeyBundleMessageBuilder *preKeyBuilder = [SSKProtoPrekeyBundleMessage builderFromPreKeyBundle:bundle]; SSKProtoPrekeyBundleMessageBuilder *preKeyBuilder = [SSKProtoPrekeyBundleMessage builderFromPreKeyBundle:bundle];

@ -1125,24 +1125,27 @@ NSString *NSStringForOutgoingMessageRecipientState(OWSOutgoingMessageRecipientSt
} }
- (SSKProtoContentBuilder *)prepareCustomContentBuilder:(SignalRecipient *)recipient { - (SSKProtoContentBuilder *)prepareCustomContentBuilder:(SignalRecipient *)recipient {
return SSKProtoContent.builder;
}
- (nullable NSData *)buildPlainTextData:(SignalRecipient *)recipient
{
NSError *error;
SSKProtoDataMessage *_Nullable dataMessage = [self buildDataMessage:recipient.recipientId]; SSKProtoDataMessage *_Nullable dataMessage = [self buildDataMessage:recipient.recipientId];
if (error || !dataMessage) {
OWSFailDebug(@"could not build protobuf: %@", error); if (!dataMessage) {
OWSFailDebug(@"Couldn't build protobuf.");
return nil; return nil;
} }
SSKProtoContentBuilder *contentBuilder = SSKProtoContent.builder;
[contentBuilder setDataMessage:dataMessage];
return contentBuilder;
}
- (nullable NSData *)buildPlainTextData:(SignalRecipient *)recipient
{
SSKProtoContentBuilder *contentBuilder = [self prepareCustomContentBuilder:recipient]; SSKProtoContentBuilder *contentBuilder = [self prepareCustomContentBuilder:recipient];
[contentBuilder setDataMessage:dataMessage]; NSError *error;
NSData *_Nullable contentData = [contentBuilder buildSerializedDataAndReturnError:&error]; NSData *_Nullable contentData = [contentBuilder buildSerializedDataAndReturnError:&error];
if (error || !contentData) { if (error || !contentData) {
OWSFailDebug(@"could not serialize protobuf: %@", error); OWSFailDebug(@"Couldn't serialize protobuf due to error: %@.", error);
return nil; return nil;
} }

@ -928,7 +928,6 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
{ {
OWSAssertDebug(messageSend); OWSAssertDebug(messageSend);
OWSAssertDebug(messageSend.thread || [messageSend.message isKindOfClass:[OWSOutgoingSyncMessage class]]); OWSAssertDebug(messageSend.thread || [messageSend.message isKindOfClass:[OWSOutgoingSyncMessage class]]);
OWSAssertDebug(messageSend.isUDSend);
TSOutgoingMessage *message = messageSend.message; TSOutgoingMessage *message = messageSend.message;
SignalRecipient *recipient = messageSend.recipient; SignalRecipient *recipient = messageSend.recipient;

Loading…
Cancel
Save