From a5f831fd6cf69a62ca16074e1a087c04b08be5c6 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 24 Mar 2021 12:07:02 +1100 Subject: [PATCH] Keep track of last message & last deletion server IDs --- .../Database/Storage+OpenGroups.swift | 80 +++++++++++++++---- .../Open Groups/V2/OpenGroupAPIV2.swift | 67 +++++++++++++--- SessionMessagingKit/Storage.swift | 20 +++-- 3 files changed, 132 insertions(+), 35 deletions(-) diff --git a/SessionMessagingKit/Database/Storage+OpenGroups.swift b/SessionMessagingKit/Database/Storage+OpenGroups.swift index 0083054f3..c8f351210 100644 --- a/SessionMessagingKit/Database/Storage+OpenGroups.swift +++ b/SessionMessagingKit/Database/Storage+OpenGroups.swift @@ -121,44 +121,56 @@ extension Storage { // MARK: - Last Message Server ID - public static let lastMessageServerIDCollection = "LokiGroupChatLastMessageServerIDCollection" + public static let lastMessageServerIDCollection = "SNLastMessageServerIDCollection" - public func getLastMessageServerID(for group: UInt64, on server: String) -> UInt64? { - var result: UInt64? = nil + public func getLastMessageServerID(for room: String, on server: String) -> Int64? { + let collection = Storage.lastMessageServerIDCollection + let key = "\(server).\(room)" + var result: Int64? = nil Storage.read { transaction in - result = transaction.object(forKey: "\(server).\(group)", inCollection: Storage.lastMessageServerIDCollection) as? UInt64 + result = transaction.object(forKey: key, inCollection: collection) as? Int64 } return result } - public func setLastMessageServerID(for group: UInt64, on server: String, to newValue: UInt64, using transaction: Any) { - (transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: "\(server).\(group)", inCollection: Storage.lastMessageServerIDCollection) + public func setLastMessageServerID(for room: String, on server: String, to newValue: Int64, using transaction: Any) { + let collection = Storage.lastMessageServerIDCollection + let key = "\(server).\(room)" + (transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: key, inCollection: collection) } - public func removeLastMessageServerID(for group: UInt64, on server: String, using transaction: Any) { - (transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: "\(server).\(group)", inCollection: Storage.lastMessageServerIDCollection) + public func removeLastMessageServerID(for room: String, on server: String, using transaction: Any) { + let collection = Storage.lastMessageServerIDCollection + let key = "\(server).\(room)" + (transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: key, inCollection: collection) } // MARK: - Last Deletion Server ID - public static let lastDeletionServerIDCollection = "LokiGroupChatLastDeletionServerIDCollection" + public static let lastDeletionServerIDCollection = "SNLastDeletionServerIDCollection" - public func getLastDeletionServerID(for group: UInt64, on server: String) -> UInt64? { - var result: UInt64? = nil + public func getLastDeletionServerID(for room: String, on server: String) -> Int64? { + let collection = Storage.lastDeletionServerIDCollection + let key = "\(server).\(room)" + var result: Int64? = nil Storage.read { transaction in - result = transaction.object(forKey: "\(server).\(group)", inCollection: Storage.lastDeletionServerIDCollection) as? UInt64 + result = transaction.object(forKey: key, inCollection: collection) as? Int64 } return result } - public func setLastDeletionServerID(for group: UInt64, on server: String, to newValue: UInt64, using transaction: Any) { - (transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: "\(server).\(group)", inCollection: Storage.lastDeletionServerIDCollection) + public func setLastDeletionServerID(for room: String, on server: String, to newValue: Int64, using transaction: Any) { + let collection = Storage.lastDeletionServerIDCollection + let key = "\(server).\(room)" + (transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: key, inCollection: collection) } - public func removeLastDeletionServerID(for group: UInt64, on server: String, using transaction: Any) { - (transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: "\(server).\(group)", inCollection: Storage.lastDeletionServerIDCollection) + public func removeLastDeletionServerID(for room: String, on server: String, using transaction: Any) { + let collection = Storage.lastDeletionServerIDCollection + let key = "\(server).\(room)" + (transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: key, inCollection: collection) } @@ -240,4 +252,40 @@ extension Storage { let collection = Storage.getAuthTokenCollection(for: server) (transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: server, inCollection: collection) } + + public static let oldLastMessageServerIDCollection = "LokiGroupChatLastMessageServerIDCollection" + + public func getLastMessageServerID(for group: UInt64, on server: String) -> UInt64? { + var result: UInt64? = nil + Storage.read { transaction in + result = transaction.object(forKey: "\(server).\(group)", inCollection: Storage.oldLastMessageServerIDCollection) as? UInt64 + } + return result + } + + public func setLastMessageServerID(for group: UInt64, on server: String, to newValue: UInt64, using transaction: Any) { + (transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: "\(server).\(group)", inCollection: Storage.oldLastMessageServerIDCollection) + } + + public func removeLastMessageServerID(for group: UInt64, on server: String, using transaction: Any) { + (transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: "\(server).\(group)", inCollection: Storage.oldLastMessageServerIDCollection) + } + + public static let oldLastDeletionServerIDCollection = "LokiGroupChatLastDeletionServerIDCollection" + + public func getLastDeletionServerID(for group: UInt64, on server: String) -> UInt64? { + var result: UInt64? = nil + Storage.read { transaction in + result = transaction.object(forKey: "\(server).\(group)", inCollection: Storage.oldLastDeletionServerIDCollection) as? UInt64 + } + return result + } + + public func setLastDeletionServerID(for group: UInt64, on server: String, to newValue: UInt64, using transaction: Any) { + (transaction as! YapDatabaseReadWriteTransaction).setObject(newValue, forKey: "\(server).\(group)", inCollection: Storage.oldLastDeletionServerIDCollection) + } + + public func removeLastDeletionServerID(for group: UInt64, on server: String, using transaction: Any) { + (transaction as! YapDatabaseReadWriteTransaction).removeObject(forKey: "\(server).\(group)", inCollection: Storage.oldLastDeletionServerIDCollection) + } } diff --git a/SessionMessagingKit/Open Groups/V2/OpenGroupAPIV2.swift b/SessionMessagingKit/Open Groups/V2/OpenGroupAPIV2.swift index 24f339e80..a0b7a0fe3 100644 --- a/SessionMessagingKit/Open Groups/V2/OpenGroupAPIV2.swift +++ b/SessionMessagingKit/Open Groups/V2/OpenGroupAPIV2.swift @@ -1,6 +1,8 @@ import PromiseKit import SessionSnodeKit +// TODO: Message signature validation + public enum OpenGroupAPIV2 { // MARK: Error @@ -70,8 +72,12 @@ public enum OpenGroupAPIV2 { tsRequest.setValue(request.room, forKey: "Room") if request.useOnionRouting { guard let publicKey = SNMessagingKitConfiguration.shared.storage.getOpenGroupPublicKey(for: request.server) else { return Promise(error: Error.noPublicKey) } - return getAuthToken(for: request.room, on: request.server).then(on: DispatchQueue.global(qos: .default)) { authToken -> Promise in - tsRequest.setValue(authToken, forKey: "Authorization") + if request.isAuthRequired { + return getAuthToken(for: request.room, on: request.server).then(on: DispatchQueue.global(qos: .default)) { authToken -> Promise in + tsRequest.setValue(authToken, forKey: "Authorization") + return OnionRequestAPI.sendOnionRequest(tsRequest, to: request.server, using: publicKey) + } + } else { return OnionRequestAPI.sendOnionRequest(tsRequest, to: request.server, using: publicKey) } } else { @@ -125,7 +131,12 @@ public enum OpenGroupAPIV2 { /// Should be called when leaving a group. public static func deleteAuthToken(for room: String, on server: String) -> Promise { let request = Request(verb: .delete, room: room, server: server, endpoint: "auth_token") - return send(request).map(on: DispatchQueue.global(qos: .userInitiated)) { _ in } + return send(request).map(on: DispatchQueue.global(qos: .userInitiated)) { _ in + let storage = SNMessagingKitConfiguration.shared.storage + storage.write { transaction in + storage.removeAuthToken(for: room, on: server, using: transaction) + } + } } // MARK: File Storage @@ -159,20 +170,35 @@ public enum OpenGroupAPIV2 { } public static func getMessages(for room: String, on server: String) -> Promise<[OpenGroupMessageV2]> { - // TODO: From server ID & limit - let queryParameters: [String:String] = [:] + let storage = SNMessagingKitConfiguration.shared.storage + var queryParameters: [String:String] = [:] + if let lastMessageServerID = storage.getLastMessageServerID(for: room, on: server) { + queryParameters["from_server_id"] = String(lastMessageServerID) + } let request = Request(verb: .get, room: room, server: server, endpoint: "messages", queryParameters: queryParameters) - return send(request).map(on: DispatchQueue.global(qos: .userInitiated)) { json in + return send(request).then(on: DispatchQueue.global(qos: .userInitiated)) { json -> Promise<[OpenGroupMessageV2]> in guard let rawMessages = json["messages"] as? [[String:Any]] else { throw Error.parsingFailed } let messages: [OpenGroupMessageV2] = rawMessages.compactMap { json in // TODO: Signature validation - guard let message = OpenGroupMessageV2.fromJSON(json) else { + guard let message = OpenGroupMessageV2.fromJSON(json), message.serverID != nil else { SNLog("Couldn't parse open group message from JSON: \(json).") return nil } return message } - return messages + let serverID = messages.map { $0.serverID! }.max() ?? 0 // Safe because messages with a nil serverID are filtered out above + let lastMessageServerID = storage.getLastMessageServerID(for: room, on: server) ?? 0 + if serverID > lastMessageServerID { + let (promise, seal) = Promise<[OpenGroupMessageV2]>.pending() + storage.write(with: { transaction in + storage.setLastMessageServerID(for: room, on: server, to: serverID, using: transaction) + }, completion: { + seal.fulfill(messages) + }) + return promise + } else { + return Promise.value(messages) + } } } @@ -183,12 +209,27 @@ public enum OpenGroupAPIV2 { } public static func getDeletedMessages(for room: String, on server: String) -> Promise<[Int64]> { - // TODO: From server ID & limit - let queryParameters: [String:String] = [:] + let storage = SNMessagingKitConfiguration.shared.storage + var queryParameters: [String:String] = [:] + if let lastDeletionServerID = storage.getLastDeletionServerID(for: room, on: server) { + queryParameters["from_server_id"] = String(lastDeletionServerID) + } let request = Request(verb: .get, room: room, server: server, endpoint: "deleted_messages", queryParameters: queryParameters) - return send(request).map(on: DispatchQueue.global(qos: .userInitiated)) { json in - guard let ids = json["ids"] as? [Int64] else { throw Error.parsingFailed } - return ids + return send(request).then(on: DispatchQueue.global(qos: .userInitiated)) { json -> Promise<[Int64]> in + guard let serverIDs = json["ids"] as? [Int64] else { throw Error.parsingFailed } + let serverID = serverIDs.max() ?? 0 + let lastDeletionServerID = storage.getLastDeletionServerID(for: room, on: server) ?? 0 + if serverID > lastDeletionServerID { + let (promise, seal) = Promise<[Int64]>.pending() + storage.write(with: { transaction in + storage.setLastDeletionServerID(for: room, on: server, to: serverID, using: transaction) + }, completion: { + seal.fulfill(serverIDs) + }) + return promise + } else { + return Promise.value(serverIDs) + } } } diff --git a/SessionMessagingKit/Storage.swift b/SessionMessagingKit/Storage.swift index 11877cc2e..a3dda1822 100644 --- a/SessionMessagingKit/Storage.swift +++ b/SessionMessagingKit/Storage.swift @@ -55,15 +55,15 @@ public protocol SessionMessagingKitStorageProtocol { // MARK: - Last Message Server ID - func getLastMessageServerID(for group: UInt64, on server: String) -> UInt64? - func setLastMessageServerID(for group: UInt64, on server: String, to newValue: UInt64, using transaction: Any) - func removeLastMessageServerID(for group: UInt64, on server: String, using transaction: Any) + func getLastMessageServerID(for room: String, on server: String) -> Int64? + func setLastMessageServerID(for room: String, on server: String, to newValue: Int64, using transaction: Any) + func removeLastMessageServerID(for room: String, on server: String, using transaction: Any) // MARK: - Last Deletion Server ID - func getLastDeletionServerID(for group: UInt64, on server: String) -> UInt64? - func setLastDeletionServerID(for group: UInt64, on server: String, to newValue: UInt64, using transaction: Any) - func removeLastDeletionServerID(for group: UInt64, on server: String, using transaction: Any) + func getLastDeletionServerID(for room: String, on server: String) -> Int64? + func setLastDeletionServerID(for room: String, on server: String, to newValue: Int64, using transaction: Any) + func removeLastDeletionServerID(for room: String, on server: String, using transaction: Any) // MARK: - Open Group Metadata @@ -93,4 +93,12 @@ public protocol SessionMessagingKitStorageProtocol { func getAuthToken(for server: String) -> String? func setAuthToken(for server: String, to newValue: String, using transaction: Any) func removeAuthToken(for server: String, using transaction: Any) + + func getLastMessageServerID(for group: UInt64, on server: String) -> UInt64? + func setLastMessageServerID(for group: UInt64, on server: String, to newValue: UInt64, using transaction: Any) + func removeLastMessageServerID(for group: UInt64, on server: String, using transaction: Any) + + func getLastDeletionServerID(for group: UInt64, on server: String) -> UInt64? + func setLastDeletionServerID(for group: UInt64, on server: String, to newValue: UInt64, using transaction: Any) + func removeLastDeletionServerID(for group: UInt64, on server: String, using transaction: Any) }