Merge pull request #37 from loki-project/group-chats

Group Chats Part 2
This commit is contained in:
gmbnt 2019-08-22 09:22:24 +10:00 committed by GitHub
commit f03dc9a254
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 130 additions and 54 deletions

View File

@ -560,6 +560,7 @@
B8162F0522892C5F00D46544 /* FriendRequestViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8162F0422892C5F00D46544 /* FriendRequestViewDelegate.swift */; };
B821F2F82272CED3002C88C0 /* AccountDetailsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B821F2F72272CED3002C88C0 /* AccountDetailsViewController.swift */; };
B821F2FA2272CEEE002C88C0 /* SeedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B821F2F92272CEEE002C88C0 /* SeedViewController.swift */; };
B845B4D4230CD09100D759F0 /* LokiGroupChatPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = B845B4D3230CD09000D759F0 /* LokiGroupChatPoller.swift */; };
B846365B22B7418B00AF1514 /* Identicon+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */; };
B89841E322B7579F00B1BDC6 /* NewConversationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B89841E222B7579F00B1BDC6 /* NewConversationViewController.swift */; };
B90418E6183E9DD40038554A /* DateUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = B90418E5183E9DD40038554A /* DateUtil.m */; };
@ -1349,6 +1350,7 @@
B8162F0422892C5F00D46544 /* FriendRequestViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FriendRequestViewDelegate.swift; sourceTree = "<group>"; };
B821F2F72272CED3002C88C0 /* AccountDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDetailsViewController.swift; sourceTree = "<group>"; };
B821F2F92272CEEE002C88C0 /* SeedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedViewController.swift; sourceTree = "<group>"; };
B845B4D3230CD09000D759F0 /* LokiGroupChatPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LokiGroupChatPoller.swift; sourceTree = "<group>"; };
B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Identicon+ObjC.swift"; sourceTree = "<group>"; };
B89841E222B7579F00B1BDC6 /* NewConversationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationViewController.swift; sourceTree = "<group>"; };
B90418E4183E9DD40038554A /* DateUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateUtil.h; sourceTree = "<group>"; };
@ -2612,6 +2614,7 @@
B821F2F72272CED3002C88C0 /* AccountDetailsViewController.swift */,
B821F2F92272CEEE002C88C0 /* SeedViewController.swift */,
24A830A12293CD0100F4CAC0 /* LokiP2PServer.swift */,
B845B4D3230CD09000D759F0 /* LokiGroupChatPoller.swift */,
);
path = Loki;
sourceTree = "<group>";
@ -3627,6 +3630,7 @@
34A4C62022175C5C0042EF2E /* OnboardingProfileViewController.swift in Sources */,
4505C2BF1E648EA300CEBF41 /* ExperienceUpgrade.swift in Sources */,
EF764C351DB67CC5000D9A87 /* UIViewController+Permissions.m in Sources */,
B845B4D4230CD09100D759F0 /* LokiGroupChatPoller.swift in Sources */,
45CD81EF1DC030E7004C9430 /* SyncPushTokensJob.swift in Sources */,
34D2CCE0206939B400CB1A14 /* DebugUIMessagesAssetLoader.m in Sources */,
4CEB78C92178EBAB00F315D2 /* OWSSessionResetJobRecord.m in Sources */,

View File

@ -8,4 +8,6 @@ extern NSString *const AppDelegateStoryboardMain;
@interface AppDelegate : UIResponder <UIApplicationDelegate>
- (void)startPublicChatPollingIfNeeded;
@end

View File

