Merge pull request #43 from loki-project/server-deletion

User Initiated Group Message Deletion
pull/46/head
gmbnt 6 years ago committed by GitHub
commit 85fe0504b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -2615,17 +2615,17 @@
B8439518228510E9000563FE /* Loki */ = {
isa = PBXGroup;
children = (
B821F2F72272CED3002C88C0 /* AccountDetailsViewController.swift */,
B8162F0222891AD600D46544 /* FriendRequestView.swift */,
B8162F0422892C5F00D46544 /* FriendRequestViewDelegate.swift */,
B89841E222B7579F00B1BDC6 /* NewConversationViewController.swift */,
B821F2F72272CED3002C88C0 /* AccountDetailsViewController.swift */,
B821F2F92272CEEE002C88C0 /* SeedViewController.swift */,
24A830A12293CD0100F4CAC0 /* LokiP2PServer.swift */,
B845B4D3230CD09000D759F0 /* LokiGroupChatPoller.swift */,
24A830A12293CD0100F4CAC0 /* LokiP2PServer.swift */,
B825849F2315024B001B41CB /* LokiRSSFeedPoller.swift */,
B89841E222B7579F00B1BDC6 /* NewConversationViewController.swift */,
B825848A230F94FE001B41CB /* QRCodeViewController.swift */,
B8258491230FA5DA001B41CB /* ScanQRCodeViewController.h */,
B8258492230FA5E9001B41CB /* ScanQRCodeViewController.m */,
B821F2F92272CEEE002C88C0 /* SeedViewController.swift */,
);
path = Loki;
sourceTree = "<group>";

@ -1504,7 +1504,7 @@ static NSTimeInterval launchStartedAt;
- (LKGroupChat *)lokiPublicChat
{
return [[LKGroupChat alloc] initWithServerID:@(LKGroupChatAPI.publicChatServerID).unsignedIntegerValue server:LKGroupChatAPI.publicChatServer displayName:NSLocalizedString(@"Loki Public Chat", @"") isDeletable:true];
return [[LKGroupChat alloc] initWithServerID:LKGroupChatAPI.publicChatServerID server:LKGroupChatAPI.publicChatServer displayName:NSLocalizedString(@"Loki Public Chat", @"") isDeletable:true];
}
- (LKRSSFeed *)lokiNewsFeed

@ -4,11 +4,16 @@ public final class LokiGroupChatPoller : NSObject {
private let group: LokiGroupChat
private var pollForNewMessagesTimer: Timer? = nil
private var pollForDeletedMessagesTimer: Timer? = nil
private var pollForModerationPermissionTimer: Timer? = nil
private var hasStarted = false
private let userHexEncodedPublicKey = OWSIdentityManager.shared().identityKeyPair()!.hexEncodedPublicKey
// MARK: Settings
private let pollForNewMessagesInterval: TimeInterval = 4
private let pollForDeletedMessagesInterval: TimeInterval = 20
private let pollForModerationPermissionInterval: TimeInterval = 10 * 60
// MARK: Lifecycle
@objc(initForGroup:)
public init(for group: LokiGroupChat) {
self.group = group
@ -20,42 +25,80 @@ public final class LokiGroupChatPoller : NSObject {
pollForNewMessagesTimer = Timer.scheduledTimer(withTimeInterval: pollForNewMessagesInterval, repeats: true) { [weak self] _ in self?.pollForNewMessages() }
pollForNewMessages() // Perform initial update
pollForDeletedMessagesTimer = Timer.scheduledTimer(withTimeInterval: pollForDeletedMessagesInterval, repeats: true) { [weak self] _ in self?.pollForDeletedMessages() }
pollForModerationPermissionTimer = Timer.scheduledTimer(withTimeInterval: pollForModerationPermissionInterval, repeats: true) { [weak self] _ in self?.pollForModerationPermission() }
hasStarted = true
}
@objc public func stop() {
pollForNewMessagesTimer?.invalidate()
pollForDeletedMessagesTimer?.invalidate()
pollForModerationPermissionTimer?.invalidate()
hasStarted = false
}
// MARK: Polling
private func pollForNewMessages() {
// Prepare
let group = self.group
let _ = LokiGroupChatAPI.getMessages(for: group.serverID, on: group.server).done { messages in
messages.reversed().forEach { message in
let senderHexEncodedPublicKey = message.hexEncodedPublicKey
let endIndex = senderHexEncodedPublicKey.endIndex
let cutoffIndex = senderHexEncodedPublicKey.index(endIndex, offsetBy: -8)
let senderDisplayName = "\(message.displayName) (...\(senderHexEncodedPublicKey[cutoffIndex..<endIndex]))"
let id = group.id.data(using: String.Encoding.utf8)!
let x1 = SSKProtoGroupContext.builder(id: id, type: .deliver)
x1.setName(group.displayName)
let x2 = SSKProtoDataMessage.builder()
x2.setTimestamp(message.timestamp)
x2.setGroup(try! x1.build())
x2.setBody(message.body)
let messageServerID = message.serverID!
let userHexEncodedPublicKey = self.userHexEncodedPublicKey
// Processing logic for incoming messages
func processIncomingMessage(_ message: LokiGroupMessage) {
let senderHexEncodedPublicKey = message.hexEncodedPublicKey
let endIndex = senderHexEncodedPublicKey.endIndex
let cutoffIndex = senderHexEncodedPublicKey.index(endIndex, offsetBy: -8)
let senderDisplayName = "\(message.displayName) (...\(senderHexEncodedPublicKey[cutoffIndex..<endIndex]))"
let id = group.id.data(using: String.Encoding.utf8)!
let x1 = SSKProtoGroupContext.builder(id: id, type: .deliver)
x1.setName(group.displayName)
let x2 = SSKProtoDataMessage.builder()
x2.setTimestamp(message.timestamp)
x2.setGroup(try! x1.build())
x2.setBody(message.body)
if let messageServerID = message.serverID {
let publicChatInfo = SSKProtoPublicChatInfo.builder()
publicChatInfo.setServerID(messageServerID)
x2.setPublicChatInfo(try! publicChatInfo.build())
let x3 = SSKProtoContent.builder()
x3.setDataMessage(try! x2.build())
let x4 = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: message.timestamp)
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)
}
let x3 = SSKProtoContent.builder()
x3.setDataMessage(try! x2.build())
let x4 = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: message.timestamp)
x4.setSource(senderDisplayName)
x4.setSourceDevice(OWSDevicePrimaryDeviceId)
x4.setContent(try! x3.build().serializedData())
let storage = OWSPrimaryStorage.shared()
storage.dbReadWriteConnection.readWrite { transaction in
SSKEnvironment.shared.messageManager.throws_processEnvelope(try! x4.build(), plaintextData: try! x3.build().serializedData(), wasReceivedByUD: false, transaction: transaction)
}
}
// Processing logic for outgoing messages
func processOutgoingMessage(_ message: LokiGroupMessage) {
guard let messageServerID = message.serverID else { return }
let storage = OWSPrimaryStorage.shared()
var isDuplicate = false
storage.dbReadConnection.read { transaction in
let id = storage.getIDForMessage(withServerID: UInt(messageServerID), in: transaction)
isDuplicate = id != nil
}
guard !isDuplicate else { return }
guard let groupID = group.id.data(using: .utf8) else { return }
let thread = TSGroupThread.getOrCreateThread(withGroupId: groupID)
let message = TSOutgoingMessage(outgoingMessageWithTimestamp: message.timestamp, in: thread, messageBody: message.body, attachmentIds: [], expiresInSeconds: 0,
expireStartedAt: 0, isVoiceMessage: false, groupMetaMessage: .deliver, quotedMessage: nil, contactShare: nil, linkPreview: nil)
storage.dbReadWriteConnection.readWrite { transaction in
message.update(withSentRecipient: group.server, wasSentByUD: false, transaction: transaction)
message.saveGroupChatMessageID(messageServerID, in: transaction)
guard let messageID = message.uniqueId else { return print("[Loki] Failed to save group message.") }
storage.setIDForMessageWithServerID(UInt(messageServerID), to: messageID, in: transaction)
}
}
// Poll
let _ = LokiGroupChatAPI.getMessages(for: group.serverID, on: group.server).done { messages in
messages.reversed().forEach { message in
let senderHexEncodedPublicKey = message.hexEncodedPublicKey
if (senderHexEncodedPublicKey != userHexEncodedPublicKey) {
processIncomingMessage(message)
} else {
processOutgoingMessage(message)
}
}
}
@ -73,4 +116,14 @@ public final class LokiGroupChatPoller : NSObject {
}
}
}
private func pollForModerationPermission() {
let group = self.group
let _ = LokiGroupChatAPI.userHasModerationPermission(for: group.serverID, on: group.server).done { isModerator in
let storage = OWSPrimaryStorage.shared()
storage.dbReadWriteConnection.readWrite { transaction in
storage.setIsModerator(isModerator, for: UInt(group.serverID), on: group.server, in: transaction)
}
}
}
}

