Merge branch 'release/3.8.0'
This commit is contained in:
commit
aa53773e84
@ -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
2
Pods
@ -1 +1 @@
|
||||
Subproject commit 311cd6086281c648c095adfc858d515a03bd8131
|
||||
Subproject commit 21e3b748d44b7afe23928e11f3e605f0bed7fe51
|
||||
@ -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 */,
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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)];
|
||||
}
|
||||
|
||||
|
||||
@ -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(()))
|
||||
}
|
||||
|
||||
|
||||
@ -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(()))
|
||||
}
|
||||
|
||||
|
||||
@ -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)];
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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")
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)!,
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
223
SignalServiceKit/src/Util/BulkUUIDLookup.swift
Normal file
223
SignalServiceKit/src/Util/BulkUUIDLookup.swift
Normal 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
|
||||
}
|
||||
}
|
||||
@ -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.)
|
||||
}
|
||||
|
||||
239
SignalServiceKit/src/Util/Profiles/BulkProfileFetch.swift
Normal file
239
SignalServiceKit/src/Util/Profiles/BulkProfileFetch.swift
Normal 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
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
76
SignalServiceKit/src/Util/Profiles/VersionedProfiles.swift
Normal file
76
SignalServiceKit/src/Util/Profiles/VersionedProfiles.swift
Normal 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.")
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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.")
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user