@ -63,7 +63,8 @@ static NSTimeInterval launchStartedAt;
@property (nonatomic) BOOL hasInitialRootViewController;
@property (nonatomic) BOOL areVersionMigrationsComplete;
@property (nonatomic) BOOL didAppLaunchFail;
@property (nonatomic) LKP2PServer *lokiP2PServer; // Loki
@property (nonatomic) LKP2PServer *lokiP2PServer;
@property (nonatomic) LKGroupChatPoller *lokiPublicChatPoller;
@end
@ -758,7 +759,7 @@ static NSTimeInterval launchStartedAt;
[Environment.shared.contactsManager fetchSystemContactsOnceIfAlreadyAuthorized];
// Loki: Start long polling
[LKAPI startLongPollingIfNecessary];
[LKAPI startLongPollingIfNeeded];
// Loki: Tell our friends that we are online
[LKP2PAPI broadcastOnlineStatus];
@ -1357,7 +1358,7 @@ static NSTimeInterval launchStartedAt;
[self.readReceiptManager setAreReadReceiptsEnabled:YES];
// Start long polling
[LKAPI startLongPollingIfNecessary];
[LKAPI startLongPollingIfNeeded];
}
}
@ -1482,4 +1483,29 @@ static NSTimeInterval launchStartedAt;
OWSLogInfo(@"");
}
#pragma mark - Loki
- (void)setUpPublicChatIfNeeded
{
if (self.lokiPublicChatPoller != nil) { return; }
self.lokiPublicChatPoller = [[LKGroupChatPoller alloc] initWithGroup:(NSUInteger)LKGroupChatAPI.publicChatID];
BOOL isPublicChatSetUp = [NSUserDefaults.standardUserDefaults boolForKey:@"isPublicChatSetUp"];
if (isPublicChatSetUp) { return; }
NSString *title = NSLocalizedString(@"Loki Public Chat", @"");
NSData *groupID = [[[LKGroupChatAPI.serverURL stringByAppendingString:@"."] stringByAppendingString:@(LKGroupChatAPI.publicChatID).stringValue] dataUsingEncoding:NSUTF8StringEncoding];
TSGroupModel *group = [[TSGroupModel alloc] initWithTitle:title memberIds:@[ OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey ] image:nil groupId:groupID];
__block TSGroupThread *thread;
[OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
thread = [TSGroupThread getOrCreateThreadWithGroupModel:group transaction:transaction];
}];
[OWSProfileManager.sharedManager addThreadToProfileWhitelist:thread];
[NSUserDefaults.standardUserDefaults setBool:YES forKey:@"isPublicChatSetUp"];
}
- (void)startPublicChatPollingIfNeeded
{
[self setUpPublicChatIfNeeded];
[self.lokiPublicChatPoller startIfNeeded];
}
@end

View File

@ -0,0 +1,53 @@
@objc(LKGroupChatPoller)
public final class LokiGroupChatPoller : NSObject {
private let group: UInt
private var timer: Timer? = nil
private var hasStarted = false
private let pollInterval: TimeInterval = 4
@objc public init(group: UInt) {
self.group = group
super.init()
}
@objc public func startIfNeeded() {
if hasStarted { return }
timer = Timer.scheduledTimer(withTimeInterval: pollInterval, repeats: true) { [weak self] _ in self?.poll() }
hasStarted = true
}
@objc public func stop() {
timer?.invalidate()
hasStarted = false
}
private func poll() {
let group = self.group
let _ = LokiGroupChatAPI.getMessages(for: group).map { messages in
messages.reversed().map { message in
let id = "\(LokiGroupChatAPI.serverURL).\(group)".data(using: String.Encoding.utf8)!
let x1 = SSKProtoGroupContext.builder(id: id, type: .deliver)
x1.setName(NSLocalizedString("Loki Public Chat", comment: ""))
let x2 = SSKProtoDataMessage.builder()
x2.setTimestamp(message.timestamp)
x2.setGroup(try! x1.build())
x2.setBody(message.body)
let x3 = SSKProtoContent.builder()
x3.setDataMessage(try! x2.build())
let x4 = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: message.timestamp)
let senderHexEncodedPublicKey = message.hexEncodedPublicKey
let endIndex = senderHexEncodedPublicKey.endIndex
let cutoffIndex = senderHexEncodedPublicKey.index(endIndex, offsetBy: -8)
let senderDisplayName = "\(message.displayName) (...\(senderHexEncodedPublicKey[cutoffIndex..<endIndex]))"
x4.setSource(senderDisplayName)
x4.setSourceDevice(OWSDevicePrimaryDeviceId)
x4.setContent(try! x3.build().serializedData())
OWSPrimaryStorage.shared().dbReadWriteConnection.readWrite { transaction in
SSKEnvironment.shared.messageManager.throws_processEnvelope(try! x4.build(), plaintextData: try! x3.build().serializedData(), wasReceivedByUD: false, transaction: transaction)
}
}
}
}
}

View File

@ -221,10 +221,10 @@ final class SeedViewController : OnboardingBaseViewController {
}
}
// Use KVC to access dbConnection even though it's private
let databaseConnection = OWSIdentityManager.shared().value(forKey: "dbConnection") as! YapDatabaseConnection
let identityManager = OWSIdentityManager.shared()
let databaseConnection = identityManager.value(forKey: "dbConnection") as! YapDatabaseConnection
databaseConnection.setObject(seed.toHexString(), forKey: "LKLokiSeed", inCollection: OWSPrimaryStorageIdentityKeyStoreCollection)
if seed.count == 16 { seed = seed + seed }
let identityManager = OWSIdentityManager.shared()
identityManager.generateNewIdentityKeyPair(fromSeed: seed) // This also stores it
let keyPair = identityManager.identityKeyPair()!
let hexEncodedPublicKey = keyPair.hexEncodedPublicKey

View File

