diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index fc2b1d99c..9724c5aca 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -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 = ""; }; B821F2F72272CED3002C88C0 /* AccountDetailsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountDetailsViewController.swift; sourceTree = ""; }; B821F2F92272CEEE002C88C0 /* SeedViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SeedViewController.swift; sourceTree = ""; }; + B845B4D3230CD09000D759F0 /* LokiGroupChatPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LokiGroupChatPoller.swift; sourceTree = ""; }; B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Identicon+ObjC.swift"; sourceTree = ""; }; B89841E222B7579F00B1BDC6 /* NewConversationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationViewController.swift; sourceTree = ""; }; B90418E4183E9DD40038554A /* DateUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateUtil.h; sourceTree = ""; }; @@ -2612,6 +2614,7 @@ B821F2F72272CED3002C88C0 /* AccountDetailsViewController.swift */, B821F2F92272CEEE002C88C0 /* SeedViewController.swift */, 24A830A12293CD0100F4CAC0 /* LokiP2PServer.swift */, + B845B4D3230CD09000D759F0 /* LokiGroupChatPoller.swift */, ); path = Loki; sourceTree = ""; @@ -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 */, diff --git a/Signal/src/AppDelegate.h b/Signal/src/AppDelegate.h index 3c563548a..c14a3e70b 100644 --- a/Signal/src/AppDelegate.h +++ b/Signal/src/AppDelegate.h @@ -8,4 +8,6 @@ extern NSString *const AppDelegateStoryboardMain; @interface AppDelegate : UIResponder +- (void)startPublicChatPollingIfNeeded; + @end diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 37ceb85bf..f29d76ebb 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -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 diff --git a/Signal/src/Loki/LokiGroupChatPoller.swift b/Signal/src/Loki/LokiGroupChatPoller.swift new file mode 100644 index 000000000..18109b0c5 --- /dev/null +++ b/Signal/src/Loki/LokiGroupChatPoller.swift @@ -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.. [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) diff --git a/Signal/src/ViewControllers/ConversationView/ConversationHeaderView.swift b/Signal/src/ViewControllers/ConversationView/ConversationHeaderView.swift index 680060ac6..5a816534f 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationHeaderView.swift +++ b/Signal/src/ViewControllers/ConversationView/ConversationHeaderView.swift @@ -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) { diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index ded5e3991..8947a9239 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -1601,7 +1601,9 @@ typedef enum : NSUInteger { }]]; - self.headerView.attributedSubtitle = subtitleText; + if (!self.thread.isGroupThread) { + self.headerView.attributedSubtitle = subtitleText; + } } #pragma mark - Updating diff --git a/Signal/src/ViewControllers/HomeView/HomeViewController.m b/Signal/src/ViewControllers/HomeView/HomeViewController.m index a1a352346..17e0202e3 100644 --- a/Signal/src/ViewControllers/HomeView/HomeViewController.m +++ b/Signal/src/ViewControllers/HomeView/HomeViewController.m @@ -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 diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 150922ec5..d3ba71b67 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -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"; diff --git a/SignalMessaging/profiles/OWSProfileManager.m b/SignalMessaging/profiles/OWSProfileManager.m index 072128081..06cc09b1e 100644 --- a/SignalMessaging/profiles/OWSProfileManager.m +++ b/SignalMessaging/profiles/OWSProfileManager.m @@ -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]; } diff --git a/SignalMessaging/utils/ThreadUtil.m b/SignalMessaging/utils/ThreadUtil.m index 0dcccaee5..fa6f28b81 100644 --- a/SignalMessaging/utils/ThreadUtil.m +++ b/SignalMessaging/utils/ThreadUtil.m @@ -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 = diff --git a/SignalServiceKit/src/Loki/API/LokiAPI+LongPolling.swift b/SignalServiceKit/src/Loki/API/LokiAPI+LongPolling.swift index 9a9821015..70554fd51 100644 --- a/SignalServiceKit/src/Loki/API/LokiAPI+LongPolling.swift +++ b/SignalServiceKit/src/Loki/API/LokiAPI+LongPolling.swift @@ -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 diff --git a/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift b/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift index c34502902..5309235d9 100644 --- a/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift @@ -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) } } diff --git a/SignalServiceKit/src/Loki/API/LokiGroupMessage.swift b/SignalServiceKit/src/Loki/API/LokiGroupMessage.swift index 1873d695b..53d053531 100644 --- a/SignalServiceKit/src/Loki/API/LokiGroupMessage.swift +++ b/SignalServiceKit/src/Loki/API/LokiGroupMessage.swift @@ -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 ] ] ] } diff --git a/SignalServiceKit/src/Messages/OWSMessageManager.m b/SignalServiceKit/src/Messages/OWSMessageManager.m index 7749189de..394535ec9 100644 --- a/SignalServiceKit/src/Messages/OWSMessageManager.m +++ b/SignalServiceKit/src/Messages/OWSMessageManager.m @@ -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; } diff --git a/SignalServiceKit/src/Messages/OWSMessageSender.m b/SignalServiceKit/src/Messages/OWSMessageSender.m index 68f041d4c..db6203490 100644 --- a/SignalServiceKit/src/Messages/OWSMessageSender.m +++ b/SignalServiceKit/src/Messages/OWSMessageSender.m @@ -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 *_Nullable deviceMessages = - [self deviceMessagesForMessageSend:messageSend error:&deviceMessagesError]; + NSArray *_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] diff --git a/SignalServiceKit/src/Messages/TSGroupModel.m b/SignalServiceKit/src/Messages/TSGroupModel.m index e20df12be..3dd811f60 100644 --- a/SignalServiceKit/src/Messages/TSGroupModel.m +++ b/SignalServiceKit/src/Messages/TSGroupModel.m @@ -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; }