diff --git a/Pods b/Pods
index 2870e676d..67dbced37 160000
--- a/Pods
+++ b/Pods
@@ -1 +1 @@
-Subproject commit 2870e676deec6a7ddb931edb6f0284f1f5b36085
+Subproject commit 67dbced37481e0011a3df1397ed57711384a4957
diff --git a/Signal/Signal-Info.plist b/Signal/Signal-Info.plist
index 378a5f3ca..9d7c02e10 100644
--- a/Signal/Signal-Info.plist
+++ b/Signal/Signal-Info.plist
@@ -5,9 +5,9 @@
BuildDetails
CarthageVersion
- 0.33.0
+ 0.34.0
OSXVersion
- 10.15.3
+ 10.15.2
WebRTCCommit
1445d719bf05280270e9f77576f80f973fd847f8 M73
diff --git a/Signal/src/Jobs/ConversationConfigurationSyncOperation.swift b/Signal/src/Jobs/ConversationConfigurationSyncOperation.swift
index 783c8af68..0a47382b7 100644
--- a/Signal/src/Jobs/ConversationConfigurationSyncOperation.swift
+++ b/Signal/src/Jobs/ConversationConfigurationSyncOperation.swift
@@ -64,7 +64,7 @@ class ConversationConfigurationSyncOperation: OWSOperation {
// The current implementation works, but seems wasteful.
// Does desktop handle single group sync correctly?
// What does Android do?
- let syncMessage: OWSSyncGroupsMessage = OWSSyncGroupsMessage()
+ let syncMessage: OWSSyncGroupsMessage = OWSSyncGroupsMessage(groupThread: groupThread)
var dataSource: DataSource?
self.dbConnection.read { transaction in
diff --git a/Signal/src/Loki/View Controllers/DeviceLinkingModal.swift b/Signal/src/Loki/View Controllers/DeviceLinkingModal.swift
index 9ce16ef95..195dd5669 100644
--- a/Signal/src/Loki/View Controllers/DeviceLinkingModal.swift
+++ b/Signal/src/Loki/View Controllers/DeviceLinkingModal.swift
@@ -165,7 +165,8 @@ final class DeviceLinkingModal : Modal, DeviceLinkingSessionDelegate {
let linkingAuthorizationMessage = DeviceLinkingUtilities.getLinkingAuthorizationMessage(for: deviceLink)
ThreadUtil.enqueue(linkingAuthorizationMessage)
SSKEnvironment.shared.messageSender.send(linkingAuthorizationMessage, success: {
- let _ = SSKEnvironment.shared.syncManager.syncAllContacts()
+ let _ = [SSKEnvironment.shared.syncManager.syncAllContacts(),
+ SSKEnvironment.shared.syncManager.syncAllGroups()]
}) { _ in
print("[Loki] Failed to send device link authorization message.")
}
diff --git a/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift b/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift
index 1ea538597..23d1aee5f 100644
--- a/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift
+++ b/Signal/src/Loki/View Controllers/NewClosedGroupVC.swift
@@ -168,8 +168,14 @@ final class NewClosedGroupVC : UIViewController, UITableViewDataSource, UITableV
return showError(title: NSLocalizedString("A closed group cannot have more than 10 members", comment: ""))
}
let userHexEncodedPublicKey = getUserHexEncodedPublicKey()
- let members = [String](selectedContacts) + [ userHexEncodedPublicKey ]
- let admins = [ userHexEncodedPublicKey ]
+ let storage = OWSPrimaryStorage.shared()
+ var masterHexEncodedPublicKey = ""
+ storage.dbReadConnection.readWrite { transaction in
+ masterHexEncodedPublicKey = storage.getMasterHexEncodedPublicKey(for: userHexEncodedPublicKey, in: transaction) ?? userHexEncodedPublicKey
+ }
+ let members = [String](selectedContacts) + [ masterHexEncodedPublicKey ]
+ let admins = [ masterHexEncodedPublicKey ]
+
let groupID = LKGroupUtilities.getEncodedClosedGroupIDAsData(Randomness.generateRandomBytes(kGroupIdLength)!.toHexString())
let group = TSGroupModel(title: name, memberIds: members, image: nil, groupId: groupID, groupType: .closedGroup, adminIds: admins)
let thread = TSGroupThread.getOrCreateThread(with: group)
diff --git a/Signal/src/ViewControllers/DebugUI/DebugUISyncMessages.m b/Signal/src/ViewControllers/DebugUI/DebugUISyncMessages.m
index cc493cd89..124f14c86 100644
--- a/Signal/src/ViewControllers/DebugUI/DebugUISyncMessages.m
+++ b/Signal/src/ViewControllers/DebugUI/DebugUISyncMessages.m
@@ -108,20 +108,7 @@ NS_ASSUME_NONNULL_BEGIN
+ (void)sendGroupSyncMessage
{
- OWSSyncGroupsMessage *syncGroupsMessage = [[OWSSyncGroupsMessage alloc] init];
- __block DataSource *dataSource;
- [self.dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
- dataSource = [DataSourceValue
- dataSourceWithSyncMessageData:[syncGroupsMessage buildPlainTextAttachmentDataWithTransaction:transaction]];
- }];
-
- [self.messageSenderJobQueue addMediaMessage:syncGroupsMessage
- dataSource:dataSource
- contentType:OWSMimeTypeApplicationOctetStream
- sourceFilename:nil
- caption:nil
- albumMessageId:nil
- isTemporaryAttachment:YES];
+ [[self.syncManager syncAllGroups] retainUntilComplete];
}
+ (void)sendBlockListSyncMessage
diff --git a/Signal/src/ViewControllers/ThreadSettings/UpdateGroupViewController.m b/Signal/src/ViewControllers/ThreadSettings/UpdateGroupViewController.m
index 6da77bb3b..a55c240d2 100644
--- a/Signal/src/ViewControllers/ThreadSettings/UpdateGroupViewController.m
+++ b/Signal/src/ViewControllers/ThreadSettings/UpdateGroupViewController.m
@@ -385,7 +385,6 @@ NS_ASSUME_NONNULL_BEGIN
groupId:self.thread.groupModel.groupId
groupType:self.thread.groupModel.groupType
adminIds:self.thread.groupModel.groupAdminIds];
- groupModel.removedMembers = self.removedRecipientIds;
[self.conversationSettingsViewDelegate groupWasUpdated:groupModel];
}
diff --git a/SignalMessaging/contacts/OWSSyncManager.m b/SignalMessaging/contacts/OWSSyncManager.m
index 287adbedd..d5c2c4574 100644
--- a/SignalMessaging/contacts/OWSSyncManager.m
+++ b/SignalMessaging/contacts/OWSSyncManager.m
@@ -16,12 +16,14 @@
#import
#import
#import
+#import
#import
#import
#import
#import
#import
#import
+#import
NS_ASSUME_NONNULL_BEGIN
@@ -280,13 +282,28 @@ NSString *const kSyncManagerLastContactSyncKey = @"kTSStorageManagerOWSSyncManag
- (AnyPromise *)syncAllContacts
{
NSMutableArray *friends = @[].mutableCopy;
+ NSMutableArray *promises = @[].mutableCopy;
[TSContactThread enumerateCollectionObjectsUsingBlock:^(TSContactThread *thread, BOOL *stop) {
NSString *hexEncodedPublicKey = thread.contactIdentifier;
- if (hexEncodedPublicKey != nil && thread.isContactFriend) {
+ if (hexEncodedPublicKey != nil && thread.isContactFriend && thread.shouldThreadBeVisible && !thread.isForceHidden) {
[friends addObject:[[SignalAccount alloc] initWithRecipientId:hexEncodedPublicKey]];
}
}];
- return [self syncContactsForSignalAccounts:friends];
+ [friends addObject:[[SignalAccount alloc] initWithRecipientId:self.tsAccountManager.localNumber]];
+ NSMutableArray *signalAccounts = @[].mutableCopy;
+ for (SignalAccount *contact in friends) {
+ [signalAccounts addObject:contact];
+ if (signalAccounts.count >= 3) {
+ [promises addObject:[self syncContactsForSignalAccounts:[signalAccounts copy]]];
+ [signalAccounts removeAllObjects];
+ }
+ }
+ if (signalAccounts.count > 0) {
+ [promises addObject:[self syncContactsForSignalAccounts:signalAccounts]];
+ }
+ AnyPromise *promise = PMKJoin(promises);
+ [promise retainUntilComplete];
+ return promise;
}
- (AnyPromise *)syncContactsForSignalAccounts:(NSArray *)signalAccounts
@@ -307,6 +324,49 @@ NSString *const kSyncManagerLastContactSyncKey = @"kTSStorageManagerOWSSyncManag
return promise;
}
+- (AnyPromise *)syncAllGroups
+{
+ NSMutableArray *groupThreads = @[].mutableCopy;
+ NSMutableArray *promises = @[].mutableCopy;
+ [TSGroupThread enumerateCollectionObjectsUsingBlock:^(id obj, BOOL *stop) {
+ if (![obj isKindOfClass:[TSGroupThread class]]) {
+ if (![obj isKindOfClass:[TSContactThread class]]) {
+ OWSLogWarn(@"Ignoring non group thread in thread collection: %@", obj);
+
+ }
+ return;
+ }
+ TSGroupThread *thread = (TSGroupThread *)obj;
+ if (thread.groupModel.groupType == closedGroup && thread.shouldThreadBeVisible && !thread.isForceHidden) {
+ [groupThreads addObject:thread];
+ }
+ }];
+ for (TSGroupThread *groupThread in groupThreads) {
+ [promises addObject:[self syncGroupForThread:groupThread]];
+ }
+ AnyPromise *promise = PMKJoin(promises);
+ [promise retainUntilComplete];
+ return promise;
+}
+
+- (AnyPromise *)syncGroupForThread:(TSGroupThread *)thread
+{
+ OWSSyncGroupsMessage *syncGroupsMessage = [[OWSSyncGroupsMessage alloc] initWithGroupThread:thread];
+ AnyPromise *promise = [AnyPromise promiseWithResolverBlock:^(PMKResolver resolve) {
+ [self.messageSender sendMessage:syncGroupsMessage
+ success:^{
+ OWSLogInfo(@"Successfully sent groups sync message.");
+ resolve(@(1));
+ }
+ failure:^(NSError *error) {
+ OWSLogError(@"Failed to send groups sync message with error: %@.", error);
+ resolve(error);
+ }];
+ }];
+ [promise retainUntilComplete];
+ return promise;
+}
+
@end
NS_ASSUME_NONNULL_END
diff --git a/SignalServiceKit/protobuf/SignalService.proto b/SignalServiceKit/protobuf/SignalService.proto
index aad7efdb2..0c9ee9425 100644
--- a/SignalServiceKit/protobuf/SignalService.proto
+++ b/SignalServiceKit/protobuf/SignalService.proto
@@ -319,6 +319,7 @@ message SyncMessage {
message Groups {
optional AttachmentPointer blob = 1;
+ optional bytes data = 101; // Loki
}
message Blocked {
@@ -399,7 +400,7 @@ message GroupContext {
optional string name = 3;
repeated string members = 4;
optional AttachmentPointer avatar = 5;
- repeated string admins = 6;
+ repeated string admins = 6; // Loki
}
message ContactDetails {
@@ -435,6 +436,7 @@ message GroupDetails {
optional uint32 expireTimer = 6;
optional string color = 7;
optional bool blocked = 8;
+ repeated string admins = 9; // Loki
}
// Internal - DO NOT SEND
diff --git a/SignalServiceKit/src/Contacts/Threads/TSGroupThread.h b/SignalServiceKit/src/Contacts/Threads/TSGroupThread.h
index 4b614371f..a243daa7f 100644
--- a/SignalServiceKit/src/Contacts/Threads/TSGroupThread.h
+++ b/SignalServiceKit/src/Contacts/Threads/TSGroupThread.h
@@ -38,6 +38,8 @@ extern NSString *const TSGroupThread_NotificationKey_UniqueId;
- (BOOL)isLocalUserInGroup;
- (BOOL)isLocalUserInGroupWithTransaction:(YapDatabaseReadTransaction *)transaction;
+- (BOOL)isUserInGroup:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadWriteTransaction *)transaction;
+- (BOOL)isUserAdminForGroup:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadWriteTransaction *)transaction;
// all group threads containing recipient as a member
+ (NSArray *)groupThreadsWithRecipientId:(NSString *)recipientId
diff --git a/SignalServiceKit/src/Contacts/Threads/TSGroupThread.m b/SignalServiceKit/src/Contacts/Threads/TSGroupThread.m
index 31f7cfaeb..98b80ecc3 100644
--- a/SignalServiceKit/src/Contacts/Threads/TSGroupThread.m
+++ b/SignalServiceKit/src/Contacts/Threads/TSGroupThread.m
@@ -204,6 +204,20 @@ NSString *const TSGroupThread_NotificationKey_UniqueId = @"TSGroupThread_Notific
return [linkedDeviceHexEncodedPublicKeys intersectsSet:[NSSet setWithArray:self.groupModel.groupMemberIds]];
}
+- (BOOL)isUserInGroup:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadWriteTransaction *)transaction
+{
+ if (hexEncodedPublicKey == nil) { return NO; }
+ NSSet *linkedDeviceHexEncodedPublicKeys = [LKDatabaseUtilities getLinkedDeviceHexEncodedPublicKeysFor:hexEncodedPublicKey in:transaction];
+ return [linkedDeviceHexEncodedPublicKeys intersectsSet:[NSSet setWithArray:self.groupModel.groupMemberIds]];
+}
+
+- (BOOL)isUserAdminForGroup:(NSString *)hexEncodedPublicKey transaction:(YapDatabaseReadWriteTransaction *)transaction
+{
+ if (hexEncodedPublicKey == nil) { return NO; }
+ NSSet *linkedDeviceHexEncodedPublicKeys = [LKDatabaseUtilities getLinkedDeviceHexEncodedPublicKeysFor:hexEncodedPublicKey in:transaction];
+ return [linkedDeviceHexEncodedPublicKeys intersectsSet:[NSSet setWithArray:self.groupModel.groupAdminIds]];
+}
+
- (NSString *)name
{
// TODO sometimes groupName is set to the empty string. I'm hesitent to change
@@ -227,10 +241,13 @@ NSString *const TSGroupThread_NotificationKey_UniqueId = @"TSGroupThread_Notific
- (void)leaveGroupWithTransaction:(YapDatabaseReadWriteTransaction *)transaction
{
- NSMutableArray *newGroupMemberIds = [self.groupModel.groupMemberIds mutableCopy];
- [newGroupMemberIds removeObject:[TSAccountManager localNumber]];
-
- self.groupModel.groupMemberIds = newGroupMemberIds;
+ NSMutableSet *newGroupMemberIds = [NSMutableSet setWithArray:self.groupModel.groupMemberIds];
+ NSString *userHexEncodedPublicKey = TSAccountManager.localNumber;
+ if (userHexEncodedPublicKey != nil) {
+ NSSet *linkedDeviceHexEncodedPublicKeys = [LKDatabaseUtilities getLinkedDeviceHexEncodedPublicKeysFor:userHexEncodedPublicKey in:transaction];
+ [newGroupMemberIds minusSet:linkedDeviceHexEncodedPublicKeys];
+ }
+ self.groupModel.groupMemberIds = newGroupMemberIds.allObjects;
[self saveWithTransaction:transaction];
}
diff --git a/SignalServiceKit/src/Devices/OWSGroupsOutputStream.m b/SignalServiceKit/src/Devices/OWSGroupsOutputStream.m
index 80ba744a7..fe14f15c3 100644
--- a/SignalServiceKit/src/Devices/OWSGroupsOutputStream.m
+++ b/SignalServiceKit/src/Devices/OWSGroupsOutputStream.m
@@ -26,11 +26,12 @@ NS_ASSUME_NONNULL_BEGIN
[groupBuilder setName:group.groupName];
[groupBuilder setMembers:group.groupMemberIds];
[groupBuilder setColor:groupThread.conversationColorName];
+ [groupBuilder setAdmins:group.groupAdminIds];
if ([OWSBlockingManager.sharedManager isGroupIdBlocked:group.groupId]) {
[groupBuilder setBlocked:YES];
}
-
+ /*
NSData *avatarPng;
if (group.groupImage) {
SSKProtoGroupDetailsAvatarBuilder *avatarBuilder = [SSKProtoGroupDetailsAvatar builder];
@@ -46,7 +47,7 @@ NS_ASSUME_NONNULL_BEGIN
} else {
[groupBuilder setAvatar:avatarProto];
}
- }
+ } */
OWSDisappearingMessagesConfiguration *_Nullable disappearingMessagesConfiguration =
[OWSDisappearingMessagesConfiguration fetchObjectWithUniqueID:groupThread.uniqueId transaction:transaction];
@@ -69,12 +70,12 @@ NS_ASSUME_NONNULL_BEGIN
uint32_t groupDataLength = (uint32_t)groupData.length;
- [self writeVariableLengthUInt32:groupDataLength];
+ [self writeUInt32:groupDataLength];
[self writeData:groupData];
- if (avatarPng) {
- [self writeData:avatarPng];
- }
+// if (avatarPng) {
+// [self writeData:avatarPng];
+// }
}
@end
diff --git a/SignalServiceKit/src/Loki/Messaging/ContactParser.swift b/SignalServiceKit/src/Loki/Messaging/ContactParser.swift
index 7b3dea666..b29e9c218 100644
--- a/SignalServiceKit/src/Loki/Messaging/ContactParser.swift
+++ b/SignalServiceKit/src/Loki/Messaging/ContactParser.swift
@@ -17,7 +17,7 @@
guard let size = uncheckedSize, size < data.count else { break }
let sizeAsInt = Int(size)
index += 4
- guard index + sizeAsInt < data.count else { break }
+ guard index + sizeAsInt <= data.count else { break }
let protoAsData = data[index..<(index+sizeAsInt)]
guard let proto = try? SSKProtoContactDetails.parseData(protoAsData) else { break }
index += sizeAsInt
diff --git a/SignalServiceKit/src/Loki/Messaging/GroupParser.swift b/SignalServiceKit/src/Loki/Messaging/GroupParser.swift
new file mode 100644
index 000000000..3c3fd68b1
--- /dev/null
+++ b/SignalServiceKit/src/Loki/Messaging/GroupParser.swift
@@ -0,0 +1,34 @@
+
+@objc public final class GroupParser : NSObject {
+ private let data: Data
+
+ @objc public init(data: Data) {
+ self.data = data
+ }
+
+ @objc public func parseGroupModels() -> [TSGroupModel] {
+ var index = 0
+ var result: [TSGroupModel] = []
+ while index < data.endIndex {
+ var uncheckedSize: UInt32? = try? data[index..<(index+4)].withUnsafeBytes { $0.pointee }
+ if let size = uncheckedSize, size >= data.count, let intermediate = try? data[index..<(index+4)].reversed() {
+ uncheckedSize = Data(intermediate).withUnsafeBytes { $0.pointee }
+ }
+ guard let size = uncheckedSize, size < data.count else { break }
+ let sizeAsInt = Int(size)
+ index += 4
+ guard index + sizeAsInt <= data.count else { break }
+ let protoAsData = data[index..<(index+sizeAsInt)]
+ guard let proto = try? SSKProtoGroupDetails.parseData(protoAsData) else { break }
+ index += sizeAsInt
+ var groupModel = TSGroupModel.init(title: proto.name,
+ memberIds: proto.members,
+ image: nil,
+ groupId: proto.id,
+ groupType: GroupType.closedGroup,
+ adminIds: proto.admins)
+ result.append(groupModel)
+ }
+ return result
+ }
+}
diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.h b/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.h
index f31996d6b..87c8ea79e 100644
--- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.h
+++ b/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.h
@@ -28,6 +28,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, readonly) uint64_t expirationStartedAt;
@property (nonatomic, readonly) uint32_t expirationDuration;
@property (nonatomic, readonly) BOOL isGroupUpdate;
+@property (nonatomic, readonly) BOOL isGroupQuit;
@property (nonatomic, readonly) BOOL isExpirationTimerUpdate;
@property (nonatomic, readonly) BOOL isEndSessionMessage;
@property (nonatomic, readonly, nullable) NSData *groupId;
diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.m b/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.m
index d3218938a..994c1af86 100644
--- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.m
+++ b/SignalServiceKit/src/Messages/DeviceSyncing/OWSIncomingSentMessageTranscript.m
@@ -34,6 +34,7 @@ NS_ASSUME_NONNULL_BEGIN
_body = _dataMessage.body;
_groupId = _dataMessage.group.id;
_isGroupUpdate = _dataMessage.group != nil && (_dataMessage.group.type == SSKProtoGroupContextTypeUpdate);
+ _isGroupQuit = _dataMessage.group != nil && (_dataMessage.group.type == SSKProtoGroupContextTypeQuit);
_isExpirationTimerUpdate = (_dataMessage.flags & SSKProtoDataMessageFlagsExpirationTimerUpdate) != 0;
_isEndSessionMessage = (_dataMessage.flags & SSKProtoDataMessageFlagsEndSession) != 0;
_isRecipientUpdate = sentProto.isRecipientUpdate;
diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m
index 19bd4263c..2e67f3996 100644
--- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m
+++ b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncContactsMessage.m
@@ -109,21 +109,6 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable NSData *)buildPlainTextAttachmentDataWithTransaction:(YapDatabaseReadTransaction *)transaction
{
NSMutableArray *signalAccounts = [self.signalAccounts mutableCopy];
-
- NSString *_Nullable localNumber = self.tsAccountManager.localNumber;
- OWSAssertDebug(localNumber);
- if (localNumber) {
- BOOL hasLocalNumber = NO;
- for (SignalAccount *signalAccount in signalAccounts) {
- hasLocalNumber |= [signalAccount.recipientId isEqualToString:localNumber];
- }
- if (!hasLocalNumber) {
- SignalAccount *signalAccount = [[SignalAccount alloc] initWithRecipientId:localNumber];
- // OWSContactsOutputStream requires all signalAccount to have a contact.
- signalAccount.contact = [[Contact alloc] initWithSystemContact:[CNContact new]];
- [signalAccounts addObject:signalAccount];
- }
- }
// TODO use temp file stream to avoid loading everything into memory at once
// First though, we need to re-engineer our attachment process to accept streams (encrypting with stream,
diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.h b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.h
index 7f5ba902f..6f7ccc188 100644
--- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.h
+++ b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.h
@@ -7,10 +7,14 @@
NS_ASSUME_NONNULL_BEGIN
@class YapDatabaseReadTransaction;
+@class TSGroupThread;
@interface OWSSyncGroupsMessage : OWSOutgoingSyncMessage
-- (instancetype)init NS_DESIGNATED_INITIALIZER;
+- (instancetype)init NS_UNAVAILABLE;
+
+- (instancetype)initWithGroupThread:(TSGroupThread *)thread NS_DESIGNATED_INITIALIZER;
+
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
- (nullable NSData *)buildPlainTextAttachmentDataWithTransaction:(YapDatabaseReadTransaction *)transaction;
diff --git a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.m b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.m
index f76b36f87..9ee62dd34 100644
--- a/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.m
+++ b/SignalServiceKit/src/Messages/DeviceSyncing/OWSSyncGroupsMessage.m
@@ -11,14 +11,28 @@
#import "TSGroupThread.h"
#import
#import
+#import "OWSPrimaryStorage.h"
NS_ASSUME_NONNULL_BEGIN
+@interface OWSSyncGroupsMessage ()
+
+@property (nonatomic, readonly) TSGroupThread *groupThread;
+
+@end
+
@implementation OWSSyncGroupsMessage
-- (instancetype)init
+- (instancetype)initWithGroupThread:(TSGroupThread *)thread
{
- return [super init];
+ self = [super init];
+ if (!self) {
+ return self;
+ }
+
+ _groupThread = thread;
+
+ return self;
}
- (nullable instancetype)initWithCoder:(NSCoder *)coder
@@ -28,22 +42,32 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable SSKProtoSyncMessageBuilder *)syncMessageBuilder
{
- if (self.attachmentIds.count != 1) {
- OWSLogError(@"expected sync groups message to have exactly one attachment, but found %lu",
- (unsigned long)self.attachmentIds.count);
+ NSError *error;
+ if (self.attachmentIds.count > 1) {
+ OWSLogError(@"Expected sync group message to have one or zero attachments, but found %lu.", (unsigned long)self.attachmentIds.count);
}
-
- SSKProtoAttachmentPointer *_Nullable attachmentProto =
- [TSAttachmentStream buildProtoForAttachmentId:self.attachmentIds.firstObject];
- if (!attachmentProto) {
- OWSFailDebug(@"could not build protobuf.");
- return nil;
+
+ SSKProtoSyncMessageGroupsBuilder *groupsBuilder;
+ if (self.attachmentIds.count == 0) {
+ SSKProtoAttachmentPointerBuilder *attachmentProtoBuilder = [SSKProtoAttachmentPointer builderWithId:0];
+ SSKProtoAttachmentPointer *attachmentProto = [attachmentProtoBuilder buildAndReturnError:&error];
+ groupsBuilder = [SSKProtoSyncMessageGroups builder];
+ [groupsBuilder setBlob:attachmentProto];
+ __block NSData *data;
+ [OWSPrimaryStorage.sharedManager.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
+ data = [self buildPlainTextAttachmentDataWithTransaction:transaction];
+ }];
+ [groupsBuilder setData:data];
+ } else {
+ SSKProtoAttachmentPointer *attachmentProto = [TSAttachmentStream buildProtoForAttachmentId:self.attachmentIds.firstObject];
+ if (attachmentProto == nil) {
+ OWSFailDebug(@"Couldn't build protobuf.");
+ return nil;
+ }
+ groupsBuilder = [SSKProtoSyncMessageGroups builder];
+ [groupsBuilder setBlob:attachmentProto];
}
- SSKProtoSyncMessageGroupsBuilder *groupsBuilder = [SSKProtoSyncMessageGroups builder];
- [groupsBuilder setBlob:attachmentProto];
-
- NSError *error;
SSKProtoSyncMessageGroups *_Nullable groupsProto = [groupsBuilder buildAndReturnError:&error];
if (error || !groupsProto) {
OWSFailDebug(@"could not build protobuf: %@", error);
@@ -64,21 +88,7 @@ NS_ASSUME_NONNULL_BEGIN
NSOutputStream *dataOutputStream = [NSOutputStream outputStreamToMemory];
[dataOutputStream open];
OWSGroupsOutputStream *groupsOutputStream = [[OWSGroupsOutputStream alloc] initWithOutputStream:dataOutputStream];
-
- [TSGroupThread
- enumerateCollectionObjectsWithTransaction:transaction
- usingBlock:^(id obj, BOOL *stop) {
- if (![obj isKindOfClass:[TSGroupThread class]]) {
- if (![obj isKindOfClass:[TSContactThread class]]) {
- OWSLogWarn(
- @"Ignoring non group thread in thread collection: %@", obj);
- }
- return;
- }
- TSGroupThread *groupThread = (TSGroupThread *)obj;
- [groupsOutputStream writeGroup:groupThread transaction:transaction];
- }];
-
+ [groupsOutputStream writeGroup:self.groupThread transaction:transaction];
[dataOutputStream close];
if (groupsOutputStream.hasError) {
diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m
index db0bdccb4..ac3d4806d 100644
--- a/SignalServiceKit/src/Messages/OWSMessageManager.m
+++ b/SignalServiceKit/src/Messages/OWSMessageManager.m
@@ -1020,6 +1020,14 @@ NS_ASSUME_NONNULL_BEGIN
messageType:TSInfoMessageTypeGroupUpdate
customMessage:updateMessage];
[infoMessage saveWithTransaction:transaction];
+ } else if (transcript.isGroupQuit) {
+ TSGroupThread *groupThread = [TSGroupThread getOrCreateThreadWithGroupId:transcript.dataMessage.group.id groupType:closedGroup transaction:transaction];
+ [groupThread leaveGroupWithTransaction:transaction];
+ TSInfoMessage *infoMessage = [[TSInfoMessage alloc] initWithTimestamp:NSDate.ows_millisecondTimeStamp
+ inThread:groupThread
+ messageType:TSInfoMessageTypeGroupQuit
+ customMessage:NSLocalizedString(@"GROUP_YOU_LEFT", nil)];
+ [infoMessage saveWithTransaction:transaction];
} else {
[OWSRecordTranscriptJob
processIncomingSentMessageTranscript:transcript
@@ -1043,20 +1051,9 @@ NS_ASSUME_NONNULL_BEGIN
[[self.syncManager syncAllContacts] retainUntilComplete];
});
} else if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeGroups) {
- OWSSyncGroupsMessage *syncGroupsMessage = [[OWSSyncGroupsMessage alloc] init];
- NSData *_Nullable syncData = [syncGroupsMessage buildPlainTextAttachmentDataWithTransaction:transaction];
- if (!syncData) {
- OWSFailDebug(@"Failed to serialize groups sync message.");
- return;
- }
- DataSource *dataSource = [DataSourceValue dataSourceWithSyncMessageData:syncData];
- [self.messageSenderJobQueue addMediaMessage:syncGroupsMessage
- dataSource:dataSource
- contentType:OWSMimeTypeApplicationOctetStream
- sourceFilename:nil
- caption:nil
- albumMessageId:nil
- isTemporaryAttachment:YES];
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
+ [[self.syncManager syncAllGroups] retainUntilComplete];
+ });
} else if (syncMessage.request.type == SSKProtoSyncMessageRequestTypeBlocked) {
OWSLogInfo(@"Received request for block list");
[self.blockingManager syncBlockList];
@@ -1080,7 +1077,7 @@ NS_ASSUME_NONNULL_BEGIN
[self.identityManager throws_processIncomingSyncMessage:syncMessage.verified transaction:transaction];
} else if (syncMessage.contacts != nil) {
if (wasSentByMasterDevice && syncMessage.contacts.data.length > 0) {
- NSLog(@"[Loki] Received contact sync message.");
+ OWSLogInfo(@"[Loki] Received contact sync message.");
NSData *data = syncMessage.contacts.data;
ContactParser *parser = [[ContactParser alloc] initWithData:data];
NSArray *hexEncodedPublicKeys = [parser parseHexEncodedPublicKeys];
@@ -1091,10 +1088,8 @@ NS_ASSUME_NONNULL_BEGIN
switch (friendRequestStatus) {
case LKThreadFriendRequestStatusNone: {
OWSMessageSender *messageSender = SSKEnvironment.shared.messageSender;
- OWSMessageSend *automatedFriendRequestMessage = [messageSender getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:hexEncodedPublicKey];
- dispatch_async(OWSDispatch.sendingQueue, ^{
- [messageSender sendMessage:automatedFriendRequestMessage];
- });
+ LKFriendRequestMessage *automatedFriendRequestMessage = [messageSender getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:hexEncodedPublicKey inThread:thread transaction:transaction];
+ [self.messageSenderJobQueue addMessage:automatedFriendRequestMessage transaction:transaction];
break;
}
case LKThreadFriendRequestStatusRequestReceived: {
@@ -1108,6 +1103,26 @@ NS_ASSUME_NONNULL_BEGIN
}
}
}
+ } else if (syncMessage.groups != nil) {
+ if (wasSentByMasterDevice && syncMessage.groups.data.length > 0) {
+ OWSLogInfo(@"[Loki] Received group sync message.");
+ NSData *data = syncMessage.groups.data;
+ GroupParser *parser = [[GroupParser alloc] initWithData:data];
+ NSArray *groupModels = [parser parseGroupModels];
+ for (TSGroupModel *groupModel in groupModels) {
+ TSGroupThread *thread = [TSGroupThread threadWithGroupId:groupModel.groupId transaction:transaction];
+ if (thread == nil) {
+ thread = [TSGroupThread getOrCreateThreadWithGroupModel:groupModel transaction:transaction];
+ [thread saveWithTransaction:transaction];
+ [self establishSessionsWithMembersIfNeeded:groupModel.groupMemberIds forThread:thread transaction:transaction];
+ TSInfoMessage *infoMessage = [[TSInfoMessage alloc] initWithTimestamp:NSDate.ows_millisecondTimeStamp
+ inThread:thread
+ messageType:TSInfoMessageTypeGroupUpdate
+ customMessage:@"You have joined the group."];
+ [infoMessage saveWithTransaction:transaction];
+ }
+ }
+ }
} else {
OWSLogWarn(@"Ignoring unsupported sync message.");
}
@@ -1325,7 +1340,7 @@ NS_ASSUME_NONNULL_BEGIN
}
// Ensure sender is in the group.
- if (![gThread.groupModel.groupMemberIds containsObject:envelope.source]) {
+ if (![gThread isUserInGroup:envelope.source transaction:transaction]) {
OWSLogWarn(@"Ignoring 'Request Group Info' message for non-member of group. %@ not in %@",
envelope.source,
gThread.groupModel.groupMemberIds);
@@ -1439,7 +1454,7 @@ NS_ASSUME_NONNULL_BEGIN
switch (dataMessage.group.type) {
case SSKProtoGroupContextTypeUpdate: {
- if (oldGroupThread && ![oldGroupThread.groupModel.groupAdminIds containsObject:hexEncodedPublicKey]) {
+ if (oldGroupThread && ![oldGroupThread isUserAdminForGroup:hexEncodedPublicKey transaction:transaction]) {
[LKLogger print:[NSString stringWithFormat:@"[Loki] Received a group update from a non-admin user for %@; ignoring.", [LKGroupUtilities getEncodedGroupID:groupId]]];
return nil;
}
diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.h b/SignalServiceKit/src/Messages/OWSMessageSender.h
index f223aadcf..b6af35dc2 100644
--- a/SignalServiceKit/src/Messages/OWSMessageSender.h
+++ b/SignalServiceKit/src/Messages/OWSMessageSender.h
@@ -3,6 +3,8 @@
//
#import "DataSource.h"
+#import "TSContactThread.h"
+#import "LKFriendRequestMessage.h"
NS_ASSUME_NONNULL_BEGIN
@@ -98,6 +100,7 @@ NS_SWIFT_NAME(MessageSender)
failure:(void (^)(NSError *error))failureHandler;
- (OWSMessageSend *)getSessionRestoreMessageForHexEncodedPublicKey:(NSString *)hexEncodedPublicKey;
+- (LKFriendRequestMessage *)getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:(NSString *)hexEncodedPublicKey inThread:(TSContactThread *)thread transaction:(YapDatabaseReadWriteTransaction *)transaction;
- (OWSMessageSend *)getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:(NSString *)hexEncodedPublicKey;
- (void)sendMessage:(OWSMessageSend *)messageSend;
diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m
index 066d66206..168e67097 100644
--- a/SignalServiceKit/src/Messages/OWSMessageSender.m
+++ b/SignalServiceKit/src/Messages/OWSMessageSender.m
@@ -937,6 +937,23 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
return [[OWSMessageSend alloc] initWithMessage:message thread:thread recipient:recipient senderCertificate:senderCertificate udAccess:theirUDAccess localNumber:userHexEncodedPublicKey success:^{ } failure:^(NSError *error) { }];
}
+- (LKFriendRequestMessage *)getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:(NSString *)hexEncodedPublicKey inThread:(TSContactThread *)thread transaction:(YapDatabaseReadWriteTransaction *)transaction
+{
+ thread = [TSContactThread getOrCreateThreadWithContactId:hexEncodedPublicKey transaction:transaction];
+ // Force hide slave device thread
+ NSString *masterHexEncodedPublicKey = [LKDatabaseUtilities getMasterHexEncodedPublicKeyFor:hexEncodedPublicKey in:transaction];
+ thread.isForceHidden = masterHexEncodedPublicKey != nil && ![masterHexEncodedPublicKey isEqualToString:hexEncodedPublicKey];
+ if (thread.friendRequestStatus == LKThreadFriendRequestStatusNone || thread.friendRequestStatus == LKThreadFriendRequestStatusRequestExpired) {
+ [thread saveFriendRequestStatus:LKThreadFriendRequestStatusRequestSent withTransaction:transaction];
+ }
+ [thread saveWithTransaction:transaction];
+ LKFriendRequestMessage *message = [[LKFriendRequestMessage alloc] initOutgoingMessageWithTimestamp:NSDate.ows_millisecondTimeStamp inThread:thread messageBody:@"Please accept to enable messages to be synced across devices" attachmentIds:[NSMutableArray new]
+ expiresInSeconds:0 expireStartedAt:0 isVoiceMessage:NO groupMetaMessage:TSGroupMetaMessageUnspecified quotedMessage:nil contactShare:nil linkPreview:nil];
+ message.skipSave = YES;
+ [message saveWithTransaction:transaction];
+ return message;
+}
+
- (OWSMessageSend *)getMultiDeviceFriendRequestMessageForHexEncodedPublicKey:(NSString *)hexEncodedPublicKey
{
__block TSContactThread *thread;
diff --git a/SignalServiceKit/src/Protos/Generated/SSKProto.swift b/SignalServiceKit/src/Protos/Generated/SSKProto.swift
index c94ca5861..1884be9fd 100644
--- a/SignalServiceKit/src/Protos/Generated/SSKProto.swift
+++ b/SignalServiceKit/src/Protos/Generated/SSKProto.swift
@@ -4551,6 +4551,9 @@ extension SSKProtoSyncMessageContacts.SSKProtoSyncMessageContactsBuilder {
if let _value = blob {
builder.setBlob(_value)
}
+ if let _value = data {
+ builder.setData(_value)
+ }
return builder
}
@@ -4563,6 +4566,10 @@ extension SSKProtoSyncMessageContacts.SSKProtoSyncMessageContactsBuilder {
@objc public func setBlob(_ valueParam: SSKProtoAttachmentPointer) {
proto.blob = valueParam.proto
}
+
+ @objc public func setData(_ valueParam: Data) {
+ proto.data = valueParam
+ }
@objc public func build() throws -> SSKProtoSyncMessageGroups {
return try SSKProtoSyncMessageGroups.parseProto(proto)
@@ -4576,6 +4583,16 @@ extension SSKProtoSyncMessageContacts.SSKProtoSyncMessageContactsBuilder {
fileprivate let proto: SignalServiceProtos_SyncMessage.Groups
@objc public let blob: SSKProtoAttachmentPointer?
+
+ @objc public var data: Data? {
+ guard proto.hasData else {
+ return nil
+ }
+ return proto.data
+ }
+ @objc public var hasData: Bool {
+ return proto.hasData
+ }
private init(proto: SignalServiceProtos_SyncMessage.Groups,
blob: SSKProtoAttachmentPointer?) {
@@ -6343,6 +6360,16 @@ extension SSKProtoGroupDetailsAvatar.SSKProtoGroupDetailsAvatarBuilder {
@objc public func setMembers(_ wrappedItems: [String]) {
proto.members = wrappedItems
}
+
+ @objc public func addAdmins(_ valueParam: String) {
+ var items = proto.admins
+ items.append(valueParam)
+ proto.admins = items
+ }
+
+ @objc public func setAdmins(_ wrappedItems: [String]) {
+ proto.admins = wrappedItems
+ }
@objc public func setAvatar(_ valueParam: SSKProtoGroupDetailsAvatar) {
proto.avatar = valueParam.proto
@@ -6392,6 +6419,10 @@ extension SSKProtoGroupDetailsAvatar.SSKProtoGroupDetailsAvatarBuilder {
@objc public var members: [String] {
return proto.members
}
+
+ @objc public var admins: [String] {
+ return proto.admins
+ }
@objc public var active: Bool {
return proto.active
diff --git a/SignalServiceKit/src/Protos/Generated/SignalService.pb.swift b/SignalServiceKit/src/Protos/Generated/SignalService.pb.swift
index 2b433616e..f81521781 100644
--- a/SignalServiceKit/src/Protos/Generated/SignalService.pb.swift
+++ b/SignalServiceKit/src/Protos/Generated/SignalService.pb.swift
@@ -1945,6 +1945,16 @@ struct SignalServiceProtos_SyncMessage {
var hasBlob: Bool {return _storage._blob != nil}
/// Clears the value of `blob`. Subsequent reads from it will return its default value.
mutating func clearBlob() {_uniqueStorage()._blob = nil}
+
+ /// Loki
+ var data: Data {
+ get {return _storage._data ?? SwiftProtobuf.Internal.emptyData}
+ set {_uniqueStorage()._data = newValue}
+ }
+ /// Returns true if `data` has been explicitly set.
+ var hasData: Bool {return _storage._data != nil}
+ /// Clears the value of `data`. Subsequent reads from it will return its default value.
+ mutating func clearData() {_uniqueStorage()._data = nil}
var unknownFields = SwiftProtobuf.UnknownStorage()
@@ -2534,6 +2544,12 @@ struct SignalServiceProtos_GroupDetails {
get {return _storage._members}
set {_uniqueStorage()._members = newValue}
}
+
+ ///Loki
+ var admins: [String] {
+ get {return _storage._admins}
+ set {_uniqueStorage()._admins = newValue}
+ }
var avatar: SignalServiceProtos_GroupDetails.Avatar {
get {return _storage._avatar ?? SignalServiceProtos_GroupDetails.Avatar()}
@@ -4627,10 +4643,12 @@ extension SignalServiceProtos_SyncMessage.Groups: SwiftProtobuf.Message, SwiftPr
static let protoMessageName: String = SignalServiceProtos_SyncMessage.protoMessageName + ".Groups"
static let _protobuf_nameMap: SwiftProtobuf._NameMap = [
1: .same(proto: "blob"),
+ 101: .same(proto: "data"),
]
fileprivate class _StorageClass {
var _blob: SignalServiceProtos_AttachmentPointer? = nil
+ var _data: Data? = nil
static let defaultInstance = _StorageClass()
@@ -4638,6 +4656,7 @@ extension SignalServiceProtos_SyncMessage.Groups: SwiftProtobuf.Message, SwiftPr
init(copying source: _StorageClass) {
_blob = source._blob
+ _data = source._data
}
}
@@ -4654,6 +4673,7 @@ extension SignalServiceProtos_SyncMessage.Groups: SwiftProtobuf.Message, SwiftPr
while let fieldNumber = try decoder.nextFieldNumber() {
switch fieldNumber {
case 1: try decoder.decodeSingularMessageField(value: &_storage._blob)
+ case 101: try decoder.decodeSingularBytesField(value: &_storage._data)
default: break
}
}
@@ -4665,6 +4685,9 @@ extension SignalServiceProtos_SyncMessage.Groups: SwiftProtobuf.Message, SwiftPr
if let v = _storage._blob {
try visitor.visitSingularMessageField(value: v, fieldNumber: 1)
}
+ if let v = _storage._data {
+ try visitor.visitSingularBytesField(value: v, fieldNumber: 101)
+ }
}
try unknownFields.traverse(visitor: &visitor)
}
@@ -4675,6 +4698,7 @@ extension SignalServiceProtos_SyncMessage.Groups: SwiftProtobuf.Message, SwiftPr
let _storage = _args.0
let rhs_storage = _args.1
if _storage._blob != rhs_storage._blob {return false}
+ if _storage._data != rhs_storage._data {return false}
return true
}
if !storagesAreEqual {return false}
@@ -5223,6 +5247,7 @@ extension SignalServiceProtos_GroupDetails: SwiftProtobuf.Message, SwiftProtobuf
6: .same(proto: "expireTimer"),
7: .same(proto: "color"),
8: .same(proto: "blocked"),
+ 9: .same(proto: "admins"),
]
fileprivate class _StorageClass {
@@ -5234,6 +5259,7 @@ extension SignalServiceProtos_GroupDetails: SwiftProtobuf.Message, SwiftProtobuf
var _expireTimer: UInt32? = nil
var _color: String? = nil
var _blocked: Bool? = nil
+ var _admins: [String] = []
static let defaultInstance = _StorageClass()
@@ -5248,6 +5274,7 @@ extension SignalServiceProtos_GroupDetails: SwiftProtobuf.Message, SwiftProtobuf
_expireTimer = source._expireTimer
_color = source._color
_blocked = source._blocked
+ _admins = source._admins
}
}
@@ -5271,6 +5298,7 @@ extension SignalServiceProtos_GroupDetails: SwiftProtobuf.Message, SwiftProtobuf
case 6: try decoder.decodeSingularUInt32Field(value: &_storage._expireTimer)
case 7: try decoder.decodeSingularStringField(value: &_storage._color)
case 8: try decoder.decodeSingularBoolField(value: &_storage._blocked)
+ case 9: try decoder.decodeRepeatedStringField(value: &_storage._admins)
default: break
}
}
@@ -5303,6 +5331,9 @@ extension SignalServiceProtos_GroupDetails: SwiftProtobuf.Message, SwiftProtobuf
if let v = _storage._blocked {
try visitor.visitSingularBoolField(value: v, fieldNumber: 8)
}
+ if !_storage._admins.isEmpty {
+ try visitor.visitRepeatedStringField(value: _storage._admins, fieldNumber: 9)
+ }
}
try unknownFields.traverse(visitor: &visitor)
}
@@ -5320,6 +5351,7 @@ extension SignalServiceProtos_GroupDetails: SwiftProtobuf.Message, SwiftProtobuf
if _storage._expireTimer != rhs_storage._expireTimer {return false}
if _storage._color != rhs_storage._color {return false}
if _storage._blocked != rhs_storage._blocked {return false}
+ if _storage._admins != rhs_storage._admins {return false}
return true
}
if !storagesAreEqual {return false}
diff --git a/SignalServiceKit/src/TestUtils/OWSMockSyncManager.swift b/SignalServiceKit/src/TestUtils/OWSMockSyncManager.swift
index a3dcb9187..e408b8aaf 100644
--- a/SignalServiceKit/src/TestUtils/OWSMockSyncManager.swift
+++ b/SignalServiceKit/src/TestUtils/OWSMockSyncManager.swift
@@ -9,6 +9,12 @@ import PromiseKit
@objc
public class OWSMockSyncManager: NSObject, OWSSyncManagerProtocol {
+ public func syncAllGroups() -> AnyPromise {
+ Logger.info("")
+
+ return AnyPromise()
+ }
+
@objc public func sendConfigurationSyncMessage() {
Logger.info("")
diff --git a/SignalServiceKit/src/Util/OWSSyncManagerProtocol.h b/SignalServiceKit/src/Util/OWSSyncManagerProtocol.h
index eafb2e6fa..e0b78c36b 100644
--- a/SignalServiceKit/src/Util/OWSSyncManagerProtocol.h
+++ b/SignalServiceKit/src/Util/OWSSyncManagerProtocol.h
@@ -20,6 +20,8 @@ NS_ASSUME_NONNULL_BEGIN
- (AnyPromise *)syncContactsForSignalAccounts:(NSArray *)signalAccounts __attribute__((warn_unused_result));
+- (AnyPromise *)syncAllGroups __attribute__((warn_unused_result));
+
@end
NS_ASSUME_NONNULL_END