@ -86,7 +86,7 @@ class ConversationViewItemActions: NSObject {
actions.append(copyTextAction)
}
if !isGroup {
if !isGroup || conversationViewItem.userCanDeleteGroupMessage {
let deleteAction = MessageActionBuilder.deleteMessage(conversationViewItem: conversationViewItem, delegate: delegate)
actions.append(deleteAction)
}

@ -305,6 +305,7 @@ class ColorPickerView: UIView, ColorViewDelegate {
@objc
private class MockConversationViewItem: NSObject, ConversationViewItem {
var userCanDeleteGroupMessage: Bool = false
var interaction: TSInteraction = TSMessage()
var interactionType: OWSInteractionType = OWSInteractionType.unknown
var quotedReply: OWSQuotedReplyModel?

@ -67,6 +67,7 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType);
@property (nonatomic, readonly, nullable) OWSQuotedReplyModel *quotedReply;
@property (nonatomic, readonly) BOOL isGroupThread;
@property (nonatomic, readonly) BOOL userCanDeleteGroupMessage;
@property (nonatomic, readonly) BOOL hasBodyText;

@ -9,11 +9,14 @@
#import "OWSMessageHeaderView.h"
#import "OWSSystemMessageCell.h"
#import "Session-Swift.h"
#import "AnyPromise.h"
#import <SignalMessaging/OWSUnreadIndicator.h>
#import <SignalServiceKit/NSData+Image.h>
#import <SignalServiceKit/NSString+SSK.h>
#import <SignalServiceKit/OWSContact.h>
#import <SignalServiceKit/TSInteraction.h>
#import <SignalServiceKit/SSKEnvironment.h>
#import <SignalServiceKit/SignalServiceKit-Swift.h>
NS_ASSUME_NONNULL_BEGIN
@ -198,6 +201,11 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
}
}
- (OWSPrimaryStorage *)primaryStorage
{
return SSKEnvironment.shared.primaryStorage;
}
- (NSString *)itemId
{
return self.interaction.uniqueId;
@ -1162,6 +1170,27 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
- (void)deleteAction
{
[self.interaction remove];
if (self.isGroupThread) {
// 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_IncomingMessage && interationType != OWSInteractionType_OutgoingMessage) return;
// Make sure it's a public chat message
TSMessage *message = (TSMessage *)self.interaction;
if (!message.isGroupChatMessage) return;
// Delete the message
BOOL isSentByUser = (interationType == OWSInteractionType_OutgoingMessage);
[[LKGroupChatAPI deleteMessageWithID:message.isGroupChatMessage forGroup:LKGroupChatAPI.publicChatServerID onServer:LKGroupChatAPI.publicChatServer isSentByUser:isSentByUser].catch(^(NSError *error) {
// Roll back
[self.interaction save];
}) retainUntilComplete];
}
}
- (BOOL)hasBodyTextActionContent
@ -1204,6 +1233,34 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType)
return NO;
}
- (BOOL)userCanDeleteGroupMessage
{
if (!self.isGroupThread) return false;
// Ensure the thread is a public chat and not an RSS feed
TSGroupThread *groupThread = (TSGroupThread *)self.interaction.thread;
if (groupThread.isRSSFeed) return false;
// Only allow deletion on incoming and outgoing messages
OWSInteractionType interationType = self.interaction.interactionType;
if (interationType != OWSInteractionType_OutgoingMessage && interationType != OWSInteractionType_IncomingMessage) return false;
// Make sure it's a public chat message
TSMessage *message = (TSMessage *)self.interaction;
if (!message.isGroupChatMessage) return false;
// 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) {
isModerator = [[self primaryStorage] isModeratorForGroup:LKGroupChatAPI.publicChatServerID onServer:LKGroupChatAPI.publicChatServer in:transaction];
}];
if (!isModerator) return false;
}
return true;
}
@end
NS_ASSUME_NONNULL_END