@ -74,7 +74,9 @@ class ConversationViewItemActions: NSObject {
class func textActions(conversationViewItem: ConversationViewItem, shouldAllowReply: Bool, delegate: MessageActionsDelegate) -> [MenuAction] {
var actions: [MenuAction] = []
if shouldAllowReply {
let isGroup = conversationViewItem.isGroupThread
if shouldAllowReply && !isGroup {
let replyAction = MessageActionBuilder.reply(conversationViewItem: conversationViewItem, delegate: delegate)
actions.append(replyAction)
}
@ -84,8 +86,10 @@ class ConversationViewItemActions: NSObject {
actions.append(copyTextAction)
}
let deleteAction = MessageActionBuilder.deleteMessage(conversationViewItem: conversationViewItem, delegate: delegate)
actions.append(deleteAction)
if !isGroup {
let deleteAction = MessageActionBuilder.deleteMessage(conversationViewItem: conversationViewItem, delegate: delegate)
actions.append(deleteAction)
}
let showDetailsAction = MessageActionBuilder.showDetails(conversationViewItem: conversationViewItem, delegate: delegate)
actions.append(showDetailsAction)

View File

@ -97,8 +97,10 @@ public class ConversationHeaderView: UIStackView {
self.addArrangedSubview(avatarView)
self.addArrangedSubview(textRows)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapView))
self.addGestureRecognizer(tapGesture)
if (!thread.isGroupThread()) {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapView))
self.addGestureRecognizer(tapGesture)
}
}
required public init(coder: NSCoder) {

View File

@ -1601,7 +1601,9 @@ typedef enum : NSUInteger {
}]];
self.headerView.attributedSubtitle = subtitleText;
if (!self.thread.isGroupThread) {
self.headerView.attributedSubtitle = subtitleText;
}
}
#pragma mark - Updating

View File

@ -690,6 +690,10 @@ typedef NS_ENUM(NSInteger, HomeViewControllerSection) {
[alert addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"Cancel", @"") style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) { /* Do nothing */ }]];
[self presentAlert:alert];
}
if (OWSIdentityManager.sharedManager.identityKeyPair != nil) {
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
[appDelegate startPublicChatPollingIfNeeded];
}
}
- (void)applyDefaultBackButton

View File

@ -2606,3 +2606,4 @@
"Cancel" = "Cancel";
"Update Required" = "Update Required";
"This version of Loki Messenger is no longer supported. Please press OK to reset your account and migrate to the latest version." = "This version of Loki Messenger is no longer supported. Please press OK to reset your account and migrate to the latest version.";
"Loki Public Chat" = "Loki Public Chat";

View File

@ -605,12 +605,6 @@ typedef void (^ProfileManagerFailureBlock)(NSError *error);
uint8_t byte = (uint8_t)(0xff & byteValue);
[groupId appendBytes:&byte length:1];
}
if (groupId.length != (NSUInteger)kGroupIdLength) {
OWSFailDebug(@"Parsed group id has unexpected length: %@ (%lu)",
groupId.hexadecimalString,
(unsigned long)groupId.length);
return nil;
}
return [groupId copy];
}

View File

