Merge branch 'release/3.8.0'

This commit is contained in:
Matthew Chen 2020-04-15 17:41:19 -03:00
commit aa53773e84
33 changed files with 957 additions and 252 deletions

View File

@ -19,7 +19,7 @@
<key>CFBundleShortVersionString</key>
<string>3.8.0</string>
<key>CFBundleVersion</key>
<string>3.8.0.15</string>
<string>3.8.0.19</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>

2
Pods

@ -1 +1 @@
Subproject commit 311cd6086281c648c095adfc858d515a03bd8131
Subproject commit 21e3b748d44b7afe23928e11f3e605f0bed7fe51

View File

@ -132,7 +132,6 @@
346129AF1FD1F5D900532771 /* SystemContactsFetcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346129AE1FD1F5D900532771 /* SystemContactsFetcher.swift */; };
346129B41FD1F7E800532771 /* OWSProfileManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129B11FD1F7E800532771 /* OWSProfileManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
346129B51FD1F7E800532771 /* OWSProfileManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129B21FD1F7E800532771 /* OWSProfileManager.m */; };
346129B61FD1F7E800532771 /* ProfileFetcherJob.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346129B31FD1F7E800532771 /* ProfileFetcherJob.swift */; };
346129D51FD20ADC00532771 /* UIViewController+OWS.m in Sources */ = {isa = PBXBuildFile; fileRef = 346129D31FD20ADB00532771 /* UIViewController+OWS.m */; };
346129D61FD20ADC00532771 /* UIViewController+OWS.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129D41FD20ADC00532771 /* UIViewController+OWS.h */; settings = {ATTRIBUTES = (Public, ); }; };
346129E21FD5C0BE00532771 /* VersionMigrations.h in Headers */ = {isa = PBXBuildFile; fileRef = 346129E01FD5C0BE00532771 /* VersionMigrations.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -177,7 +176,7 @@
346E9D5421B040B700562252 /* RegistrationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346E9D5321B040B600562252 /* RegistrationController.swift */; };
3470249C238367EA0078D72C /* TextApprovalViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3470249B238367E90078D72C /* TextApprovalViewController.swift */; };
3470249E2385B6360078D72C /* OWSProfileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3470249D2385B6360078D72C /* OWSProfileManager.swift */; };
347024A0238C85850078D72C /* VersionedProfiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3470249F238C85850078D72C /* VersionedProfiles.swift */; };
347024A0238C85850078D72C /* VersionedProfilesImpl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3470249F238C85850078D72C /* VersionedProfilesImpl.swift */; };
347137FC22A1BCE800F43A63 /* OWSMessageViewOnceView.m in Sources */ = {isa = PBXBuildFile; fileRef = 347137FB22A1BCE800F43A63 /* OWSMessageViewOnceView.m */; };
347191F923F457BD003A3106 /* GroupsV2AvatarDownloadOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 347191F823F457BD003A3106 /* GroupsV2AvatarDownloadOperation.swift */; };
3476236224081F8900D4FB6F /* GroupManager+SignalMessaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3476236124081F8900D4FB6F /* GroupManager+SignalMessaging.swift */; };
@ -1006,7 +1005,6 @@
346129AE1FD1F5D900532771 /* SystemContactsFetcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SystemContactsFetcher.swift; sourceTree = "<group>"; };
346129B11FD1F7E800532771 /* OWSProfileManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSProfileManager.h; sourceTree = "<group>"; };
346129B21FD1F7E800532771 /* OWSProfileManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSProfileManager.m; sourceTree = "<group>"; };
346129B31FD1F7E800532771 /* ProfileFetcherJob.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileFetcherJob.swift; sourceTree = "<group>"; };
346129BD1FD2068600532771 /* ThreadUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ThreadUtil.h; sourceTree = "<group>"; };
346129BE1FD2068600532771 /* ThreadUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadUtil.m; sourceTree = "<group>"; };
346129D31FD20ADB00532771 /* UIViewController+OWS.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+OWS.m"; sourceTree = "<group>"; };
@ -1056,7 +1054,7 @@
346E9D5321B040B600562252 /* RegistrationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegistrationController.swift; sourceTree = "<group>"; };
3470249B238367E90078D72C /* TextApprovalViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextApprovalViewController.swift; sourceTree = "<group>"; };
3470249D2385B6360078D72C /* OWSProfileManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OWSProfileManager.swift; sourceTree = "<group>"; };
3470249F238C85850078D72C /* VersionedProfiles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionedProfiles.swift; sourceTree = "<group>"; };
3470249F238C85850078D72C /* VersionedProfilesImpl.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VersionedProfilesImpl.swift; sourceTree = "<group>"; };
347137FA22A1BCE800F43A63 /* OWSMessageViewOnceView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageViewOnceView.h; sourceTree = "<group>"; };
347137FB22A1BCE800F43A63 /* OWSMessageViewOnceView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSMessageViewOnceView.m; sourceTree = "<group>"; };
347191F823F457BD003A3106 /* GroupsV2AvatarDownloadOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupsV2AvatarDownloadOperation.swift; sourceTree = "<group>"; };
@ -2232,8 +2230,7 @@
346129B11FD1F7E800532771 /* OWSProfileManager.h */,
346129B21FD1F7E800532771 /* OWSProfileManager.m */,
3470249D2385B6360078D72C /* OWSProfileManager.swift */,
346129B31FD1F7E800532771 /* ProfileFetcherJob.swift */,
3470249F238C85850078D72C /* VersionedProfiles.swift */,
3470249F238C85850078D72C /* VersionedProfilesImpl.swift */,
);
path = profiles;
sourceTree = "<group>";
@ -2747,12 +2744,12 @@
454A96571FD600B4008D2A0E /* attachments */ = {
isa = PBXGroup;
children = (
8809CE8422F8DB2D00D38867 /* AttachmentKeyboard.swift */,
34B3F8391E8DF1700035BE1A /* AttachmentSharing.h */,
34B3F83A1E8DF1700035BE1A /* AttachmentSharing.m */,
34D913491F62D4A500722898 /* SignalAttachment.swift */,
45BC829C1FD9C4B400011CF3 /* ShareViewDelegate.swift */,
45F59A092029140500E8D2B0 /* OWSVideoPlayer.swift */,
8809CE8422F8DB2D00D38867 /* AttachmentKeyboard.swift */,
45BC829C1FD9C4B400011CF3 /* ShareViewDelegate.swift */,
34D913491F62D4A500722898 /* SignalAttachment.swift */,
);
path = attachments;
sourceTree = "<group>";
@ -2762,9 +2759,9 @@
children = (
451166BF1FD86B98000739BA /* AccountManager.swift */,
45DF5DF11DDB843F00C936C7 /* CompareSafetyNumbersActivity.swift */,
4CB5F26820F7D060004D1B42 /* MessageActions.swift */,
458E38351D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.h */,
458E38361D668EBF0094BD24 /* OWSDeviceProvisioningURLParser.m */,
4CB5F26820F7D060004D1B42 /* MessageActions.swift */,
4C5250D121E7BD7D00CE3D95 /* PhoneNumberValidator.swift */,
4C0C36F7226647FE0083F19A /* ThreadMapping.swift */,
);
@ -4388,7 +4385,6 @@
34AC0A10211B39EA00997B47 /* TappableView.swift in Sources */,
346129F91FD5F31400532771 /* OWS104CreateRecipientIdentities.m in Sources */,
34C2EEAC2270A69400BCA1D0 /* CircleView.swift in Sources */,
346129B61FD1F7E800532771 /* ProfileFetcherJob.swift in Sources */,
34AC09E9211B39B100997B47 /* OWSTableViewController.m in Sources */,
340872CE2239596100CB25B0 /* AttachmentApprovalInputAccessoryView.swift in Sources */,
346129F51FD5F31400532771 /* OWS102MoveLoggingPreferenceToUserDefaults.m in Sources */,
@ -4460,7 +4456,7 @@
34C529092361D3540009DB2A /* ApprovalFooterView.swift in Sources */,
451F8A471FD715BA005CB9DA /* OWSAvatarBuilder.m in Sources */,
349ED992221EE80D008045B0 /* AppPreferences.swift in Sources */,
347024A0238C85850078D72C /* VersionedProfiles.swift in Sources */,
347024A0238C85850078D72C /* VersionedProfilesImpl.swift in Sources */,
4C9C50FE22F36FA50054A33F /* OutboundMessage+OWS.swift in Sources */,
34480B591FD0A7A400BC14EF /* OWSScrubbingLogFormatter.m in Sources */,
342BF24D227A03F8005CEAA9 /* StickerHorizontalListView.swift in Sources */,

View File

@ -45,7 +45,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>3.8.0.15</string>
<string>3.8.0.19</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LOGS_EMAIL</key>

View File

@ -362,6 +362,11 @@ typedef enum : NSUInteger {
return SSKEnvironment.shared.syncManager;
}
- (BulkProfileFetch *)bulkProfileFetch
{
return SSKEnvironment.shared.bulkProfileFetch;
}
#pragma mark -
- (void)addNotificationListeners
@ -1202,7 +1207,7 @@ typedef enum : NSUInteger {
// recover status bar when returning from PhotoPicker, which is dark (uses light status bar)
[self setNeedsStatusBarAppearanceUpdate];
[ProfileFetcherJob fetchAndUpdateProfilesWithThread:self.thread];
[self.bulkProfileFetch fetchAndUpdateProfilesWithThread:self.thread];
[self markVisibleMessagesAsRead];
[self startReadTimer];
[self updateNavigationBarSubtitleLabel];

View File

@ -127,6 +127,16 @@ public class BaseGroupMemberViewController: OWSViewController {
autoPinView(toBottomOfViewControllerOrKeyboard: recipientPicker.view, avoidNotch: false)
updateMemberCount()
tryToFillInMissingUuuids()
}
private func tryToFillInMissingUuuids() {
let addresses = recipientPicker.contactsViewHelper.signalAccounts.map { $0.recipientAddress }
firstly {
GroupManager.tryToFillInMissingUuuids(for: addresses, isBlocking: false)
}.catch { error in
owsFailDebug("Error: \(error)")
}.retainUntilComplete()
}
@objc
@ -395,9 +405,12 @@ extension BaseGroupMemberViewController: RecipientPickerDelegate {
RemoteConfig.groupsV2IncomingMessages else {
return
}
// GroupsV2 TODO: We could debounce this effort.
if !doesRecipientSupportGroupsV2(recipient) {
tryToEnableGroupsV2ForAddress(address, ignoreErrors: true).retainUntilComplete()
DispatchQueue.global().async {
if !self.doesRecipientSupportGroupsV2(recipient) {
self.tryToEnableGroupsV2ForAddress(address,
isBlocking: false,
ignoreErrors: true).retainUntilComplete()
}
}
}
@ -420,7 +433,9 @@ extension BaseGroupMemberViewController: RecipientPickerDelegate {
return AnyPromise(Promise.value(()))
}
let ignoreErrors = !groupMemberViewDelegate.groupMemberViewIsGroupsV2Required()
return AnyPromise(tryToEnableGroupsV2ForAddress(address, ignoreErrors: ignoreErrors))
return AnyPromise(tryToEnableGroupsV2ForAddress(address,
isBlocking: true,
ignoreErrors: ignoreErrors))
}
func recipientPicker(_ recipientPickerViewController: RecipientPickerViewController,
@ -434,19 +449,31 @@ extension BaseGroupMemberViewController: RecipientPickerDelegate {
owsFailDebug("Invalid recipient.")
return false
}
return doesRecipientSupportGroupsV2(address)
}
private func doesRecipientSupportGroupsV2(_ address: SignalServiceAddress) -> Bool {
return databaseStorage.read { transaction in
return GroupManager.doesUserSupportGroupsV2(address: address, transaction: transaction)
}
}
func tryToEnableGroupsV2ForAddress(_ address: SignalServiceAddress,
isBlocking: Bool,
ignoreErrors: Bool) -> Promise<Void> {
return firstly { () -> Promise<Void> in
return GroupManager.tryToEnableGroupsV2(for: [address],
isBlocking: isBlocking,
ignoreErrors: ignoreErrors)
}.done { [weak self] _ in
// Reload view content.
self?.recipientPicker.reloadContent()
}.done(on: .global() ) { [weak self] _ in
// If we succeeded in enable groups v2 for this address,
// reload the recipient picker to reflect that.
if self?.doesRecipientSupportGroupsV2(address) ?? false {
DispatchQueue.main.async {
// Reload view content.
self?.recipientPicker.reloadContent()
}
}
}
}

View File

@ -445,6 +445,7 @@ NS_ASSUME_NONNULL_BEGIN
- (AnyPromise *)recipientPicker:(RecipientPickerViewController *)recipientPickerViewController
prepareToSelectRecipient:(PickedRecipient *)recipient
{
OWSFailDebug(@"This method should not called.");
return [AnyPromise promiseWithValue:@(1)];
}

View File

@ -88,6 +88,7 @@ extension AddToBlockListViewController: RecipientPickerDelegate {
func recipientPicker(_ recipientPickerViewController: RecipientPickerViewController,
prepareToSelectRecipient recipient: PickedRecipient) -> AnyPromise {
owsFailDebug("This method should not called.")
return AnyPromise(Promise.value(()))
}

View File

@ -86,6 +86,7 @@ extension ComposeViewController: RecipientPickerDelegate {
func recipientPicker(_ recipientPickerViewController: RecipientPickerViewController,
prepareToSelectRecipient recipient: PickedRecipient) -> AnyPromise {
owsFailDebug("This method should not called.")
return AnyPromise(Promise.value(()))
}

View File

@ -521,6 +521,7 @@ NS_ASSUME_NONNULL_BEGIN
- (AnyPromise *)recipientPicker:(RecipientPickerViewController *)recipientPickerViewController
prepareToSelectRecipient:(PickedRecipient *)recipient
{
OWSFailDebug(@"This method should not called.");
return [AnyPromise promiseWithValue:@(1)];
}

View File

@ -58,7 +58,7 @@ public extension UIDevice {
return false
case 1792:
// iPhone XR
return false
return true
case 1920, 2208:
// iPhone 6+/6S+/7+/8+//
return true

View File

@ -113,6 +113,9 @@ NS_ASSUME_NONNULL_BEGIN
OWSWindowManager *windowManager = [[OWSWindowManager alloc] initDefault];
MessageProcessing *messageProcessing = [MessageProcessing new];
MessageFetcherJob *messageFetcherJob = [MessageFetcherJob new];
BulkProfileFetch *bulkProfileFetch = [BulkProfileFetch new];
BulkUUIDLookup *bulkUUIDLookup = [BulkUUIDLookup new];
id<VersionedProfiles> versionedProfiles = [VersionedProfilesImpl new];
[Environment setShared:[[Environment alloc] initWithAudioSession:audioSession
incomingContactSyncJobQueue:incomingContactSyncJobQueue
@ -167,7 +170,10 @@ NS_ASSUME_NONNULL_BEGIN
groupsV2:groupsV2
groupV2Updates:groupV2Updates
messageProcessing:messageProcessing
messageFetcherJob:messageFetcherJob]];
messageFetcherJob:messageFetcherJob
bulkProfileFetch:bulkProfileFetch
bulkUUIDLookup:bulkUUIDLookup
versionedProfiles:versionedProfiles]];
appSpecificSingletonBlock();

View File

@ -299,7 +299,7 @@ extension YDBToGRDBMigration {
GRDBKeyValueStoreMigrator<Any>(label: "OWSSyncManager", keyStore: OWSSyncManager.keyValueStore(), ydbTransaction: ydbTransaction),
GRDBKeyValueStoreMigrator<Any>(label: "OWSOutgoingReceiptManager.deliveryReceiptStore", keyStore: OWSOutgoingReceiptManager.deliveryReceiptStore(), ydbTransaction: ydbTransaction),
GRDBKeyValueStoreMigrator<Any>(label: "OWSOutgoingReceiptManager.readReceiptStore", keyStore: OWSOutgoingReceiptManager.readReceiptStore(), ydbTransaction: ydbTransaction),
GRDBKeyValueStoreMigrator<Any>(label: "VersionedProfiles.credentialStore", keyStore: VersionedProfiles.credentialStore, ydbTransaction: ydbTransaction),
GRDBKeyValueStoreMigrator<Any>(label: "VersionedProfiles.credentialStore", keyStore: VersionedProfilesImpl.credentialStore, ydbTransaction: ydbTransaction),
GRDBKeyValueStoreMigrator<Any>(label: "KeyBackupService.keyValueStore", keyStore: KeyBackupService.keyValueStore, ydbTransaction: ydbTransaction),
GRDBKeyValueStoreMigrator<Any>(label: "KeyBackupService.tokenStore", keyStore: KeyBackupService.tokenStore, ydbTransaction: ydbTransaction),
GRDBKeyValueStoreMigrator<Any>(label: "StorageServiceOperation", keyStore: StorageServiceOperation.keyValueStore, ydbTransaction: ydbTransaction)

View File

@ -45,8 +45,8 @@ public class GroupsV2Impl: NSObject, GroupsV2Swift {
return SSKEnvironment.shared.contactsUpdater
}
private var reachabilityManager: SSKReachabilityManager {
return SSKEnvironment.shared.reachabilityManager
private var versionedProfiles: VersionedProfilesImpl {
return SSKEnvironment.shared.versionedProfiles as! VersionedProfilesImpl
}
// MARK: -
@ -114,7 +114,7 @@ public class GroupsV2Impl: NSObject, GroupsV2Swift {
databaseStorage.write { transaction in
self.clearTemporalCredentials(transaction: transaction)
VersionedProfiles.clearProfileKeyCredentials(transaction: transaction)
self.versionedProfiles.clearProfileKeyCredentials(transaction: transaction)
self.serviceStore.setString(serverPublicParamsBase64, key: lastServerPublicParamsKey, transaction: transaction)
self.serviceStore.setInt(zkgroupVersionCounter, key: lastZKgroupVersionCounterKey, transaction: transaction)
}
@ -933,8 +933,8 @@ public class GroupsV2Impl: NSObject, GroupsV2Swift {
for uuid in Set(uuids) {
do {
let address = SignalServiceAddress(uuid: uuid)
if let credential = try VersionedProfiles.profileKeyCredential(for: address,
transaction: transaction) {
if let credential = try self.versionedProfiles.profileKeyCredential(for: address,
transaction: transaction) {
credentialMap[uuid] = credential
continue
}
@ -977,9 +977,9 @@ public class GroupsV2Impl: NSObject, GroupsV2Swift {
try self.databaseStorage.read { transaction in
for uuid in uuids {
let address = SignalServiceAddress(uuid: uuid)
guard let credential = try VersionedProfiles.profileKeyCredential(for: address,
transaction: transaction) else {
throw OWSAssertionError("Could load credential.")
guard let credential = try self.versionedProfiles.profileKeyCredential(for: address,
transaction: transaction) else {
throw OWSAssertionError("Could load credential.")
}
credentialMap[uuid] = credential
}
@ -992,8 +992,8 @@ public class GroupsV2Impl: NSObject, GroupsV2Swift {
public func hasProfileKeyCredential(for address: SignalServiceAddress,
transaction: SDSAnyReadTransaction) -> Bool {
do {
return try VersionedProfiles.profileKeyCredential(for: address,
transaction: transaction) != nil
return try self.versionedProfiles.profileKeyCredential(for: address,
transaction: transaction) != nil
} catch {
owsFailDebug("Error: \(error)")
return false
@ -1049,25 +1049,6 @@ public class GroupsV2Impl: NSObject, GroupsV2Swift {
return when(fulfilled: promises).asVoid()
}
// MARK: - UUIDs
public func tryToEnsureUuidsForGroupMembers(for addresses: [SignalServiceAddress]) -> Promise<Void> {
guard FeatureFlags.useOnlyModernContactDiscovery else {
// Can't fill in UUIDs using legacy contact intersections.
return Promise.value(())
}
let phoneNumbersWithoutUuids = addresses.filter { $0.uuid == nil }.compactMap { $0.phoneNumber }
guard phoneNumbersWithoutUuids.count > 0 else {
return Promise.value(())
}
return firstly {
contactsUpdater.lookupIdentifiersPromise(phoneNumbers:
phoneNumbersWithoutUuids)
}.asVoid()
}
// MARK: - Auth Credentials
private let authCredentialStore = SDSKeyValueStore(collection: "GroupsV2Impl.authCredentialStoreStore")

View File

@ -140,12 +140,6 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
- (nullable NSString *)usernameForAddress:(SignalServiceAddress *)address
transaction:(SDSAnyReadTransaction *)transaction;
- (void)updateProfileForAddress:(SignalServiceAddress *)address
profileNameEncrypted:(nullable NSData *)profileNameEncrypted
username:(nullable NSString *)username
isUuidCapable:(BOOL)isUuidCapable
avatarUrlPath:(nullable NSString *)avatarUrlPath;
#pragma mark - Clean Up
+ (NSSet<NSString *> *)allProfileAvatarFilePathsWithTransaction:(SDSAnyReadTransaction *)transaction;

View File

@ -77,6 +77,11 @@ const NSString *kNSNotificationKey_WasLocallyInitiated = @"kNSNotificationKey_Wa
return SSKEnvironment.shared.storageServiceManager;
}
- (id<VersionedProfiles>)versionedProfiles
{
return SSKEnvironment.shared.versionedProfiles;
}
#pragma mark -
@synthesize localUserProfile = _localUserProfile;
@ -449,6 +454,15 @@ const NSString *kNSNotificationKey_WasLocallyInitiated = @"kNSNotificationKey_Wa
return [ProfileFetcherJob fetchAndUpdateProfilePromiseObjcWithAddress:address mainAppOnly:NO ignoreThrottling:YES];
}
- (AnyPromise *)updateProfileForAddressPromise:(SignalServiceAddress *)address
mainAppOnly:(BOOL)mainAppOnly
ignoreThrottling:(BOOL)ignoreThrottling
{
return [ProfileFetcherJob fetchAndUpdateProfilePromiseObjcWithAddress:address
mainAppOnly:mainAppOnly
ignoreThrottling:ignoreThrottling];
}
- (void)fetchAndUpdateProfileForUsername:(NSString *)username
success:(void (^)(SignalServiceAddress *))success
notFound:(void (^)(void))notFound
@ -656,7 +670,7 @@ const NSString *kNSNotificationKey_WasLocallyInitiated = @"kNSNotificationKey_Wa
// Whenever a user's profile key changes, we need to fetch a new
// profile key credential for them.
[VersionedProfiles clearProfileKeyCredentialForAddress:localAddress transaction:transaction];
[self.versionedProfiles clearProfileKeyCredentialForAddress:localAddress transaction:transaction];
// We schedule the updates here but process them below using processProfileKeyUpdates.
// It's more efficient to process them after the intermediary steps are done.
@ -1382,7 +1396,7 @@ const NSString *kNSNotificationKey_WasLocallyInitiated = @"kNSNotificationKey_Wa
// Whenever a user's profile key changes, we need to fetch a new
// profile key credential for them.
[VersionedProfiles clearProfileKeyCredentialForAddress:address transaction:transaction];
[self.versionedProfiles clearProfileKeyCredentialForAddress:address transaction:transaction];
[userProfile clearWithProfileKey:profileKey
wasLocallyInitiated:wasLocallyInitiated

View File

@ -8,6 +8,14 @@ import SignalServiceKit
public extension OWSProfileManager {
// MARK: - Dependencies
private class var versionedProfiles: VersionedProfilesSwift {
return SSKEnvironment.shared.versionedProfiles as! VersionedProfilesSwift
}
// MARK: -
// The main entry point for updating the local profile. It will:
//
// * Update local state optimistically.
@ -22,15 +30,15 @@ public extension OWSProfileManager {
return DispatchQueue.global().async(.promise) {
return enqueueProfileUpdate(profileGivenName: profileGivenName, profileFamilyName: profileFamilyName, profileAvatarData: profileAvatarData)
}.then { update in
return self.attemptToUpdateProfileOnService(update: update)
}.then { (_) throws -> Promise<Void> in
guard let localAddress = TSAccountManager.sharedInstance().localAddress else {
throw OWSAssertionError("missing local address")
}
return ProfileFetcherJob.fetchAndUpdateProfilePromise(address: localAddress, mainAppOnly: false, ignoreThrottling: true, fetchType: .default).asVoid()
}.done(on: .global()) { () -> Void in
Logger.verbose("Profile update did complete.")
}.then { update in
return self.attemptToUpdateProfileOnService(update: update)
}.then { (_) throws -> Promise<Void> in
guard let localAddress = TSAccountManager.sharedInstance().localAddress else {
throw OWSAssertionError("missing local address")
}
return ProfileFetcherJob.fetchAndUpdateProfilePromise(address: localAddress, mainAppOnly: false, ignoreThrottling: true, fetchType: .default).asVoid()
}.done(on: .global()) { () -> Void in
Logger.verbose("Profile update did complete.")
}
}
@ -109,13 +117,14 @@ extension OWSProfileManager {
guard let update = pendingUpdate else {
return
}
attemptToUpdateProfileOnService(update: update,
retryDelay: retryDelay)
.done { _ in
Logger.info("Update succeeded.")
}.catch { error in
Logger.error("Update failed: \(error)")
}.retainUntilComplete()
firstly {
attemptToUpdateProfileOnService(update: update,
retryDelay: retryDelay)
}.done { _ in
Logger.info("Update succeeded.")
}.catch { error in
Logger.error("Update failed: \(error)")
}.retainUntilComplete()
}
fileprivate class func attemptToUpdateProfileOnService(update: PendingProfileUpdate,
@ -133,60 +142,61 @@ extension OWSProfileManager {
let attempt = ProfileUpdateAttempt(update: update,
userProfile: userProfile)
let promise = writeProfileAvatarToDisk(attempt: attempt)
.then(on: DispatchQueue.global()) { () -> Promise<Void> in
// Optimistically update local profile state.
databaseStorage.write { transaction in
self.updateLocalProfile(with: attempt, transaction: transaction)
}
if FeatureFlags.versionedProfiledUpdate {
return updateProfileOnServiceVersioned(attempt: attempt)
} else {
return updateProfileOnServiceUnversioned(attempt: attempt)
}
}.done(on: DispatchQueue.global()) { _ in
_ = self.databaseStorage.write { (transaction: SDSAnyWriteTransaction) -> Void in
guard tryToDequeueProfileUpdate(update: attempt.update, transaction: transaction) else {
return
}
if attempt.update.profileAvatarData != nil {
if attempt.avatarFilename == nil {
owsFailDebug("Missing avatarFilename.")
}
if attempt.avatarUrlPath == nil {
owsFailDebug("Missing avatarUrlPath.")
}
}
self.updateLocalProfile(with: attempt, transaction: transaction)
}
self.attemptDidComplete(retryDelay: retryDelay, didSucceed: true)
}.recover(on: .global()) { error in
// We retry network errors forever (with exponential backoff).
// Other errors cause us to give up immediately.
// Note that we only ever retry the latest profile update.
if IsNetworkConnectivityFailure(error) {
Logger.warn("Retrying after error: \(error)")
} else {
owsFailDebug("Error: \(error)")
// Dequeue to avoid getting stuck in retry loop.
self.databaseStorage.write { transaction in
_ = tryToDequeueProfileUpdate(update: attempt.update, transaction: transaction)
}
}
self.attemptDidComplete(retryDelay: retryDelay, didSucceed: false)
// We don't actually want to recover; in this block we
// handle the business logic consequences of the error,
// but we re-throw so that the UI can distinguish
// success and failure.
throw error
let promise = firstly {
writeProfileAvatarToDisk(attempt: attempt)
}.then(on: DispatchQueue.global()) { () -> Promise<Void> in
// Optimistically update local profile state.
databaseStorage.write { transaction in
self.updateLocalProfile(with: attempt, transaction: transaction)
}
if FeatureFlags.versionedProfiledUpdate {
return updateProfileOnServiceVersioned(attempt: attempt)
} else {
return updateProfileOnServiceUnversioned(attempt: attempt)
}
}.done(on: DispatchQueue.global()) { _ in
_ = self.databaseStorage.write { (transaction: SDSAnyWriteTransaction) -> Void in
guard tryToDequeueProfileUpdate(update: attempt.update, transaction: transaction) else {
return
}
if attempt.update.profileAvatarData != nil {
if attempt.avatarFilename == nil {
owsFailDebug("Missing avatarFilename.")
}
if attempt.avatarUrlPath == nil {
owsFailDebug("Missing avatarUrlPath.")
}
}
self.updateLocalProfile(with: attempt, transaction: transaction)
}
self.attemptDidComplete(retryDelay: retryDelay, didSucceed: true)
}.recover(on: .global()) { error in
// We retry network errors forever (with exponential backoff).
// Other errors cause us to give up immediately.
// Note that we only ever retry the latest profile update.
if IsNetworkConnectivityFailure(error) {
Logger.warn("Retrying after error: \(error)")
} else {
owsFailDebug("Error: \(error)")
// Dequeue to avoid getting stuck in retry loop.
self.databaseStorage.write { transaction in
_ = tryToDequeueProfileUpdate(update: attempt.update, transaction: transaction)
}
}
self.attemptDidComplete(retryDelay: retryDelay, didSucceed: false)
// We don't actually want to recover; in this block we
// handle the business logic consequences of the error,
// but we re-throw so that the UI can distinguish
// success and failure.
throw error
}
return promise
}
@ -277,9 +287,9 @@ extension OWSProfileManager {
}
private class func updateProfileOnServiceVersioned(attempt: ProfileUpdateAttempt) -> Promise<Void> {
return VersionedProfiles.updateProfilePromise(profileGivenName: attempt.update.profileGivenName,
profileFamilyName: attempt.update.profileFamilyName,
profileAvatarData: attempt.update.profileAvatarData)
return self.versionedProfiles.updateProfilePromise(profileGivenName: attempt.update.profileGivenName,
profileFamilyName: attempt.update.profileFamilyName,
profileAvatarData: attempt.update.profileAvatarData)
.map(on: .global()) { versionedUpdate in
attempt.avatarUrlPath = versionedUpdate.avatarUrlPath
}
@ -290,11 +300,11 @@ extension OWSProfileManager {
Logger.verbose("profile givenName: \(String(describing: attempt.update.profileGivenName)), familyName: \(String(describing: attempt.update.profileFamilyName)), avatarFilename: \(String(describing: attempt.avatarFilename))")
attempt.userProfile.updateWith(givenName: attempt.update.profileGivenName,
familyName: attempt.update.profileFamilyName,
avatarUrlPath: attempt.avatarUrlPath,
avatarFileName: attempt.avatarFilename,
transaction: transaction,
completion: nil)
familyName: attempt.update.profileFamilyName,
avatarUrlPath: attempt.avatarUrlPath,
avatarFileName: attempt.avatarFilename,
transaction: transaction,
completion: nil)
}
// MARK: - Update Queue
@ -383,8 +393,8 @@ class PendingProfileUpdate: NSObject, NSCoding {
public required init?(coder aDecoder: NSCoder) {
guard let idString = aDecoder.decodeObject(forKey: "id") as? String,
let id = UUID(uuidString: idString) else {
owsFailDebug("Missing id")
return nil
owsFailDebug("Missing id")
return nil
}
self.id = id
self.profileGivenName = aDecoder.decodeObject(forKey: "profileGivenName") as? String
@ -442,8 +452,8 @@ public extension OWSProfileManager {
// Given name is required
guard let givenNameData = nameSegments[safe: 0],
let givenName = String(data: givenNameData, encoding: .utf8), !givenName.isEmpty else {
owsFailDebug("unexpectedly missing first name")
return nil
owsFailDebug("unexpectedly missing first name")
return nil
}
// Family name is optional

View File

@ -8,44 +8,37 @@ import SignalServiceKit
import SignalMetadataKit
import ZKGroup
@objc
public class VersionedProfileUpdate: NSObject {
// This will only be set if there is a profile avatar.
@objc
public let avatarUrlPath: String?
public class VersionedProfileRequestImpl: NSObject, VersionedProfileRequest {
public let request: TSRequest
public let requestContext: ProfileKeyCredentialRequestContext?
required init(avatarUrlPath: String? = nil) {
self.avatarUrlPath = avatarUrlPath
public required init(request: TSRequest,
requestContext: ProfileKeyCredentialRequestContext?) {
self.request = request
self.requestContext = requestContext
}
}
// MARK: -
public struct VersionedProfileRequest {
public let request: TSRequest
public let requestContext: ProfileKeyCredentialRequestContext?
}
// MARK: -
@objc
public class VersionedProfiles: NSObject {
public class VersionedProfilesImpl: NSObject, VersionedProfilesSwift {
// MARK: - Dependencies
private class var profileManager: OWSProfileManager {
private var profileManager: OWSProfileManager {
return OWSProfileManager.shared()
}
private class var networkManager: TSNetworkManager {
private var networkManager: TSNetworkManager {
return SSKEnvironment.shared.networkManager
}
private class var databaseStorage: SDSDatabaseStorage {
private var databaseStorage: SDSDatabaseStorage {
return SDSDatabaseStorage.shared
}
private class var tsAccountManager: TSAccountManager {
private var tsAccountManager: TSAccountManager {
return .sharedInstance()
}
@ -53,46 +46,45 @@ public class VersionedProfiles: NSObject {
public static let credentialStore = SDSKeyValueStore(collection: "VersionedProfiles.credentialStore")
// Never instantiate this class.
private override init() {}
// MARK: -
public class func clientZkProfileOperations() throws -> ClientZkProfileOperations {
public func clientZkProfileOperations() throws -> ClientZkProfileOperations {
return ClientZkProfileOperations(serverPublicParams: try GroupsV2Protos.serverPublicParams())
}
// MARK: - Update
@objc
public class func updateProfileOnService(profileGivenName: String?,
profileFamilyName: String?,
profileAvatarData: Data?) {
updateProfilePromise(profileGivenName: profileGivenName,
profileFamilyName: profileFamilyName,
profileAvatarData: profileAvatarData)
.done { _ in
Logger.verbose("success")
public func updateProfileOnService(profileGivenName: String?,
profileFamilyName: String?,
profileAvatarData: Data?) {
firstly {
updateProfilePromise(profileGivenName: profileGivenName,
profileFamilyName: profileFamilyName,
profileAvatarData: profileAvatarData)
}.done { _ in
Logger.verbose("success")
// TODO: This is temporary for testing.
let localAddress = TSAccountManager.sharedInstance().localAddress!
// TODO: This is temporary for testing.
let localAddress = TSAccountManager.sharedInstance().localAddress!
firstly {
ProfileFetcherJob.fetchAndUpdateProfilePromise(address: localAddress,
mainAppOnly: false,
ignoreThrottling: true,
fetchType: .versioned)
.done { _ in
Logger.verbose("success")
}.catch { error in
owsFailDebug("error: \(error)")
}.retainUntilComplete()
}.done { _ in
Logger.verbose("success")
}.catch { error in
owsFailDebug("error: \(error)")
}.retainUntilComplete()
}.catch { error in
owsFailDebug("error: \(error)")
}.retainUntilComplete()
}
public class func updateProfilePromise(profileGivenName: String?,
profileFamilyName: String?,
profileAvatarData: Data?) -> Promise<VersionedProfileUpdate> {
public func updateProfilePromise(profileGivenName: String?,
profileFamilyName: String?,
profileAvatarData: Data?) -> Promise<VersionedProfileUpdate> {
return DispatchQueue.global().async(.promise) {
guard let localUuid = self.tsAccountManager.localUuid else {
@ -114,7 +106,7 @@ public class VersionedProfiles: NSObject {
guard let encryptedPaddedProfileName = self.profileManager.encrypt(profileNameComponents: nameComponents,
profileKey: profileKey) else {
throw OWSAssertionError("Could not encrypt profile name.")
throw OWSAssertionError("Could not encrypt profile name.")
}
nameData = encryptedPaddedProfileName
}
@ -128,7 +120,7 @@ public class VersionedProfiles: NSObject {
let profileKey: OWSAES256Key = self.profileManager.localProfileKey()
guard let encryptedProfileAvatarData = self.profileManager.encrypt(profileData: profileAvatarData,
profileKey: profileKey) else {
throw OWSAssertionError("Could not encrypt profile avatar.")
throw OWSAssertionError("Could not encrypt profile avatar.")
}
return self.parseFormAndUpload(formResponseObject: responseObject,
profileAvatarData: encryptedProfileAvatarData)
@ -137,8 +129,8 @@ public class VersionedProfiles: NSObject {
}
}
private class func parseFormAndUpload(formResponseObject: Any?,
profileAvatarData: Data) -> Promise<VersionedProfileUpdate> {
private func parseFormAndUpload(formResponseObject: Any?,
profileAvatarData: Data) -> Promise<VersionedProfileUpdate> {
return firstly { () throws -> Promise<OWSUploadForm> in
guard let response = formResponseObject as? [AnyHashable: Any] else {
throw OWSAssertionError("Unexpected response.")
@ -156,8 +148,8 @@ public class VersionedProfiles: NSObject {
// MARK: - Get
public class func versionedProfileRequest(address: SignalServiceAddress,
udAccessKey: SMKUDAccessKey?) throws -> VersionedProfileRequest {
public func versionedProfileRequest(address: SignalServiceAddress,
udAccessKey: SMKUDAccessKey?) throws -> VersionedProfileRequest {
guard address.isValid,
let uuid: UUID = address.uuid else {
throw OWSAssertionError("Invalid address: \(address)")
@ -193,20 +185,23 @@ public class VersionedProfiles: NSObject {
credentialRequest: credentialRequestArg,
udAccessKey: udAccessKey)
return VersionedProfileRequest(request: request, requestContext: requestContext)
return VersionedProfileRequestImpl(request: request, requestContext: requestContext)
}
// MARK: -
public class func parseProfileKey(profileKey: OWSAES256Key) throws -> ProfileKey {
public func parseProfileKey(profileKey: OWSAES256Key) throws -> ProfileKey {
let profileKeyData: Data = profileKey.keyData
let profileKeyDataBytes = [UInt8](profileKeyData)
return try ProfileKey(contents: profileKeyDataBytes)
}
public class func didFetchProfile(profile: SignalServiceProfile,
profileRequest: VersionedProfileRequest) {
public func didFetchProfile(profile: SignalServiceProfile,
profileRequest: VersionedProfileRequest) {
do {
guard let profileRequest = profileRequest as? VersionedProfileRequestImpl else {
return
}
guard let credentialResponseData = profile.credential else {
return
}
@ -233,7 +228,7 @@ public class VersionedProfiles: NSObject {
Logger.verbose("Updating credential for: \(uuid)")
databaseStorage.write { transaction in
credentialStore.setData(credentialData, key: uuid.uuidString, transaction: transaction)
Self.credentialStore.setData(credentialData, key: uuid.uuidString, transaction: transaction)
}
} catch {
owsFailDebug("Invalid credential: \(error).")
@ -243,17 +238,17 @@ public class VersionedProfiles: NSObject {
// MARK: - Credentials
public class func profileKeyCredentialData(for address: SignalServiceAddress,
transaction: SDSAnyReadTransaction) throws -> Data? {
public func profileKeyCredentialData(for address: SignalServiceAddress,
transaction: SDSAnyReadTransaction) throws -> Data? {
guard address.isValid,
let uuid = address.uuid else {
throw OWSAssertionError("Invalid address: \(address)")
}
return credentialStore.getData(uuid.uuidString, transaction: transaction)
return Self.credentialStore.getData(uuid.uuidString, transaction: transaction)
}
public class func profileKeyCredential(for address: SignalServiceAddress,
transaction: SDSAnyReadTransaction) throws -> ProfileKeyCredential? {
public func profileKeyCredential(for address: SignalServiceAddress,
transaction: SDSAnyReadTransaction) throws -> ProfileKeyCredential? {
guard let data = try profileKeyCredentialData(for: address, transaction: transaction) else {
return nil
}
@ -263,8 +258,8 @@ public class VersionedProfiles: NSObject {
}
@objc(clearProfileKeyCredentialForAddress:transaction:)
public class func clearProfileKeyCredential(for address: SignalServiceAddress,
transaction: SDSAnyWriteTransaction) {
public func clearProfileKeyCredential(for address: SignalServiceAddress,
transaction: SDSAnyWriteTransaction) {
guard address.isValid else {
owsFailDebug("Invalid address: \(address)")
return
@ -272,11 +267,11 @@ public class VersionedProfiles: NSObject {
guard let uuid = address.uuid else {
return
}
credentialStore.removeValue(forKey: uuid.uuidString, transaction: transaction)
Self.credentialStore.removeValue(forKey: uuid.uuidString, transaction: transaction)
}
public class func clearProfileKeyCredentials(transaction: SDSAnyWriteTransaction) {
credentialStore.removeAll(transaction: transaction)
public func clearProfileKeyCredentials(transaction: SDSAnyWriteTransaction) {
Self.credentialStore.removeAll(transaction: transaction)
}
}

View File

@ -19,6 +19,9 @@ class DefaultStickerPack {
}
private class func parseAll() -> [StickerPackInfo: DefaultStickerPack] {
guard FeatureFlags.isUsingProductionService else {
return [:]
}
let packs = [
// Bandit the Cat
DefaultStickerPack(packIdHex: "9acc9e8aba563d26a4994e69263e3b25", packKeyHex: "5a6dff3948c28efb9b7aaf93ecc375c69fc316e78077ed26867a14d10a0f6a12", shouldAutoInstall: false)!,

View File

@ -145,6 +145,11 @@ public class RequestMaker: NSObject {
resolver.reject(RequestMakerError.websocketRequestError(statusCode: statusCode, responseData: responseData, underlyingError: error))
})
}.recover { (error: Error) -> Promise<RequestMakerResult> in
if error.httpStatusCode == 413 {
// We've hit rate limit; don't retry.
throw error
}
switch error {
case RequestMakerError.websocketRequestError(let statusCode, _, _):
if isUDRequest && (statusCode == 401 || statusCode == 403) {
@ -193,6 +198,11 @@ public class RequestMaker: NSObject {
wasSentByUD: isUDRequest,
wasSentByWebsocket: false)
}.recover { (error: Error) -> Promise<RequestMakerResult> in
if error.httpStatusCode == 413 {
// We've hit rate limit; don't retry.
throw error
}
if isUDRequest,
let statusCode = error.httpStatusCode,
statusCode == 401 || statusCode == 403 {

View File

@ -85,6 +85,15 @@ NS_ASSUME_NONNULL_BEGIN
- (void)updateProfileForAddress:(SignalServiceAddress *)address;
- (AnyPromise *)updateProfileForAddressPromise:(SignalServiceAddress *)address;
- (AnyPromise *)updateProfileForAddressPromise:(SignalServiceAddress *)address
mainAppOnly:(BOOL)mainAppOnly
ignoreThrottling:(BOOL)ignoreThrottling;
- (void)updateProfileForAddress:(SignalServiceAddress *)address
profileNameEncrypted:(nullable NSData *)profileNameEncrypted
username:(nullable NSString *)username
isUuidCapable:(BOOL)isUuidCapable
avatarUrlPath:(nullable NSString *)avatarUrlPath;
- (BOOL)recipientAddressIsUuidCapable:(SignalServiceAddress *)address transaction:(SDSAnyReadTransaction *)transaction;

View File

@ -5,6 +5,8 @@
NS_ASSUME_NONNULL_BEGIN
@class AccountServiceClient;
@class BulkProfileFetch;
@class BulkUUIDLookup;
@class ContactsUpdater;
@class GroupsV2MessageProcessor;
@class MessageFetcherJob;
@ -51,6 +53,7 @@ NS_ASSUME_NONNULL_BEGIN
@protocol GroupsV2;
@protocol GroupV2Updates;
@protocol PendingReadReceiptRecorder;
@protocol VersionedProfiles;
@interface SSKEnvironment : NSObject
@ -99,7 +102,10 @@ NS_ASSUME_NONNULL_BEGIN
groupsV2:(id<GroupsV2>)groupsV2
groupV2Updates:(id<GroupV2Updates>)groupV2Updates
messageProcessing:(MessageProcessing *)messageProcessing
messageFetcherJob:(MessageFetcherJob *)messageFetcherJob NS_DESIGNATED_INITIALIZER;
messageFetcherJob:(MessageFetcherJob *)messageFetcherJob
bulkProfileFetch:(BulkProfileFetch *)bulkProfileFetch
bulkUUIDLookup:(BulkUUIDLookup *)bulkUUIDLookup
versionedProfiles:(id<VersionedProfiles>)versionedProfiles NS_DESIGNATED_INITIALIZER;
@property (nonatomic, readonly, class) SSKEnvironment *shared;
@ -148,13 +154,15 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly) id<StorageServiceManagerProtocol> storageServiceManager;
@property (nonatomic, readonly) id<GroupsV2> groupsV2;
@property (nonatomic, readonly) id<GroupV2Updates> groupV2Updates;
@property (nonatomic, readonly) StickerManager *stickerManager;
@property (nonatomic, readonly) SDSDatabaseStorage *databaseStorage;
@property (nonatomic, readonly) StorageCoordinator *storageCoordinator;
@property (nonatomic, readonly) SSKPreferences *sskPreferences;
@property (nonatomic, readonly) MessageProcessing *messageProcessing;
@property (nonatomic, readonly) MessageFetcherJob *messageFetcherJob;
@property (nonatomic, readonly) BulkProfileFetch *bulkProfileFetch;
@property (nonatomic, readonly) BulkUUIDLookup *bulkUUIDLookup;
@property (nonatomic, readonly) id<VersionedProfiles> versionedProfiles;
@property (nonatomic, readonly, nullable) OWSPrimaryStorage *primaryStorage;

View File

@ -49,6 +49,9 @@ static SSKEnvironment *sharedSSKEnvironment;
@property (nonatomic) id<GroupV2Updates> groupV2Updates;
@property (nonatomic) MessageProcessing *messageProcessing;
@property (nonatomic) MessageFetcherJob *messageFetcherJob;
@property (nonatomic) BulkProfileFetch *bulkProfileFetch;
@property (nonatomic) BulkUUIDLookup *bulkUUIDLookup;
@property (nonatomic) id<VersionedProfiles> versionedProfiles;
@end
@ -103,6 +106,9 @@ static SSKEnvironment *sharedSSKEnvironment;
groupV2Updates:(id<GroupV2Updates>)groupV2Updates
messageProcessing:(MessageProcessing *)messageProcessing
messageFetcherJob:(MessageFetcherJob *)messageFetcherJob
bulkProfileFetch:(BulkProfileFetch *)bulkProfileFetch
bulkUUIDLookup:(BulkUUIDLookup *)bulkUUIDLookup
versionedProfiles:(id<VersionedProfiles>)versionedProfiles
{
self = [super init];
if (!self) {
@ -150,7 +156,10 @@ static SSKEnvironment *sharedSSKEnvironment;
OWSAssertDebug(groupsV2);
OWSAssertDebug(groupV2Updates);
OWSAssertDebug(messageProcessing);
OWSCAssertDebug(messageFetcherJob);
OWSAssertDebug(messageFetcherJob);
OWSAssertDebug(bulkProfileFetch);
OWSAssertDebug(versionedProfiles);
OWSAssertDebug(bulkUUIDLookup);
_contactsManager = contactsManager;
_linkPreviewManager = linkPreviewManager;
@ -195,6 +204,9 @@ static SSKEnvironment *sharedSSKEnvironment;
_groupV2Updates = groupV2Updates;
_messageProcessing = messageProcessing;
_messageFetcherJob = messageFetcherJob;
_bulkProfileFetch = bulkProfileFetch;
_versionedProfiles = versionedProfiles;
_bulkUUIDLookup = bulkUUIDLookup;
return self;
}

View File

@ -104,6 +104,9 @@ NS_ASSUME_NONNULL_BEGIN
id<GroupV2Updates> groupV2Updates = [[MockGroupV2Updates alloc] init];
MessageProcessing *messageProcessing = [MessageProcessing new];
MessageFetcherJob *messageFetcherJob = [MessageFetcherJob new];
BulkProfileFetch *bulkProfileFetch = [BulkProfileFetch new];
BulkUUIDLookup *bulkUUIDLookup = [BulkUUIDLookup new];
id<VersionedProfiles> versionedProfiles = [MockVersionedProfiles new];
self = [super initWithContactsManager:contactsManager
linkPreviewManager:linkPreviewManager
@ -147,7 +150,10 @@ NS_ASSUME_NONNULL_BEGIN
groupsV2:groupsV2
groupV2Updates:groupV2Updates
messageProcessing:messageProcessing
messageFetcherJob:messageFetcherJob];
messageFetcherJob:messageFetcherJob
bulkProfileFetch:bulkProfileFetch
bulkUUIDLookup:bulkUUIDLookup
versionedProfiles:versionedProfiles];
if (!self) {
return nil;

View File

@ -216,6 +216,13 @@ NS_ASSUME_NONNULL_BEGIN
return [AnyPromise promiseWithValue:@(1)];
}
- (AnyPromise *)updateProfileForAddressPromise:(SignalServiceAddress *)address
mainAppOnly:(BOOL)mainAppOnly
ignoreThrottling:(BOOL)ignoreThrottling
{
return [AnyPromise promiseWithValue:@(1)];
}
- (void)warmCaches
{
// Do nothing.
@ -257,6 +264,15 @@ NS_ASSUME_NONNULL_BEGIN
return self.hasLocalProfile;
}
- (void)updateProfileForAddress:(SignalServiceAddress *)address
profileNameEncrypted:(nullable NSData *)profileNameEncrypted
username:(nullable NSString *)username
isUuidCapable:(BOOL)isUuidCapable
avatarUrlPath:(nullable NSString *)avatarUrlPath
{
// Do nothing.
}
@end
#endif

View File

@ -0,0 +1,223 @@
//
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//
import PromiseKit
@objc
public class BulkUUIDLookup: NSObject {
// MARK: - Dependencies
private var tsAccountManager: TSAccountManager {
return .sharedInstance()
}
private var reachabilityManager: SSKReachabilityManager {
return SSKEnvironment.shared.reachabilityManager
}
private var contactsUpdater: ContactsUpdater {
return SSKEnvironment.shared.contactsUpdater
}
// MARK: -
private let serialQueue = DispatchQueue(label: "BulkUUIDLookup")
// This property should only be accessed on serialQueue.
private var phoneNumberQueue = Set<String>()
// This property should only be accessed on serialQueue.
private var isUpdateInFlight = false
struct UpdateOutcome {
let outcome: Outcome
enum Outcome {
case networkFailure
case retryLimit
case serviceError
case success
case unknownError
}
let date: Date
init(_ outcome: Outcome) {
self.outcome = outcome
self.date = Date()
}
}
// This property should only be accessed on serialQueue.
private var lastOutcomeMap = [String: UpdateOutcome]()
// This property should only be accessed on serialQueue.
private var lastRateLimitErrorDate: Date?
@objc
public required override init() {
super.init()
SwiftSingletons.register(self)
observeNotifications()
}
private func observeNotifications() {
NotificationCenter.default.addObserver(forName: SSKReachability.owsReachabilityDidChange,
object: nil, queue: nil) { [weak self] _ in
guard let self = self else { return }
self.serialQueue.async {
self.process()
}
}
NotificationCenter.default.addObserver(forName: .registrationStateDidChange, object: nil, queue: nil) { [weak self] _ in
guard let self = self else { return }
self.serialQueue.async {
self.process()
}
}
}
// This should be used for non-urgent uuid lookups.
@objc
public func lookupUuids(phoneNumbers: [String]) {
serialQueue.async {
self.phoneNumberQueue.formUnion(phoneNumbers)
self.process()
}
}
private func process() {
assertOnQueue(serialQueue)
guard CurrentAppContext().isMainApp else {
return
}
guard reachabilityManager.isReachable else {
return
}
guard tsAccountManager.isRegisteredAndReady else {
return
}
guard FeatureFlags.useOnlyModernContactDiscovery ||
FeatureFlags.compareLegacyContactDiscoveryAgainstModern else {
// Can't fill in UUIDs using legacy contact intersections.
return
}
// Only one update in flight at a time.
guard !self.isUpdateInFlight else {
return
}
// Build batch.
let phoneNumbers = phoneNumberQueue.filter { self.shouldUpdatePhoneNumber($0) }
phoneNumberQueue.removeAll()
// De-bounce.
Logger.verbose("Updating: \(phoneNumbers)")
// Perform update.
isUpdateInFlight = true
firstly {
return contactsUpdater.lookupIdentifiersPromise(phoneNumbers: Array(phoneNumbers)).asVoid()
}.done {
self.serialQueue.async {
self.isUpdateInFlight = false
let outcome = UpdateOutcome(.success)
for phoneNumber in phoneNumbers {
self.lastOutcomeMap[phoneNumber] = outcome
}
self.process()
}
}.catch { error in
self.serialQueue.async {
self.isUpdateInFlight = false
let outcome: UpdateOutcome
let nsError = error as NSError
if nsError.domain == OWSSignalServiceKitErrorDomain &&
nsError.code == OWSErrorCode.contactsUpdaterRateLimit.rawValue {
Logger.error("Error: \(error)")
outcome = UpdateOutcome(.retryLimit)
self.lastRateLimitErrorDate = Date()
} else {
switch error {
case ContactDiscoveryService.ServiceError.error4xx,
ContactDiscoveryService.ServiceError.error5xx:
owsFailDebug("Error: \(error)")
outcome = UpdateOutcome(.serviceError)
case ContactDiscoveryService.ServiceError.tooManyRequests:
Logger.error("Error: \(error)")
outcome = UpdateOutcome(.retryLimit)
self.lastRateLimitErrorDate = Date()
default:
if IsNetworkConnectivityFailure(error) {
Logger.warn("Error: \(error)")
outcome = UpdateOutcome(.networkFailure)
} else if error.httpStatusCode == 413 {
Logger.error("Error: \(error)")
outcome = UpdateOutcome(.retryLimit)
self.lastRateLimitErrorDate = Date()
} else if let httpStatusCode = error.httpStatusCode,
httpStatusCode >= 400,
httpStatusCode <= 599 {
owsFailDebug("Error: \(error)")
outcome = UpdateOutcome(.serviceError)
} else {
owsFailDebug("Error: \(error)")
outcome = UpdateOutcome(.unknownError)
}
}
}
for phoneNumber in phoneNumbers {
self.lastOutcomeMap[phoneNumber] = outcome
}
self.process()
}
}.retainUntilComplete()
}
private func shouldUpdatePhoneNumber(_ phoneNumber: String) -> Bool {
assertOnQueue(serialQueue)
guard SignalServiceAddress(phoneNumber: phoneNumber).uuid == nil else {
return false
}
// Skip if we've recently had a rate limit error.
if let lastRateLimitErrorDate = self.lastRateLimitErrorDate {
let minElapsedSeconds = 5 * kMinuteInterval
let elapsedSeconds = lastRateLimitErrorDate.timeIntervalSinceNow
guard elapsedSeconds >= minElapsedSeconds else {
return false
}
}
guard let lastOutcome = lastOutcomeMap[phoneNumber] else {
return true
}
let minElapsedSeconds: TimeInterval
let elapsedSeconds = lastOutcome.date.timeIntervalSinceNow
switch lastOutcome.outcome {
case .networkFailure:
minElapsedSeconds = 1 * kMinuteInterval
case .retryLimit:
minElapsedSeconds = 15 * kMinuteInterval
case .serviceError:
minElapsedSeconds = 30 * kMinuteInterval
case .unknownError:
minElapsedSeconds = 60 * kMinuteInterval
case .success:
minElapsedSeconds = 60 * kMinuteInterval
}
return elapsedSeconds >= minElapsedSeconds
}
}

View File

@ -45,6 +45,10 @@ public struct OrderedSet<Element> where Element: Hashable {
return set.count
}
public var first: Element? {
return orderedMembers.first
}
// TODO: I only implemented the minimum API that I needed. There's lots more that could
// reasonably be added to an OrderedSet (union, etc.)
}

View File

@ -0,0 +1,239 @@
//
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//
import PromiseKit
@objc
public class BulkProfileFetch: NSObject {
// MARK: - Dependencies
private var profileManager: ProfileManagerProtocol {
return SSKEnvironment.shared.profileManager
}
private var tsAccountManager: TSAccountManager {
return .sharedInstance()
}
private var reachabilityManager: SSKReachabilityManager {
return SSKEnvironment.shared.reachabilityManager
}
// MARK: -
private let serialQueue = DispatchQueue(label: "BulkProfileFetch")
// This property should only be accessed on serialQueue.
private var addressQueue = OrderedSet<SignalServiceAddress>()
// This property should only be accessed on serialQueue.
private var isUpdateInFlight = false
struct UpdateOutcome {
let outcome: Outcome
enum Outcome {
case networkFailure
case retryLimit
case noProfile
case serviceError
case success
case throttled
}
let date: Date
init(_ outcome: Outcome) {
self.outcome = outcome
self.date = Date()
}
}
// This property should only be accessed on serialQueue.
private var lastOutcomeMap = [SignalServiceAddress: UpdateOutcome]()
// This property should only be accessed on serialQueue.
private var lastRateLimitErrorDate: Date?
@objc
public required override init() {
super.init()
SwiftSingletons.register(self)
AppReadiness.runNowOrWhenAppDidBecomeReady {
// TODO: There would be benefit to trying to update
// missing & stale profiles on launch.
}
observeNotifications()
}
private func observeNotifications() {
NotificationCenter.default.addObserver(forName: SSKReachability.owsReachabilityDidChange,
object: nil, queue: nil) { [weak self] _ in
guard let self = self else { return }
self.serialQueue.async {
self.process()
}
}
NotificationCenter.default.addObserver(forName: .registrationStateDidChange, object: nil, queue: nil) { [weak self] _ in
guard let self = self else { return }
self.serialQueue.async {
self.process()
}
}
}
// This should be used for non-urgent profile updates.
@objc
public func fetchAndUpdateProfiles(thread: TSThread) {
fetchAndUpdateProfiles(addresses: thread.recipientAddresses)
}
// This should be used for non-urgent profile updates.
@objc
public func fetchAndUpdateProfile(address: SignalServiceAddress) {
fetchAndUpdateProfiles(addresses: [address])
}
// This should be used for non-urgent profile updates.
@objc
public func fetchAndUpdateProfiles(addresses: [SignalServiceAddress]) {
serialQueue.async {
guard let localAddress = self.tsAccountManager.localAddress else {
owsFailDebug("missing local address")
return
}
for address in addresses {
guard address != localAddress else {
continue
}
guard !self.addressQueue.contains(address) else {
continue
}
self.addressQueue.append(address)
}
self.process()
}
}
private func process() {
assertOnQueue(serialQueue)
guard CurrentAppContext().isMainApp else {
return
}
guard reachabilityManager.isReachable else {
return
}
guard tsAccountManager.isRegisteredAndReady else {
return
}
// Only one update in flight at a time.
guard !self.isUpdateInFlight else {
return
}
// Dequeue.
guard let address = self.addressQueue.first else {
return
}
self.addressQueue.remove(address)
// De-bounce.
guard self.shouldUpdateAddress(address) else {
return
}
Logger.verbose("Updating: \(address)")
// Perform update.
isUpdateInFlight = true
// We need to throttle these jobs.
// Always wait N seconds between update jobs.
let updateDelaySeconds: TimeInterval = 3
firstly {
self.profileManager.updateProfile(forAddressPromise: address,
mainAppOnly: true,
ignoreThrottling: false).asVoid()
}.done {
self.serialQueue.asyncAfter(deadline: DispatchTime.now() + updateDelaySeconds) {
self.isUpdateInFlight = false
self.lastOutcomeMap[address] = UpdateOutcome(.success)
self.process()
}
}.catch { error in
self.serialQueue.asyncAfter(deadline: DispatchTime.now() + updateDelaySeconds) {
self.isUpdateInFlight = false
switch error {
case ProfileFetchError.missing:
Logger.error("Error: \(error)")
self.lastOutcomeMap[address] = UpdateOutcome(.noProfile)
case ProfileFetchError.throttled:
self.lastOutcomeMap[address] = UpdateOutcome(.throttled)
case ProfileFetchError.rateLimit:
Logger.error("Error: \(error)")
self.lastOutcomeMap[address] = UpdateOutcome(.retryLimit)
self.lastRateLimitErrorDate = Date()
default:
if IsNetworkConnectivityFailure(error) {
Logger.warn("Error: \(error)")
self.lastOutcomeMap[address] = UpdateOutcome(.networkFailure)
} else if error.httpStatusCode == 413 {
Logger.error("Error: \(error)")
self.lastOutcomeMap[address] = UpdateOutcome(.retryLimit)
self.lastRateLimitErrorDate = Date()
} else if error.httpStatusCode == 404 {
Logger.error("Error: \(error)")
self.lastOutcomeMap[address] = UpdateOutcome(.noProfile)
} else {
// TODO: We may need to handle more status codes.
owsFailDebug("Error: \(error)")
self.lastOutcomeMap[address] = UpdateOutcome(.serviceError)
}
}
self.process()
}
}.retainUntilComplete()
}
private func shouldUpdateAddress(_ address: SignalServiceAddress) -> Bool {
assertOnQueue(serialQueue)
// Skip if we've recently had a rate limit error.
if let lastRateLimitErrorDate = self.lastRateLimitErrorDate {
let minElapsedSeconds = 5 * kMinuteInterval
let elapsedSeconds = lastRateLimitErrorDate.timeIntervalSinceNow
guard elapsedSeconds >= minElapsedSeconds else {
return false
}
}
guard let lastOutcome = lastOutcomeMap[address] else {
return true
}
let minElapsedSeconds: TimeInterval
let elapsedSeconds = lastOutcome.date.timeIntervalSinceNow
switch lastOutcome.outcome {
case .networkFailure:
minElapsedSeconds = 1 * kMinuteInterval
case .retryLimit:
minElapsedSeconds = 5 * kMinuteInterval
case .throttled:
minElapsedSeconds = 5 * kMinuteInterval
case .noProfile:
minElapsedSeconds = 6 * kHourInterval
case .serviceError:
minElapsedSeconds = 30 * kMinuteInterval
case .success:
minElapsedSeconds = 15 * kMinuteInterval
}
return elapsedSeconds >= minElapsedSeconds
}
}

View File

@ -4,7 +4,6 @@
import Foundation
import PromiseKit
import SignalServiceKit
import SignalMetadataKit
@objc
@ -13,6 +12,26 @@ public enum ProfileFetchError: Int, Error {
case throttled
case notMainApp
case cantRequestVersionedProfile
case rateLimit
}
// MARK: -
extension ProfileFetchError: CustomStringConvertible {
public var description: String {
switch self {
case .missing:
return "ProfileFetchError.missing"
case .throttled:
return "ProfileFetchError.throttled"
case .notMainApp:
return "ProfileFetchError.notMainApp"
case .cantRequestVersionedProfile:
return "ProfileFetchError.cantRequestVersionedProfile"
case .rateLimit:
return "ProfileFetchError.rateLimit"
}
}
}
// MARK: -
@ -155,19 +174,6 @@ public class ProfileFetcherJob: NSObject {
.retainUntilComplete()
}
@objc(fetchAndUpdateProfilesWithThread:)
public class func fetchAndUpdateProfiles(thread: TSThread) {
let addresses = thread.recipientAddresses
let subjects = addresses.map { ProfileRequestSubject.address(address: $0) }
let options = ProfileFetchOptions()
var promises = [Promise<SignalServiceProfile>]()
for subject in subjects {
let job = ProfileFetcherJob(subject: subject, options: options)
promises.append(job.runAsPromise())
}
when(fulfilled: promises).retainUntilComplete()
}
private init(subject: ProfileRequestSubject,
options: ProfileFetchOptions) {
self.subject = subject
@ -188,8 +194,8 @@ public class ProfileFetcherJob: NSObject {
return SSKEnvironment.shared.udManager
}
private var profileManager: OWSProfileManager {
return OWSProfileManager.shared()
private var profileManager: ProfileManagerProtocol {
return SSKEnvironment.shared.profileManager
}
private var identityManager: OWSIdentityManager {
@ -213,6 +219,10 @@ public class ProfileFetcherJob: NSObject {
return SDSDatabaseStorage.shared
}
private var versionedProfiles: VersionedProfiles {
return SSKEnvironment.shared.versionedProfiles
}
// MARK: -
private func runAsPromise() -> Promise<SignalServiceProfile> {
@ -220,9 +230,12 @@ public class ProfileFetcherJob: NSObject {
self.addBackgroundTask()
}.then(on: DispatchQueue.global()) { _ in
return self.requestProfile()
}.map(on: DispatchQueue.global()) { fetchedProfile in
self.updateProfile(fetchedProfile: fetchedProfile)
return fetchedProfile.profile
}.then(on: DispatchQueue.global()) { fetchedProfile in
return firstly {
self.updateProfile(fetchedProfile: fetchedProfile)
}.map(on: DispatchQueue.global()) { _ in
return fetchedProfile.profile
}
}
}
@ -255,7 +268,7 @@ public class ProfileFetcherJob: NSObject {
return requestProfileWithRetries()
}
private func requestProfileWithRetries(remainingRetries: Int = 3) -> Promise<FetchedProfile> {
private func requestProfileWithRetries(retryCount: Int = 0) -> Promise<FetchedProfile> {
let subject = self.subject
let (promise, resolver) = Promise<FetchedProfile>.pending()
@ -265,8 +278,10 @@ public class ProfileFetcherJob: NSObject {
resolver.fulfill(fetchedProfile)
}.catch(on: DispatchQueue.global()) { error in
if error.httpStatusCode == 404 {
resolver.reject(ProfileFetchError.missing)
return
return resolver.reject(ProfileFetchError.missing)
}
if error.httpStatusCode == 413 {
return resolver.reject(ProfileFetchError.rateLimit)
}
switch error {
@ -281,14 +296,15 @@ public class ProfileFetcherJob: NSObject {
resolver.reject(error)
return
default:
guard remainingRetries > 0 else {
let maxRetries = 3
guard retryCount < maxRetries else {
Logger.warn("failed to get profile with error: \(error)")
resolver.reject(error)
return
}
firstly {
self.requestProfileWithRetries(remainingRetries: remainingRetries - 1)
self.requestProfileWithRetries(retryCount: retryCount + 1)
}.done(on: DispatchQueue.global()) { fetchedProfile in
resolver.fulfill(fetchedProfile)
}.catch(on: DispatchQueue.global()) { error in
@ -362,7 +378,7 @@ public class ProfileFetcherJob: NSObject {
if shouldUseVersionedFetch {
do {
let request = try VersionedProfiles.versionedProfileRequest(address: address, udAccessKey: udAccessKeyForRequest)
let request = try self.versionedProfiles.versionedProfileRequest(address: address, udAccessKey: udAccessKeyForRequest)
currentVersionedProfileRequest = request
return request.request
} catch {
@ -395,7 +411,7 @@ public class ProfileFetcherJob: NSObject {
let address = profile.address
if let profileRequest = fetchedProfile.versionedProfileRequest {
VersionedProfiles.didFetchProfile(profile: profile, profileRequest: profileRequest)
self.versionedProfiles.didFetchProfile(profile: profile, profileRequest: profileRequest)
}
profileManager.updateProfile(for: address,

View File

@ -0,0 +1,76 @@
//
// Copyright (c) 2020 Open Whisper Systems. All rights reserved.
//
import Foundation
import PromiseKit
//import SignalServiceKit
import SignalMetadataKit
//import ZKGroup
@objc
public class VersionedProfileUpdate: NSObject {
// This will only be set if there is a profile avatar.
@objc
public let avatarUrlPath: String?
public required init(avatarUrlPath: String? = nil) {
self.avatarUrlPath = avatarUrlPath
}
}
// MARK: -
@objc
public protocol VersionedProfileRequest: class {
var request: TSRequest { get }
}
// MARK: -
@objc
public protocol VersionedProfiles: class {
@objc(clearProfileKeyCredentialForAddress:transaction:)
func clearProfileKeyCredential(for address: SignalServiceAddress,
transaction: SDSAnyWriteTransaction)
func clearProfileKeyCredentials(transaction: SDSAnyWriteTransaction)
func versionedProfileRequest(address: SignalServiceAddress,
udAccessKey: SMKUDAccessKey?) throws -> VersionedProfileRequest
func didFetchProfile(profile: SignalServiceProfile,
profileRequest: VersionedProfileRequest)
}
// MARK: -
public protocol VersionedProfilesSwift: VersionedProfiles {
func updateProfilePromise(profileGivenName: String?,
profileFamilyName: String?,
profileAvatarData: Data?) -> Promise<VersionedProfileUpdate>
}
// MARK: -
@objc
public class MockVersionedProfiles: NSObject, VersionedProfilesSwift {
public func clearProfileKeyCredential(for address: SignalServiceAddress,
transaction: SDSAnyWriteTransaction) {}
public func clearProfileKeyCredentials(transaction: SDSAnyWriteTransaction) {}
public func versionedProfileRequest(address: SignalServiceAddress,
udAccessKey: SMKUDAccessKey?) throws -> VersionedProfileRequest {
owsFail("Not implemented.")
}
public func didFetchProfile(profile: SignalServiceProfile,
profileRequest: VersionedProfileRequest) {}
public func updateProfilePromise(profileGivenName: String?,
profileFamilyName: String?,
profileAvatarData: Data?) -> Promise<VersionedProfileUpdate> {
owsFail("Not implemented.")
}
}

View File

@ -72,6 +72,18 @@ public class GroupManager: NSObject {
return SSKEnvironment.shared.messageProcessing
}
private class var bulkProfileFetch: BulkProfileFetch {
return SSKEnvironment.shared.bulkProfileFetch
}
private class var bulkUUIDLookup: BulkUUIDLookup {
return SSKEnvironment.shared.bulkUUIDLookup
}
private class var contactsUpdater: ContactsUpdater {
return SSKEnvironment.shared.contactsUpdater
}
// MARK: -
// Never instantiate this class.
@ -222,7 +234,7 @@ public class GroupManager: NSObject {
}.then(on: .global()) { () -> Promise<Void> in
var memberSet = Set(membersParam)
memberSet.insert(localAddress)
return self.tryToEnableGroupsV2(for: Array(memberSet), ignoreErrors: true)
return self.tryToEnableGroupsV2(for: Array(memberSet), isBlocking: true, ignoreErrors: true)
}.map(on: .global()) { () throws -> GroupMembership in
// Build member list.
//
@ -719,7 +731,7 @@ public class GroupManager: NSObject {
guard RemoteConfig.groupsV2CreateGroups else {
return Promise.value(())
}
return self.tryToEnableGroupsV2(for: Array(proposedGroupModel.groupMembership.allUsers), ignoreErrors: true)
return self.tryToEnableGroupsV2(for: Array(proposedGroupModel.groupMembership.allUsers), isBlocking: true, ignoreErrors: true)
}.then(on: .global()) { () -> Promise<Void> in
return self.ensureLocalProfileHasCommitmentIfNecessary()
}.then(on: DispatchQueue.global()) { () -> Promise<String?> in
@ -1197,8 +1209,9 @@ public class GroupManager: NSObject {
// MARK: - UUIDs
public static func tryToEnableGroupsV2(for addresses: [SignalServiceAddress],
isBlocking: Bool,
ignoreErrors: Bool) -> Promise<Void> {
let promise = tryToEnableGroupsV2(for: addresses)
let promise = tryToEnableGroupsV2(for: addresses, isBlocking: isBlocking)
if ignoreErrors {
return promise.recover { error -> Guarantee<Void> in
Logger.warn("Error: \(error).")
@ -1209,7 +1222,8 @@ public class GroupManager: NSObject {
}
}
private static func tryToEnableGroupsV2(for addresses: [SignalServiceAddress]) -> Promise<Void> {
private static func tryToEnableGroupsV2(for addresses: [SignalServiceAddress],
isBlocking: Bool) -> Promise<Void> {
return firstly { () -> Promise<Void> in
for address in addresses {
guard address.isValid else {
@ -1218,9 +1232,45 @@ public class GroupManager: NSObject {
}
return Promise.value(())
}.then(on: .global()) { _ -> Promise<Void> in
// Try to ensure UUIDs.
return self.groupsV2.tryToEnsureUuidsForGroupMembers(for: addresses)
return self.tryToFillInMissingUuuids(for: addresses, isBlocking: isBlocking)
}.then(on: .global()) { _ -> Promise<Void> in
return self.tryToEnableGroupsV2Capability(for: addresses, isBlocking: isBlocking)
}
}
public static func tryToFillInMissingUuuids(for addresses: [SignalServiceAddress],
isBlocking: Bool) -> Promise<Void> {
guard FeatureFlags.useOnlyModernContactDiscovery ||
FeatureFlags.compareLegacyContactDiscoveryAgainstModern else {
// Can't fill in UUIDs using legacy contact intersections.
return Promise.value(())
}
let phoneNumbersWithoutUuids = addresses.filter { $0.uuid == nil }.compactMap { $0.phoneNumber }
guard phoneNumbersWithoutUuids.count > 0 else {
return Promise.value(())
}
if isBlocking {
// Block on the outcome.
return contactsUpdater.lookupIdentifiersPromise(phoneNumbers:
phoneNumbersWithoutUuids).asVoid()
} else {
// This will throttle, de-bounce, etc.
self.bulkUUIDLookup.lookupUuids(phoneNumbers: phoneNumbersWithoutUuids)
return Promise.value(())
}
}
private static func tryToEnableGroupsV2Capability(for addresses: [SignalServiceAddress],
isBlocking: Bool) -> Promise<Void> {
return firstly { () -> Promise<[SignalServiceAddress]> in
let validAddresses = addresses.filter { $0.isValid }
if validAddresses.count < addresses.count {
owsFailDebug("Invalid addresses.")
}
return Promise.value(validAddresses)
}.then(on: .global()) { (addresses: [SignalServiceAddress]) -> Promise<Void> in
// Try to ensure groups v2 capability.
var addressesWithoutCapability = [SignalServiceAddress]()
self.databaseStorage.read { transaction in
@ -1233,11 +1283,18 @@ public class GroupManager: NSObject {
guard !addressesWithoutCapability.isEmpty else {
return Promise.value(())
}
var promises = [Promise<Void>]()
for address in addressesWithoutCapability {
promises.append(self.profileManager.updateProfile(forAddressPromise: address).asVoid())
if isBlocking {
// Block on the outcome of the profile updates.
var promises = [Promise<Void>]()
for address in addressesWithoutCapability {
promises.append(self.profileManager.updateProfile(forAddressPromise: address).asVoid())
}
return when(fulfilled: promises)
} else {
// This will throttle, de-bounce, etc.
self.bulkProfileFetch.fetchAndUpdateProfiles(addresses: addressesWithoutCapability)
return Promise.value(())
}
return when(fulfilled: promises)
}
}

View File

@ -66,8 +66,6 @@ public protocol GroupsV2Swift: GroupsV2 {
func tryToEnsureProfileKeyCredentials(for addresses: [SignalServiceAddress]) -> Promise<Void>
func tryToEnsureUuidsForGroupMembers(for addresses: [SignalServiceAddress]) -> Promise<Void>
func fetchCurrentGroupV2Snapshot(groupModel: TSGroupModelV2) -> Promise<GroupV2Snapshot>
func fetchCurrentGroupV2Snapshot(groupSecretParamsData: Data) -> Promise<GroupV2Snapshot>
@ -326,10 +324,6 @@ public class MockGroupsV2: NSObject, GroupsV2Swift {
owsFail("Not implemented.")
}
public func tryToEnsureUuidsForGroupMembers(for addresses: [SignalServiceAddress]) -> Promise<Void> {
owsFail("Not implemented.")
}
public func fetchCurrentGroupV2Snapshot(groupModel: TSGroupModelV2) -> Promise<GroupV2Snapshot> {
owsFail("Not implemented.")
}

View File

@ -19,7 +19,7 @@
<key>CFBundleShortVersionString</key>
<string>3.8.0</string>
<key>CFBundleVersion</key>
<string>3.8.0.15</string>
<string>3.8.0.19</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>NSAppTransportSecurity</key>