From 3a1d07e5a8eb7a61dcfc3a2c45918c210165df00 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Mon, 2 Sep 2019 13:27:12 +1000 Subject: [PATCH] Fix promise usage & clean --- Signal/src/Loki/LokiGroupChatPoller.swift | 2 +- Signal/src/Models/MessageActions.swift | 2 +- .../ColorPickerViewController.swift | 3 +- .../ConversationView/ConversationViewItem.h | 2 +- .../ConversationView/ConversationViewItem.m | 20 ++++--- .../src/Loki/API/LokiGroupChatAPI.swift | 53 +++++++------------ .../src/Loki/Crypto/OWSPrimaryStorage+Loki.h | 4 +- .../src/Loki/Crypto/OWSPrimaryStorage+Loki.m | 2 + 8 files changed, 37 insertions(+), 51 deletions(-) diff --git a/Signal/src/Loki/LokiGroupChatPoller.swift b/Signal/src/Loki/LokiGroupChatPoller.swift index 65bd710c2..ccf9ed5ce 100644 --- a/Signal/src/Loki/LokiGroupChatPoller.swift +++ b/Signal/src/Loki/LokiGroupChatPoller.swift @@ -127,7 +127,7 @@ public final class LokiGroupChatPoller : NSObject { private func pollForModerationPermission() { let group = self.group - let _ = LokiGroupChatAPI.isCurrentUserMod(on: group.server).done { [storage] isModerator in + let _ = LokiGroupChatAPI.userHasModerationPermission(for: group.serverID, on: group.server).done { [storage] isModerator in storage.dbReadWriteConnection.readWrite { transaction in storage.setIsModerator(isModerator, for: UInt(group.serverID), on: group.server, in: transaction) } diff --git a/Signal/src/Models/MessageActions.swift b/Signal/src/Models/MessageActions.swift index 9b2c6c7da..f65defe0e 100644 --- a/Signal/src/Models/MessageActions.swift +++ b/Signal/src/Models/MessageActions.swift @@ -86,7 +86,7 @@ class ConversationViewItemActions: NSObject { actions.append(copyTextAction) } - if !isGroup || conversationViewItem.canDeleteGroupMessage { + if !isGroup || conversationViewItem.userCanDeleteGroupMessage { let deleteAction = MessageActionBuilder.deleteMessage(conversationViewItem: conversationViewItem, delegate: delegate) actions.append(deleteAction) } diff --git a/Signal/src/ViewControllers/ColorPickerViewController.swift b/Signal/src/ViewControllers/ColorPickerViewController.swift index 7fb02acc2..f82137efd 100644 --- a/Signal/src/ViewControllers/ColorPickerViewController.swift +++ b/Signal/src/ViewControllers/ColorPickerViewController.swift @@ -305,8 +305,7 @@ class ColorPickerView: UIView, ColorViewDelegate { @objc private class MockConversationViewItem: NSObject, ConversationViewItem { - var canDeleteGroupMessage: Bool = false - + var userCanDeleteGroupMessage: Bool = false var interaction: TSInteraction = TSMessage() var interactionType: OWSInteractionType = OWSInteractionType.unknown var quotedReply: OWSQuotedReplyModel? diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h index ad60957c4..6504d0ef9 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h @@ -67,7 +67,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType); @property (nonatomic, readonly, nullable) OWSQuotedReplyModel *quotedReply; @property (nonatomic, readonly) BOOL isGroupThread; -@property (nonatomic, readonly) BOOL canDeleteGroupMessage; +@property (nonatomic, readonly) BOOL userCanDeleteGroupMessage; @property (nonatomic, readonly) BOOL hasBodyText; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m index f6da3dd37..7d3fa49f0 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.m @@ -203,8 +203,6 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) - (OWSPrimaryStorage *)primaryStorage { - OWSAssertDebug(SSKEnvironment.shared.primaryStorage); - return SSKEnvironment.shared.primaryStorage; } @@ -1171,25 +1169,25 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) - (void)deleteAction { - // Delete it optimistically [self.interaction remove]; if (self.isGroupThread) { - // If it's RSS then skip + // Skip if the thread is an RSS feed TSGroupThread *groupThread = (TSGroupThread *)self.interaction.thread; if (groupThread.isRSSFeed) return; // Only allow deletion on incoming and outgoing messages OWSInteractionType interationType = self.interaction.interactionType; - if (interationType != OWSInteractionType_OutgoingMessage && interationType != OWSInteractionType_IncomingMessage) return; + if (interationType != OWSInteractionType_IncomingMessage && interationType != OWSInteractionType_OutgoingMessage) return; - // Check that we have the server id for the message + // Make sure it's a public chat message TSMessage *message = (TSMessage *)self.interaction; if (!message.isPublicChatMessage) return; // Delete the message - [[LKGroupChatAPI deleteMessageWithServerID:message.publicChatMessageID forGroup:LKGroupChatAPI.publicChatServerID onServer:LKGroupChatAPI.publicChatServer isOurOwnMessage:interationType == OWSInteractionType_OutgoingMessage].catch(^(NSError *error) { - // If we fail then add the interaction back in + BOOL isSentByUser = (interationType == OWSInteractionType_OutgoingMessage); + [[LKGroupChatAPI deleteMessageWithID:message.publicChatMessageID forGroup:LKGroupChatAPI.publicChatServerID onServer:LKGroupChatAPI.publicChatServer isSentByUser:isSentByUser].catch(^(NSError *error) { + // Roll back [self.interaction save]; }) retainUntilComplete]; } @@ -1235,11 +1233,11 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) return NO; } -- (BOOL)canDeleteGroupMessage +- (BOOL)userCanDeleteGroupMessage { if (!self.isGroupThread) return false; - // Make sure it's a public chat and not an rss feed + // Ensure the thread is a public chat and not an RSS feed TSGroupThread *groupThread = (TSGroupThread *)self.interaction.thread; if (groupThread.isRSSFeed) return false; @@ -1251,7 +1249,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType) TSMessage *message = (TSMessage *)self.interaction; if (!message.isPublicChatMessage) return false; - // Don't allow deletion if we're not mods on incoming messages + // Only allow deletion on incoming messages if the user has moderation permission if (interationType == OWSInteractionType_IncomingMessage) { __block BOOL isModerator; [[self primaryStorage].dbReadWriteConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { diff --git a/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift b/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift index ca612748c..059d8c91d 100644 --- a/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift @@ -11,7 +11,7 @@ public final class LokiGroupChatAPI : NSObject { // MARK: Public Chat @objc public static let publicChatServer = "https://chat.lokinet.org" @objc public static let publicChatMessageType = "network.loki.messenger.publicChat" - @objc public static let publicChatServerID: UInt = 1 + @objc public static let publicChatServerID: UInt64 = 1 // MARK: Convenience private static var userDisplayName: String { @@ -28,7 +28,7 @@ public final class LokiGroupChatAPI : NSObject { // MARK: Error public enum Error : Swift.Error { - case tokenParsingFailed, tokenDecryptionFailed, messageParsingFailed, deletionParsingFailed, jsonParsingFailed + case parsingFailed, decryptionFailed } // MARK: Database @@ -87,7 +87,7 @@ public final class LokiGroupChatAPI : NSObject { return TSNetworkManager.shared().makePromise(request: request).map { $0.responseObject }.map { rawResponse in guard let json = rawResponse as? JSON, let base64EncodedChallenge = json["cipherText64"] as? String, let base64EncodedServerPublicKey = json["serverPubKey64"] as? String, let challenge = Data(base64Encoded: base64EncodedChallenge), var serverPublicKey = Data(base64Encoded: base64EncodedServerPublicKey) else { - throw Error.tokenParsingFailed + throw Error.parsingFailed } // Discard the "05" prefix if needed if (serverPublicKey.count == 33) { @@ -97,7 +97,7 @@ public final class LokiGroupChatAPI : NSObject { // The challenge is prefixed by the 16 bit IV guard let tokenAsData = try? DiffieHellman.decrypt(challenge, publicKey: serverPublicKey, privateKey: userKeyPair.privateKey), let token = String(bytes: tokenAsData, encoding: .utf8) else { - throw Error.tokenDecryptionFailed + throw Error.decryptionFailed } return token } @@ -136,7 +136,7 @@ public final class LokiGroupChatAPI : NSObject { return TSNetworkManager.shared().makePromise(request: request).map { $0.responseObject }.map { rawResponse in guard let json = rawResponse as? JSON, let rawMessages = json["data"] as? [JSON] else { print("[Loki] Couldn't parse messages for group chat with ID: \(group) on server: \(server) from: \(rawResponse).") - throw Error.messageParsingFailed + throw Error.parsingFailed } return rawMessages.flatMap { message in guard let annotations = message["annotations"] as? [JSON], let annotation = annotations.first, let value = annotation["value"] as? JSON, @@ -167,7 +167,7 @@ public final class LokiGroupChatAPI : NSObject { guard let json = rawResponse as? JSON, let messageAsJSON = json["data"] as? JSON, let serverID = messageAsJSON["id"] as? UInt64, let body = messageAsJSON["text"] as? String, let dateAsString = messageAsJSON["created_at"] as? String, let date = dateFormatter.date(from: dateAsString) else { print("[Loki] Couldn't parse message for group chat with ID: \(group) on server: \(server) from: \(rawResponse).") - throw Error.messageParsingFailed + throw Error.parsingFailed } let timestamp = UInt64(date.timeIntervalSince1970) * 1000 return LokiGroupMessage(serverID: serverID, hexEncodedPublicKey: userHexEncodedPublicKey, displayName: displayName, body: body, type: publicChatMessageType, timestamp: timestamp) @@ -194,7 +194,7 @@ public final class LokiGroupChatAPI : NSObject { return TSNetworkManager.shared().makePromise(request: request).map { $0.responseObject }.map { rawResponse in guard let json = rawResponse as? JSON, let deletions = json["data"] as? [JSON] else { print("[Loki] Couldn't parse deleted messages for group chat with ID: \(group) on server: \(server) from: \(rawResponse).") - throw Error.deletionParsingFailed + throw Error.parsingFailed } return deletions.flatMap { deletion in guard let serverID = deletion["id"] as? UInt64, let messageServerID = deletion["message_id"] as? UInt64 else { @@ -208,43 +208,30 @@ public final class LokiGroupChatAPI : NSObject { } } - public static func deleteMessageWithServerID(_ messageServerID: UInt, for group: UInt64, on server: String, isOurOwnMessage: Bool = true) -> Promise { + public static func deleteMessage(with messageID: UInt, for group: UInt64, on server: String, isSentByUser: Bool) -> Promise { return getAuthToken(for: server).then { token -> Promise in - let modTag = isOurOwnMessage ? "" : "[Mod]" - print("[Loki]\(modTag) Deleting message with server ID: \(messageServerID) for group chat with ID: \(group) on server: \(server).") - - let endpoint = isOurOwnMessage ? "\(server)/channels/\(group)/messages/\(messageServerID)" : "\(server)/loki/v1/moderation/message/\(messageServerID)" - let url = URL(string: endpoint)! + let isModerationRequest = !isSentByUser + print("[Loki] Deleting message with ID: \(messageID) for group chat with ID: \(group) on server: \(server) (isModerationRequest = \(isModerationRequest)).") + let urlAsString = isSentByUser ? "\(server)/channels/\(group)/messages/\(messageID)" : "\(server)/loki/v1/moderation/message/\(messageID)" + let url = URL(string: urlAsString)! let request = TSRequest(url: url, method: "DELETE", parameters: [:]) request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ] - return TSNetworkManager.shared().makePromise(request: request).map { result -> Void in - print("[Loki]\(modTag) Deleted message \(messageServerID) on server \(server).") - }.recover { error in - // If we got 404 or 410 then message doesn't exist on the server - if let error = error as? NetworkManagerError, error.statusCode == 404 || error.statusCode == 410 { - print("[Loki]\(modTag) Message \(messageServerID) was already deleted on the server.") - return - } - - print("[Loki]\(modTag) Failed to delete message \(messageServerID) on server \(server).") - throw error + return TSNetworkManager.shared().makePromise(request: request).done { result -> Void in + print("[Loki] Deleted message with ID: \(messageID) on server: \(server).") } } - } - public static func isCurrentUserMod(on server: String) -> Promise { + public static func userHasModerationPermission(for group: UInt64, on server: String) -> Promise { return getAuthToken(for: server).then { token -> Promise in let url = URL(string: "\(server)/loki/v1/user_info")! let request = TSRequest(url: url) request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ] return TSNetworkManager.shared().makePromise(request: request).map { $0.responseObject }.map { rawResponse in guard let json = rawResponse as? JSON, let data = json["data"] as? JSON else { - print("[Loki] Couldn't parse json for user info.") - throw Error.jsonParsingFailed + print("[Loki] Couldn't parse moderation permission for group chat with ID: \(group) on server: \(server) from: \(rawResponse).") + throw Error.parsingFailed } - - // moderator_status is not set for users that are not mods return data["moderator_status"] as? Bool ?? false } } @@ -261,8 +248,8 @@ public final class LokiGroupChatAPI : NSObject { return AnyPromise.from(sendMessage(message, to: group, on: server)) } - @objc (deleteMessageWithServerID:forGroup:onServer:isOurOwnMessage:) - public static func objc_deleteMessageWithServerID(_ messageServerID: UInt, for group: UInt64, on server: String, ourMessage: Bool = true) -> AnyPromise { - return AnyPromise.from(deleteMessageWithServerID(messageServerID, for: group, on: server, isOurOwnMessage: ourMessage)) + @objc (deleteMessageWithID:forGroup:onServer:isSentByUser:) + public static func objc_deleteMessage(with messageID: UInt, for group: UInt64, on server: String, isSentByUser: Bool) -> AnyPromise { + return AnyPromise.from(deleteMessage(with: messageID, for: group, on: server, isSentByUser: isSentByUser)) } } diff --git a/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.h b/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.h index ed6e781b3..528b70213 100644 --- a/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.h +++ b/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.h @@ -72,7 +72,7 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)removePreKeyBundleForContact:(NSString *)pubKey transaction:(YapDatabaseReadWriteTransaction *)transaction; -# pragma mark - Last Hash Handling +# pragma mark - Last Hash /** Get the last message hash for the given service node. @@ -95,7 +95,7 @@ NS_ASSUME_NONNULL_BEGIN */ - (void)setLastMessageHashForServiceNode:(NSString *)serviceNode hash:(NSString *)hash expiresAt:(u_int64_t)expiresAt transaction:(YapDatabaseReadWriteTransaction *)transaction NS_SWIFT_NAME(setLastMessageHash(forServiceNode:hash:expiresAt:transaction:)); -# pragma mark - Public chat +# pragma mark - Group Chat - (void)setIDForMessageWithServerID:(NSUInteger)serverID to:(NSString *)messageID in:(YapDatabaseReadWriteTransaction *)transaction; - (NSString *_Nullable)getIDForMessageWithServerID:(NSUInteger)serverID in:(YapDatabaseReadTransaction *)transaction; diff --git a/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.m b/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.m index 42b870e06..fa89c1386 100644 --- a/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.m +++ b/SignalServiceKit/src/Loki/Crypto/OWSPrimaryStorage+Loki.m @@ -164,6 +164,8 @@ [transaction removeObjectForKey:serviceNode inCollection:LKLastMessageHashCollection]; } +# pragma mark - Group Chat + - (void)setIDForMessageWithServerID:(NSUInteger)serverID to:(NSString *)messageID in:(YapDatabaseReadWriteTransaction *)transaction { NSString *key = [NSString stringWithFormat:@"%@", @(serverID)]; [transaction setObject:messageID forKey:key inCollection:LKMessageIDCollection];