@ -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 = 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
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,
@ -145,7 +145,6 @@ public final class LokiGroupChatAPI : NSObject {
print("[Loki] Couldn't parse message for group chat with ID: \(group) on server: \(server) from: \(message).")
return nil
}
guard hexEncodedPublicKey != userHexEncodedPublicKey else { return nil }
let lastMessageServerID = getLastMessageServerID(for: group, on: server)
if serverID > (lastMessageServerID ?? 0) { setLastMessageServerID(for: group, on: server, to: serverID) }
return LokiGroupMessage(serverID: serverID, hexEncodedPublicKey: hexEncodedPublicKey, displayName: displayName, body: body, type: publicChatMessageType, timestamp: timestamp)
@ -168,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)
@ -195,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 {
@ -209,6 +208,35 @@ public final class LokiGroupChatAPI : NSObject {
}
}
public static func deleteMessage(with messageID: UInt, for group: UInt64, on server: String, isSentByUser: Bool) -> Promise<Void> {
return getAuthToken(for: server).then { token -> Promise<Void> in
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).done { result -> Void in
print("[Loki] Deleted message with ID: \(messageID) on server: \(server).")
}
}
}
public static func userHasModerationPermission(for group: UInt64, on server: String) -> Promise<Bool> {
return getAuthToken(for: server).then { token -> Promise<Bool> 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 moderation permission for group chat with ID: \(group) on server: \(server) from: \(rawResponse).")
throw Error.parsingFailed
}
return data["moderator_status"] as? Bool ?? false
}
}
}
// MARK: Public API (Obj-C)
@objc(getMessagesForGroup:onServer:)
public static func objc_getMessages(for group: UInt64, on server: String) -> AnyPromise {
@ -219,4 +247,9 @@ public final class LokiGroupChatAPI : NSObject {
public static func objc_sendMessage(_ message: LokiGroupMessage, to group: UInt64, on server: String) -> AnyPromise {
return AnyPromise.from(sendMessage(message, to: group, on: server))
}
@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))
}
}

