diff --git a/Signal/src/Loki/Settings/DeviceLinkingModal.swift b/Signal/src/Loki/Settings/DeviceLinkingModal.swift index 903f7321a..7ce3674e4 100644 --- a/Signal/src/Loki/Settings/DeviceLinkingModal.swift +++ b/Signal/src/Loki/Settings/DeviceLinkingModal.swift @@ -165,6 +165,11 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate { let deviceLink = self.deviceLink! let linkingAuthorizationMessage = DeviceLinkingUtilities.getLinkingAuthorizationMessage(for: deviceLink) ThreadUtil.enqueue(linkingAuthorizationMessage) + SSKEnvironment.shared.messageSender.send(linkingAuthorizationMessage, success: { + let _ = SSKEnvironment.shared.syncManager.syncAllContacts() + }) { _ in + print("[Loki] Failed to send device link authorization message.") + } let session = DeviceLinkingSession.current! session.stopListeningForLinkingRequests() session.markLinkingRequestAsProcessed() @@ -176,9 +181,6 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate { }.catch { error in print("[Loki] Failed to add device link due to error: \(error).") } - Timer.scheduledTimer(withTimeInterval: 8, repeats: false) { _ in - let _ = SSKEnvironment.shared.syncManager.syncAllContacts() - } } func handleDeviceLinkAuthorized(_ deviceLink: DeviceLink) { diff --git a/SignalMessaging/profiles/OWSProfileManager.m b/SignalMessaging/profiles/OWSProfileManager.m index 5cd095e56..907f97370 100644 --- a/SignalMessaging/profiles/OWSProfileManager.m +++ b/SignalMessaging/profiles/OWSProfileManager.m @@ -340,6 +340,11 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error); } } +- (void)updateUserProfileWithDisplayName:(nullable NSString*)displayName profilePictureURL:(nullable NSString*)profilePictureURL transaction:(YapDatabaseReadWriteTransaction *)transaction +{ + [self updateLocalProfileName:displayName avatarImage:nil success:^{ } failure:^{ }]; +} + - (nullable NSString *)profilePictureURL { return self.localUserProfile.avatarUrlPath; diff --git a/SignalServiceKit/src/Loki/API/LokiAPI.swift b/SignalServiceKit/src/Loki/API/LokiAPI.swift index 1ee3c63f3..e192f8915 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI.swift @@ -266,6 +266,8 @@ public final class LokiAPI : NSObject { @objc public static func isDuplicateSyncMessage(_ syncMessage: SSKProtoSyncMessageSent, from hexEncodedPublicKey: String) -> Bool { var timestamps: Set = syncMessageTimestamps[hexEncodedPublicKey] ?? [] + let hasTimestamp = syncMessage.timestamp != 0 + guard hasTimestamp else { return false } let result = timestamps.contains(syncMessage.timestamp) timestamps.insert(syncMessage.timestamp) syncMessageTimestamps[hexEncodedPublicKey] = timestamps diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m index e1a69f985..b2aef164d 100644 --- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m +++ b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m @@ -101,6 +101,20 @@ NS_ASSUME_NONNULL_BEGIN } SSKProtoSyncMessageBuilder *syncMessageBuilder = [SSKProtoSyncMessage builder]; [syncMessageBuilder setContacts:contactsProto]; + + // Loki: Set display name & profile picture + id profileManager = SSKEnvironment.shared.profileManager; + NSString *displayName = profileManager.localProfileName; + NSString *profilePictureURL = profileManager.profilePictureURL; + SSKProtoDataMessageLokiProfileBuilder *profileBuilder = [SSKProtoDataMessageLokiProfile builder]; + [profileBuilder setDisplayName:displayName]; + [profileBuilder setProfilePicture:profilePictureURL ?: @""]; + SSKProtoDataMessageBuilder *messageBuilder = [SSKProtoDataMessage builder]; + [messageBuilder setProfile:[profileBuilder buildAndReturnError:nil]]; + SSKProtoSyncMessageSentBuilder *transcriptBuilder = [SSKProtoSyncMessageSent builder]; + [transcriptBuilder setMessage:[messageBuilder buildAndReturnError:nil]]; + [syncMessageBuilder setSent:[transcriptBuilder buildAndReturnError:nil]]; + return syncMessageBuilder; } diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index 94b318583..4a8eade9b 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -925,56 +925,103 @@ NS_ASSUME_NONNULL_BEGIN } if (syncMessage.sent) { - OWSIncomingSentMessageTranscript *transcript = - [[OWSIncomingSentMessageTranscript alloc] initWithProto:syncMessage.sent transaction:transaction]; - - SSKProtoDataMessage *_Nullable dataMessage = syncMessage.sent.message; - if (!dataMessage) { - OWSFailDebug(@"Missing dataMessage."); - return; - } - NSString *destination = syncMessage.sent.destination; - if (dataMessage && destination.length > 0 && dataMessage.hasProfileKey) { - // If we observe a linked device sending our profile key to another - // user, we can infer that that user belongs in our profile whitelist. - if (dataMessage.group) { - [self.profileManager addGroupIdToProfileWhitelist:dataMessage.group.id]; - } else { - [self.profileManager addUserToProfileWhitelist:destination]; - } + NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; + NSString *masterHexEncodedPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:userHexEncodedPublicKey in:transaction]; + BOOL wasSentByMasterDevice = [masterHexEncodedPublicKey isEqual:envelope.source]; + + // Loki: Try to update using the provided profile + if (wasSentByMasterDevice) { + dispatch_async(dispatch_get_main_queue(), ^{ + SSKProtoDataMessageLokiProfile *profile = syncMessage.sent.message.profile; + NSString *displayName = profile.displayName; + NSString *profilePictureURL = profile.profilePicture; + [self.profileManager updateUserProfileWithDisplayName:displayName profilePictureURL:profilePictureURL transaction:transaction]; + }); } - if ([self isDataMessageGroupAvatarUpdate:syncMessage.sent.message] && !syncMessage.sent.isRecipientUpdate) { - [OWSRecordTranscriptJob - processIncomingSentMessageTranscript:transcript - serverID:0 - attachmentHandler:^(NSArray *attachmentStreams) { - OWSAssertDebug(attachmentStreams.count == 1); - TSAttachmentStream *attachmentStream = attachmentStreams.firstObject; - [self.dbConnection readWriteWithBlock:^( - YapDatabaseReadWriteTransaction *transaction) { - TSGroupThread *_Nullable groupThread = - [TSGroupThread threadWithGroupId:dataMessage.group.id - transaction:transaction]; - if (!groupThread) { - OWSFailDebug(@"ignoring sync group avatar update for unknown group."); - return; - } - - [groupThread updateAvatarWithAttachmentStream:attachmentStream - transaction:transaction]; - }]; - } - transaction:transaction]; + // Loki: Handle contact sync if needed + if (syncMessage.contacts != nil) { + if (wasSentByMasterDevice) { + NSLog(@"[Loki] Received contact sync message."); + NSData *data = syncMessage.contacts.data; + ContactParser *parser = [[ContactParser alloc] initWithData:data]; + NSArray *hexEncodedPublicKeys = [parser parseHexEncodedPublicKeys]; + // Try to establish sessions + for (NSString *hexEncodedPublicKey in hexEncodedPublicKeys) { + TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction]; + LKThreadFriendRequestStatus friendRequestStatus = thread.friendRequestStatus; + switch (friendRequestStatus) { + case LKThreadFriendRequestStatusNone: { + OWSMessageSender *messageSender = SSKEnvironment.shared.messageSender; + OWSMessageSend *automatedFriendRequestMessage = [messageSender getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:hexEncodedPublicKey]; + dispatch_async(OWSDispatch.sendingQueue, ^{ + [messageSender sendMessage:automatedFriendRequestMessage]; + }); + break; + } + case LKThreadFriendRequestStatusRequestReceived: { + [thread saveFriendRequestStatus:LKThreadFriendRequestStatusFriends withTransaction:transaction]; + // The two lines below are equivalent to calling [ThreadUtil enqueueFriendRequestAcceptanceMessageInThread:thread] + LKEphemeralMessage *backgroundMessage = [[LKEphemeralMessage alloc] initInThread:thread]; + [self.messageSenderJobQueue addMessage:backgroundMessage transaction:transaction]; + break; + } + default: break; // Do nothing + } + } + } } else { - [OWSRecordTranscriptJob - processIncomingSentMessageTranscript:transcript - serverID:serverID - attachmentHandler:^(NSArray *attachmentStreams) { - OWSLogDebug(@"successfully fetched transcript attachments: %lu", - (unsigned long)attachmentStreams.count); - } - transaction:transaction]; + OWSIncomingSentMessageTranscript *transcript = + [[OWSIncomingSentMessageTranscript alloc] initWithProto:syncMessage.sent transaction:transaction]; + + SSKProtoDataMessage *_Nullable dataMessage = syncMessage.sent.message; + if (!dataMessage) { + OWSFailDebug(@"Missing dataMessage."); + return; + } + NSString *destination = syncMessage.sent.destination; + if (dataMessage && destination.length > 0 && dataMessage.hasProfileKey) { + // If we observe a linked device sending our profile key to another + // user, we can infer that that user belongs in our profile whitelist. + if (dataMessage.group) { + [self.profileManager addGroupIdToProfileWhitelist:dataMessage.group.id]; + } else { + [self.profileManager addUserToProfileWhitelist:destination]; + } + } + + if ([self isDataMessageGroupAvatarUpdate:syncMessage.sent.message] && !syncMessage.sent.isRecipientUpdate) { + [OWSRecordTranscriptJob + processIncomingSentMessageTranscript:transcript + serverID:0 + attachmentHandler:^(NSArray *attachmentStreams) { + OWSAssertDebug(attachmentStreams.count == 1); + TSAttachmentStream *attachmentStream = attachmentStreams.firstObject; + [self.dbConnection readWriteWithBlock:^( + YapDatabaseReadWriteTransaction *transaction) { + TSGroupThread *_Nullable groupThread = + [TSGroupThread threadWithGroupId:dataMessage.group.id + transaction:transaction]; + if (!groupThread) { + OWSFailDebug(@"ignoring sync group avatar update for unknown group."); + return; + } + + [groupThread updateAvatarWithAttachmentStream:attachmentStream + transaction:transaction]; + }]; + } + transaction:transaction]; + } else { + [OWSRecordTranscriptJob + processIncomingSentMessageTranscript:transcript + serverID:serverID + attachmentHandler:^(NSArray *attachmentStreams) { + OWSLogDebug(@"successfully fetched transcript attachments: %lu", + (unsigned long)attachmentStreams.count); + } + transaction:transaction]; + } } } else if (syncMessage.request) { if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeContacts) { @@ -1023,34 +1070,6 @@ NS_ASSUME_NONNULL_BEGIN } else if (syncMessage.verified) { OWSLogInfo(@"Received verification state for %@", syncMessage.verified.destination); [self.identityManager throws_processIncomingSyncMessage:syncMessage.verified transaction:transaction]; - } else if (syncMessage.contacts) { - NSLog(@"[Loki] Received contact sync message."); - NSData *data = syncMessage.contacts.data; - ContactParser *parser = [[ContactParser alloc] initWithData:data]; - NSArray *hexEncodedPublicKeys = [parser parseHexEncodedPublicKeys]; - // Try to establish sessions - for (NSString *hexEncodedPublicKey in hexEncodedPublicKeys) { - TSContactThread *thread = [TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction]; - LKThreadFriendRequestStatus friendRequestStatus = thread.friendRequestStatus; - switch (friendRequestStatus) { - case LKThreadFriendRequestStatusNone: { - OWSMessageSender *messageSender = SSKEnvironment.shared.messageSender; - OWSMessageSend *automatedFriendRequestMessage = [messageSender getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:hexEncodedPublicKey]; - dispatch_async(OWSDispatch.sendingQueue, ^{ - [messageSender sendMessage:automatedFriendRequestMessage]; - }); - break; - } - case LKThreadFriendRequestStatusRequestReceived: { - [thread saveFriendRequestStatus:LKThreadFriendRequestStatusFriends withTransaction:transaction]; - // The two lines below are equivalent to calling [ThreadUtil enqueueFriendRequestAcceptanceMessageInThread:thread] - LKEphemeralMessage *backgroundMessage = [[LKEphemeralMessage alloc] initInThread:thread]; - [self.messageSenderJobQueue addMessage:backgroundMessage transaction:transaction]; - break; - } - default: break; // Do nothing - } - } } else { OWSLogWarn(@"Ignoring unsupported sync message."); } diff --git a/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h b/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h index 09ae7f244..1e8820789 100644 --- a/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h +++ b/SignalServiceKit/src/Protocols/ProfileManagerProtocol.h @@ -17,6 +17,7 @@ NS_ASSUME_NONNULL_BEGIN - (nullable NSString *)profilePictureURL; - (nullable NSData *)profileKeyDataForRecipientId:(NSString *)recipientId; +- (void)updateUserProfileWithDisplayName:(nullable NSString*)displayName profilePictureURL:(nullable NSString*)profilePictureURL transaction:(YapDatabaseReadWriteTransaction *)transaction; - (void)setProfileKeyData:(NSData *)profileKeyData forRecipientId:(NSString *)recipientId; - (BOOL)isUserInProfileWhitelist:(NSString *)recipientId; diff --git a/SignalServiceKit/src/TestUtils/OWSFakeProfileManager.m b/SignalServiceKit/src/TestUtils/OWSFakeProfileManager.m index ac8bd1446..17cccf777 100644 --- a/SignalServiceKit/src/TestUtils/OWSFakeProfileManager.m +++ b/SignalServiceKit/src/TestUtils/OWSFakeProfileManager.m @@ -48,6 +48,11 @@ NS_ASSUME_NONNULL_BEGIN return _localProfileKey; } +- (void)updateUserProfileWithDisplayName:(nullable NSString*)displayName profilePictureURL:(nullable NSString*)profilePictureURL transaction:(YapDatabaseReadWriteTransaction *)transaction +{ + // Do nothing +} + - (void)setProfileKeyData:(NSData *)profileKey forRecipientId:(NSString *)recipientId { OWSAES256Key *_Nullable key = [OWSAES256Key keyWithData:profileKey];