@ -185,7 +185,7 @@ typedef void (^BuildOutgoingMessageCompletionBlock)(TSOutgoingMessage *savedMess
// Loki: If we're not friends then always set the message to a friend request message
// If we're friends then the assumption is that we have the other user's prekey bundle
NSString *messageClassAsString = thread.isContactFriend ? @"TSOutgoingMessage" : @"LKFriendRequestMessage";
NSString *messageClassAsString = (thread.isContactFriend || thread.isGroupThread) ? @"TSOutgoingMessage" : @"LKFriendRequestMessage";
Class messageClass = NSClassFromString(messageClassAsString);
TSOutgoingMessage *message =

View File

@ -10,7 +10,7 @@ public extension LokiAPI {
/// Start long polling.
/// This will send a notification if new messages were received
@objc public static func startLongPollingIfNecessary() {
@objc public static func startLongPollingIfNeeded() {
guard !isLongPolling else { return }
isLongPolling = true
shouldStopPolling = false

View File

@ -48,6 +48,7 @@ public final class LokiGroupChatAPI : NSObject {
print("[Loki] Couldn't parse message for group chat with ID: \(group) from: \(message).")
return nil
}
guard hexEncodedPublicKey != userHexEncodedPublicKey else { return nil }
return LokiGroupMessage(serverID: serverID, hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName, body: body, type: publicChatMessageType, timestamp: timestamp)
}
}

View File

@ -2,13 +2,13 @@ import PromiseKit
@objc(LKGroupMessage)
public final class LokiGroupMessage : NSObject {
let serverID: UInt?
let hexEncodedPublicKey: String
let displayName: String
let body: String
public let serverID: UInt?
public let hexEncodedPublicKey: String
public let displayName: String
public let body: String
/// - Note: Expressed as milliseconds since 00:00:00 UTC on 1 January 1970.
let timestamp: UInt64
let type: String
public let timestamp: UInt64
public let type: String
public init(serverID: UInt?, hexEncodedPublicKey: String, displayName: String, body: String, type: String, timestamp: UInt64) {
self.serverID = serverID
@ -24,7 +24,7 @@ public final class LokiGroupMessage : NSObject {
self.init(serverID: nil, hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName, body: body, type: type, timestamp: timestamp)
}
public func toJSON() -> JSON {
internal func toJSON() -> JSON {
let value: JSON = [ "timestamp" : timestamp, "from" : displayName, "source" : hexEncodedPublicKey ]
return [ "text" : body, "annotations": [ [ "type" : type, "value" : value ] ] ]
}

View File

@ -259,7 +259,7 @@ NS_ASSUME_NONNULL_BEGIN
OWSLogInfo(@"handling decrypted envelope: %@", [self descriptionForEnvelope:envelope]);
if (!envelope.hasSource || envelope.source.length < 1 || ![ECKeyPair isValidHexEncodedPublicKeyWithCandidate:envelope.source]) {
if (!envelope.hasSource || envelope.source.length < 1) {
OWSFailDebug(@"incoming envelope has invalid source");
return;
}

View File

@ -500,26 +500,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
if ([message isKindOfClass:[OWSOutgoingSyncMessage class]]) {
[recipientIds addObject:self.tsAccountManager.localNumber];
} else if (thread.isGroupThread) {
TSGroupThread *groupThread = (TSGroupThread *)thread;
// Send to the intersection of:
//
// * "sending" recipients of the message.
// * members of the group.
//
// I.e. try to send a message IFF:
//
// * The recipient was in the group when the message was first tried to be sent.
// * The recipient is still in the group.
// * The recipient is in the "sending" state.
[recipientIds addObjectsFromArray:message.sendingRecipientIds];
// Only send to members in the latest known group member list.
[recipientIds intersectSet:[NSSet setWithArray:groupThread.groupModel.groupMemberIds]];
if ([recipientIds containsObject:self.tsAccountManager.localNumber]) {
OWSFailDebug(@"Message send recipients should not include self.");
}
[recipientIds addObject:LKGroupChatAPI.serverURL];
} else if ([thread isKindOfClass:[TSContactThread class]]) {
NSString *recipientContactId = ((TSContactThread *)thread).contactIdentifier;
@ -974,8 +955,12 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
}
NSError *deviceMessagesError;
NSArray<NSDictionary *> *_Nullable deviceMessages =
[self deviceMessagesForMessageSend:messageSend error:&deviceMessagesError];
NSArray<NSDictionary *> *_Nullable deviceMessages;
if (!message.thread.isGroupThread) {
deviceMessages = [self deviceMessagesForMessageSend:messageSend error:&deviceMessagesError];
} else {
deviceMessages = @{};
}
if (deviceMessagesError || !deviceMessages) {
OWSAssertDebug(deviceMessagesError);
return messageSend.failure(deviceMessagesError);
@ -1081,7 +1066,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
}
}
if (deviceMessages.count == 0) {
if (deviceMessages.count == 0 && !message.thread.isGroupThread) {
// This might happen:
//
// * The first (after upgrading?) time we send a sync message to our linked devices.
@ -1117,14 +1102,14 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
OWSFailDebug(@"Missing underlying error: %@.", error);
}
} else {
OWSFailDebug(@"Unexpected error: %@.", error);
//OWSFailDebug(@"Unexpected error: %@.", error);
}
[self messageSendDidFail:messageSend deviceMessages:deviceMessages statusCode:statusCode error:error responseData:responseData];
};
if ([recipient.recipientId isEqualToString:LKGroupChatAPI.serverURL]) {
NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
NSString *displayName = [SSKEnvironment.shared.contactsManager displayNameForPhoneIdentifier:userHexEncodedPublicKey];
NSString *displayName = @"Anonymous";
if (displayName == nil) { displayName = @"Anonymous"; }
LKGroupMessage *groupMessage = [[LKGroupMessage alloc] initWithHexEncodedPublicKey:userHexEncodedPublicKey displayName:displayName body:message.body type:LKGroupChatAPI.publicChatMessageType timestamp:message.timestamp];
[[LKGroupChatAPI sendMessage:groupMessage toGroup:LKGroupChatAPI.publicChatID]

View File

@ -27,7 +27,6 @@ const int32_t kGroupIdLength = 16;
groupId:(NSData *)groupId
{
OWSAssertDebug(memberIds);
OWSAssertDebug(groupId.length == kGroupIdLength);
_groupName = title;
_groupMemberIds = [memberIds copy];
@ -49,7 +48,6 @@ const int32_t kGroupIdLength = 16;
if (_groupMemberIds == nil) {
_groupMemberIds = [NSArray new];
}
OWSAssertDebug(self.groupId.length == kGroupIdLength);
return self;
}