@ -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,8 +95,12 @@ 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 - Group Chat
- (void)setIDForMessageWithServerID:(NSUInteger)serverID to:(NSString *)messageID in:(YapDatabaseReadWriteTransaction *)transaction;
- (NSString *)getIDForMessageWithServerID:(NSUInteger)serverID in:(YapDatabaseReadTransaction *)transaction;
- (NSString *_Nullable)getIDForMessageWithServerID:(NSUInteger)serverID in:(YapDatabaseReadTransaction *)transaction;
- (void)setIsModerator:(BOOL)isModerator forGroup:(NSUInteger)group onServer:(NSString *)server in:(YapDatabaseReadWriteTransaction *)transaction NS_SWIFT_NAME(setIsModerator(_:for:on:in:));
- (BOOL)isModeratorForGroup:(NSUInteger)group onServer:(NSString *)server in:(YapDatabaseReadTransaction *)transaction NS_SWIFT_NAME(isModerator(for:on:in:));
@end

@ -19,6 +19,7 @@
#define LKReceivedMessageHashesKey @"LKReceivedMessageHashesKey"
#define LKReceivedMessageHashesCollection @"LKReceivedMessageHashesCollection"
#define LKMessageIDCollection @"LKMessageIDCollection"
#define LKModerationPermissionCollection @"LKModerationPermissionCollection"
@implementation OWSPrimaryStorage (Loki)
@ -163,14 +164,26 @@
[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];
}
- (NSString *)getIDForMessageWithServerID:(NSUInteger)serverID in:(YapDatabaseReadTransaction *)transaction {
- (NSString *_Nullable)getIDForMessageWithServerID:(NSUInteger)serverID in:(YapDatabaseReadTransaction *)transaction {
NSString *key = [NSString stringWithFormat:@"%@", @(serverID)];
return [transaction objectForKey:key inCollection:LKMessageIDCollection];
}
- (void)setIsModerator:(BOOL)isModerator forGroup:(NSUInteger)group onServer:(NSString *)server in:(YapDatabaseReadWriteTransaction *)transaction {
NSString *key = [NSString stringWithFormat:@"%@.%@", server, @(group)];
[transaction setBool:isModerator forKey:key inCollection:LKModerationPermissionCollection];
}
- (BOOL)isModeratorForGroup:(NSUInteger)group onServer:(NSString *)server in:(YapDatabaseReadTransaction *)transaction NS_SWIFT_NAME(isModerator(for:on:in:)) {
NSString *key = [NSString stringWithFormat:@"%@.%@", server, @(group)];
return [transaction boolForKey:key inCollection:LKModerationPermissionCollection defaultValue:false];
}
@end

@ -39,14 +39,15 @@ typedef NS_ENUM(NSInteger, LKMessageFriendRequestStatus) {
@property (nonatomic, readonly, nullable) OWSContact *contactShare;
@property (nonatomic, readonly, nullable) OWSLinkPreview *linkPreview;
// Loki friend request handling
// ========
@property (nonatomic) LKMessageFriendRequestStatus friendRequestStatus;
@property (nonatomic, readonly) NSString *friendRequestStatusDescription;
@property (nonatomic) uint64_t friendRequestExpiresAt;
@property (nonatomic, readonly) BOOL isFriendRequest;
@property (nonatomic, readonly) BOOL hasFriendRequestStatusMessage;
@property (nonatomic) BOOL isP2P;
// ========
// Group chat
@property (nonatomic) uint64_t groupChatMessageID;
@property (nonatomic, readonly) BOOL isGroupChatMessage;
- (instancetype)initInteractionWithTimestamp:(uint64_t)timestamp inThread:(TSThread *)thread NS_UNAVAILABLE;
@ -92,6 +93,10 @@ typedef NS_ENUM(NSInteger, LKMessageFriendRequestStatus) {
- (void)saveFriendRequestStatus:(LKMessageFriendRequestStatus)friendRequestStatus withTransaction:(YapDatabaseReadWriteTransaction *_Nullable)transaction;
- (void)saveFriendRequestExpiresAt:(u_int64_t)expiresAt withTransaction:(YapDatabaseReadWriteTransaction *_Nullable)transaction;
#pragma mark - Group Chat
- (void)saveGroupChatMessageID:(uint64_t)serverMessageID in:(YapDatabaseReadWriteTransaction *_Nullable)transaction;
@end
NS_ASSUME_NONNULL_END

@ -84,6 +84,7 @@ static const NSUInteger OWSMessageSchemaVersion = 4;
_linkPreview = linkPreview;
_friendRequestStatus = LKMessageFriendRequestStatusNone;
_friendRequestExpiresAt = 0;
_groupChatMessageID = -1;
return self;
}
@ -490,6 +491,23 @@ static const NSUInteger OWSMessageSchemaVersion = 4;
return self.isFriendRequest && self.friendRequestStatus != LKMessageFriendRequestStatusSendingOrFailed;
}
#pragma mark - Group Chat
- (BOOL) isGroupChatMessage {
return self.groupChatMessageID > 0;
}
- (void)saveGroupChatMessageID:(uint64_t)serverMessageID in:(YapDatabaseReadWriteTransaction *_Nullable)transaction {
self.groupChatMessageID = serverMessageID;
if (transaction == nil) {
[self save];
[self.dbReadWriteConnection flushTransactionsWithCompletionQueue:dispatch_get_main_queue() completionBlock:^{}];
} else {
[self saveWithTransaction:transaction];
[transaction.connection flushTransactionsWithCompletionQueue:dispatch_get_main_queue() completionBlock:^{}];
}
}
@end
NS_ASSUME_NONNULL_END

@ -1360,7 +1360,7 @@ NS_ASSUME_NONNULL_BEGIN
envelopeAddress(envelope),
groupId,
(unsigned long)timestamp);
// Legit usage of senderTimestamp when creating an incoming group message record
TSIncomingMessage *incomingMessage =
[[TSIncomingMessage alloc] initIncomingMessageWithTimestamp:timestamp
@ -1377,6 +1377,7 @@ NS_ASSUME_NONNULL_BEGIN
wasReceivedByUD:wasReceivedByUD];
if (envelope.isPtpMessage) { incomingMessage.isP2P = YES; }
if (dataMessage.publicChatInfo && dataMessage.publicChatInfo.hasServerID) { incomingMessage.groupChatMessageID = dataMessage.publicChatInfo.serverID; }
NSArray<TSAttachmentPointer *> *attachmentPointers =
[TSAttachmentPointer attachmentPointersFromProtos:dataMessage.attachments

@ -1117,6 +1117,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
[[LKGroupChatAPI sendMessage:groupMessage toGroup:LKGroupChatAPI.publicChatServerID onServer:LKGroupChatAPI.publicChatServer]
.thenOn(OWSDispatch.sendingQueue, ^(LKGroupMessage *groupMessage) {
[self.dbConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[message saveGroupChatMessageID:groupMessage.serverID in:transaction];
[OWSPrimaryStorage.sharedManager setIDForMessageWithServerID:groupMessage.serverID to:message.uniqueId in:transaction];
}];
[self messageSendDidSucceed:messageSend deviceMessages:deviceMessages wasSentByUD:false wasSentByWebsocket:false];

Loading…
Cancel
Save