Merge pull request #104 from RyanRory/sync-closed-group
Closed group functionality and synchronisation
This commit is contained in:
commit
eebff42412
2
Pods
2
Pods
|
@ -1 +1 @@
|
|||
Subproject commit 2870e676deec6a7ddb931edb6f0284f1f5b36085
|
||||
Subproject commit 67dbced37481e0011a3df1397ed57711384a4957
|
|
@ -5,9 +5,9 @@
|
|||
<key>BuildDetails</key>
|
||||
<dict>
|
||||
<key>CarthageVersion</key>
|
||||
<string>0.33.0</string>
|
||||
<string>0.34.0</string>
|
||||
<key>OSXVersion</key>
|
||||
<string>10.15.3</string>
|
||||
<string>10.15.2</string>
|
||||
<key>WebRTCCommit</key>
|
||||
<string>1445d719bf05280270e9f77576f80f973fd847f8 M73</string>
|
||||
</dict>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.")
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
|
||||
|
|
|
@ -16,12 +16,14 @@
|
|||
#import <SignalServiceKit/OWSPrimaryStorage.h>
|
||||
#import <SignalServiceKit/OWSSyncConfigurationMessage.h>
|
||||
#import <SignalServiceKit/OWSSyncContactsMessage.h>
|
||||
#import <SignalServiceKit/OWSSyncGroupsMessage.h>
|
||||
#import <SignalServiceKit/SSKEnvironment.h>
|
||||
#import <SignalServiceKit/SignalAccount.h>
|
||||
#import <SignalServiceKit/SignalServiceKit-Swift.h>
|
||||
#import <SignalServiceKit/TSAccountManager.h>
|
||||
#import <SignalServiceKit/YapDatabaseConnection+OWS.h>
|
||||
#import <SignalServiceKit/TSContactThread.h>
|
||||
#import <SignalServiceKit/TSGroupThread.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
|
@ -280,13 +282,28 @@ NSString *const kSyncManagerLastContactSyncKey = @"kTSStorageManagerOWSSyncManag
|
|||
- (AnyPromise *)syncAllContacts
|
||||
{
|
||||
NSMutableArray<SignalAccount *> *friends = @[].mutableCopy;
|
||||
NSMutableArray<AnyPromise *> *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<SignalAccount *> *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<SignalAccount *> *)signalAccounts
|
||||
|
@ -307,6 +324,49 @@ NSString *const kSyncManagerLastContactSyncKey = @"kTSStorageManagerOWSSyncManag
|
|||
return promise;
|
||||
}
|
||||
|
||||
- (AnyPromise *)syncAllGroups
|
||||
{
|
||||
NSMutableArray<TSGroupThread *> *groupThreads = @[].mutableCopy;
|
||||
NSMutableArray<AnyPromise *> *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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<TSGroupThread *> *)groupThreadsWithRecipientId:(NSString *)recipientId
|
||||
|
|
|
@ -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<NSString *> *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<NSString *> *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<NSString *> *newGroupMemberIds = [self.groupModel.groupMemberIds mutableCopy];
|
||||
[newGroupMemberIds removeObject:[TSAccountManager localNumber]];
|
||||
|
||||
self.groupModel.groupMemberIds = newGroupMemberIds;
|
||||
NSMutableSet<NSString *> *newGroupMemberIds = [NSMutableSet setWithArray:self.groupModel.groupMemberIds];
|
||||
NSString *userHexEncodedPublicKey = TSAccountManager.localNumber;
|
||||
if (userHexEncodedPublicKey != nil) {
|
||||
NSSet<NSString *> *linkedDeviceHexEncodedPublicKeys = [LKDatabaseUtilities getLinkedDeviceHexEncodedPublicKeysFor:userHexEncodedPublicKey in:transaction];
|
||||
[newGroupMemberIds minusSet:linkedDeviceHexEncodedPublicKeys];
|
||||
}
|
||||
self.groupModel.groupMemberIds = newGroupMemberIds.allObjects;
|
||||
[self saveWithTransaction:transaction];
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -109,21 +109,6 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
- (nullable NSData *)buildPlainTextAttachmentDataWithTransaction:(YapDatabaseReadTransaction *)transaction
|
||||
{
|
||||
NSMutableArray<SignalAccount *> *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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -11,14 +11,28 @@
|
|||
#import "TSGroupThread.h"
|
||||
#import <SignalCoreKit/NSDate+OWS.h>
|
||||
#import <SignalServiceKit/SignalServiceKit-Swift.h>
|
||||
#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);
|
||||
}
|
||||
|
||||
SSKProtoAttachmentPointer *_Nullable attachmentProto =
|
||||
[TSAttachmentStream buildProtoForAttachmentId:self.attachmentIds.firstObject];
|
||||
if (!attachmentProto) {
|
||||
OWSFailDebug(@"could not build protobuf.");
|
||||
return nil;
|
||||
}
|
||||
|
||||
SSKProtoSyncMessageGroupsBuilder *groupsBuilder = [SSKProtoSyncMessageGroups builder];
|
||||
[groupsBuilder setBlob:attachmentProto];
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
|
@ -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<NSString *> *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<TSGroupModel *> *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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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("")
|
||||
|
|
|
@ -20,6 +20,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
- (AnyPromise *)syncContactsForSignalAccounts:(NSArray<SignalAccount *> *)signalAccounts __attribute__((warn_unused_result));
|
||||
|
||||
- (AnyPromise *)syncAllGroups __attribute__((warn_unused_result));
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
|
Loading…
Reference in New Issue