mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Merge branch 'dev' into threading
This commit is contained in:
commit
6cc28426d6
22 changed files with 173 additions and 73 deletions
|
@ -320,7 +320,7 @@ SPEC CHECKSUMS:
|
|||
SessionServiceKit: c792d64fc86aaa0f7325beaa36e1a9211a887359
|
||||
SQLCipher: e434ed542b24f38ea7b36468a13f9765e1b5c072
|
||||
SSZipArchive: 62d4947b08730e4cda640473b0066d209ff033c9
|
||||
Starscream: 8aaf1a7feb805c816d0e7d3190ef23856f6665b9
|
||||
Starscream: ef3ece99d765eeccb67de105bfa143f929026cf5
|
||||
SwiftProtobuf: 241400280f912735c1e1b9fe675fdd2c6c4d42e2
|
||||
YapDatabase: b418a4baa6906e8028748938f9159807fd039af4
|
||||
YYImage: 1e1b62a9997399593e4b9c4ecfbbabbf1d3f3b54
|
||||
|
|
2
Pods
2
Pods
|
@ -1 +1 @@
|
|||
Subproject commit 827a67a609b8a4166220440c911796f1a18614ee
|
||||
Subproject commit 6077f71ac9595fa165558a398d296a6c97b5b143
|
|
@ -1394,12 +1394,15 @@ static NSTimeInterval launchStartedAt;
|
|||
NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
|
||||
if (userHexEncodedPublicKey == nil) { return; }
|
||||
self.lokiPoller = [[LKPoller alloc] initOnMessagesReceived:^(NSArray<SSKProtoEnvelope *> *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."];
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
|
||||
public struct LokiPublicChatInfo {
|
||||
public let displayName: String
|
||||
public let profilePictureURL: String?
|
||||
public let memberCount: Int
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<Void> in
|
||||
return threadPromise.then(on: OWSDispatch.sendingQueue()) { thread -> Promise<Void> in
|
||||
let message = messageSend.message
|
||||
let messageSender = SSKEnvironment.shared.messageSender
|
||||
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
|
||||
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.
|
||||
|
|
|
@ -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<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
|
||||
@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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue