Cleaned up some TODOs and refactored more requests to prepared ones

pull/941/head
Morgan Pretty 2 years ago
parent 67311f6d25
commit 9e471fb903

@ -510,6 +510,8 @@
FD17D7CD27F546FF00122BE0 /* Setting.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7CC27F546FF00122BE0 /* Setting.swift */; };
FD17D7E527F6A09900122BE0 /* Identity.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7E427F6A09900122BE0 /* Identity.swift */; };
FD17D7E727F6A16700122BE0 /* _003_YDBToGRDBMigration.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD17D7E627F6A16700122BE0 /* _003_YDBToGRDBMigration.swift */; };
FD19363A2ACA25BA004BCF0F /* UpdatableTimestamp.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1936392ACA25BA004BCF0F /* UpdatableTimestamp.swift */; };
FD19363C2ACA3134004BCF0F /* ResponseInfo+SnodeAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD19363B2ACA3134004BCF0F /* ResponseInfo+SnodeAPI.swift */; };
FD1A94FB2900D1C2000D73D3 /* PersistableRecord+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1A94FA2900D1C2000D73D3 /* PersistableRecord+Utilities.swift */; };
FD1A94FE2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1A94FD2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift */; };
FD1C98E4282E3C5B00B76F9E /* UINavigationBar+Utilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = FD1C98E3282E3C5B00B76F9E /* UINavigationBar+Utilities.swift */; };
@ -1735,6 +1737,8 @@
FD17D7CC27F546FF00122BE0 /* Setting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Setting.swift; sourceTree = "<group>"; };
FD17D7E427F6A09900122BE0 /* Identity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Identity.swift; sourceTree = "<group>"; };
FD17D7E627F6A16700122BE0 /* _003_YDBToGRDBMigration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = _003_YDBToGRDBMigration.swift; sourceTree = "<group>"; };
FD1936392ACA25BA004BCF0F /* UpdatableTimestamp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpdatableTimestamp.swift; sourceTree = "<group>"; };
FD19363B2ACA3134004BCF0F /* ResponseInfo+SnodeAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ResponseInfo+SnodeAPI.swift"; sourceTree = "<group>"; };
FD1A94FA2900D1C2000D73D3 /* PersistableRecord+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PersistableRecord+Utilities.swift"; sourceTree = "<group>"; };
FD1A94FD2900D2EA000D73D3 /* PersistableRecordUtilitiesSpec.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistableRecordUtilitiesSpec.swift; sourceTree = "<group>"; };
FD1C98E3282E3C5B00B76F9E /* UINavigationBar+Utilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+Utilities.swift"; sourceTree = "<group>"; };
@ -4611,6 +4615,7 @@
FDF848E029405D6E007DCAE5 /* SnodeAPIError.swift */,
FDF8489029405C13007DCAE5 /* SnodeAPINamespace.swift */,
FD47E0BF2AA83D7300A55E41 /* SwarmDrainBehaviour.swift */,
FD1936392ACA25BA004BCF0F /* UpdatableTimestamp.swift */,
FD43242F2999F0BC008A0213 /* ValidatableResponse.swift */,
);
path = Types;
@ -4622,6 +4627,7 @@
FD47E0AA2AA68EEA00A55E41 /* Authentication.swift */,
FDF8489329405C1B007DCAE5 /* SnodeAPI.swift */,
FD47E0B42AA6D7AA00A55E41 /* Request+SnodeAPI.swift */,
FD19363B2ACA3134004BCF0F /* ResponseInfo+SnodeAPI.swift */,
FDF848E829405E4E007DCAE5 /* OnionRequestAPI.swift */,
FDF848E929405E4E007DCAE5 /* OnionRequestAPI+Encryption.swift */,
FDF848EA29405E4E007DCAE5 /* Notification+OnionRequestAPI.swift */,
@ -6008,6 +6014,7 @@
FDF848BC29405C5A007DCAE5 /* SnodeRecursiveResponse.swift in Sources */,
FDF848C029405C5A007DCAE5 /* ONSResolveResponse.swift in Sources */,
FD17D7A427F40F8100122BE0 /* _003_YDBToGRDBMigration.swift in Sources */,
FD19363A2ACA25BA004BCF0F /* UpdatableTimestamp.swift in Sources */,
FDFC4D9A29F0C51500992FB6 /* String+Trimming.swift in Sources */,
FDB5DAF32A96DD4F002C8721 /* PreparedRequest+OnionRequest.swift in Sources */,
FDF848C629405C5B007DCAE5 /* DeleteAllMessagesRequest.swift in Sources */,
@ -6017,6 +6024,7 @@
FDF848D129405C5B007DCAE5 /* SnodeSwarmItem.swift in Sources */,
FDF848DD29405C5B007DCAE5 /* LegacySendMessageRequest.swift in Sources */,
FDF848BD29405C5A007DCAE5 /* GetMessagesRequest.swift in Sources */,
FD19363C2ACA3134004BCF0F /* ResponseInfo+SnodeAPI.swift in Sources */,
FDF848DB29405C5B007DCAE5 /* DeleteMessagesResponse.swift in Sources */,
FDF848E629405D6E007DCAE5 /* OnionRequestAPIDestination.swift in Sources */,
FD47E0AB2AA68EEA00A55E41 /* Authentication.swift in Sources */,

@ -427,7 +427,7 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat
navigationController?.pushViewController(userSelectionVC, animated: true, completion: nil)
}
private func commitChanges() {
private func commitChanges(using dependencies: Dependencies = Dependencies()) {
let popToConversationVC: ((EditClosedGroupVC?) -> ()) = { editVC in
guard
let viewControllers: [UIViewController] = editVC?.navigationController?.viewControllers,
@ -443,9 +443,9 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat
let threadId: String = self.threadId
let updatedName: String = self.name
let userPublicKey: String = self.userPublicKey
let updatedMemberIds: Set<String> = self.membersAndZombies
.map { $0.profileId }
.asSet()
let updatedMembers: [(String, Profile?)] = self.membersAndZombies
.map { ($0.profileId, $0.profile) }
let updatedMemberIds: Set<String> = updatedMembers.map { $0.0 }.asSet()
guard updatedMemberIds != self.originalMembersAndZombieIds || updatedName != self.originalName else {
return popToConversationVC(self)
@ -464,24 +464,43 @@ final class EditClosedGroupVC: BaseVC, UITableViewDataSource, UITableViewDelegat
}
ModalActivityIndicatorViewController.present(fromViewController: navigationController) { _ in
Dependencies()[singleton: .storage]
.writePublisher { db in
// If the user is no longer a member then leave the group
guard !updatedMemberIds.contains(userPublicKey) else { return }
try MessageSender.leave(
db,
groupPublicKey: threadId,
deleteThread: true
)
}
.flatMap {
MessageSender.update(
legacyGroupPublicKey: threadId,
with: updatedMemberIds,
name: updatedName
// If the user is no longer a member then leave the group
guard updatedMemberIds.contains(userPublicKey) else {
dependencies[singleton: .storage]
.writePublisher { db in
try MessageSender.leave(
db,
groupPublicKey: threadId,
deleteThread: true
)
}
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
.receive(on: DispatchQueue.main)
.sinkUntilComplete(
receiveCompletion: { [weak self] result in
self?.dismiss(animated: true, completion: nil) // Dismiss the loader
switch result {
case .finished: popToConversationVC(self)
case .failure(let error):
self?.showError(
title: "GROUP_UPDATE_ERROR_TITLE".localized(),
message: error.localizedDescription
)
}
}
)
}
return
}
// Otherwise update the group details
MessageSender
.updateGroup(
groupIdentityPublicKey: threadId,
name: updatedName,
displayPicture: nil,
members: updatedMembers
)
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
.receive(on: DispatchQueue.main)
.sinkUntilComplete(

@ -168,43 +168,54 @@ final class NukeDataModal: Modal {
presentedViewController: UIViewController,
using dependencies: Dependencies = Dependencies()
) {
typealias PreparedClearRequests = (
deleteAll: HTTP.PreparedRequest<[String: Bool]>,
inboxRequestInfo: [HTTP.PreparedRequest<String>]
)
ModalActivityIndicatorViewController
.present(fromViewController: presentedViewController, canCancel: false) { [weak self] _ in
Publishers
.MergeMany(
dependencies[singleton: .storage]
.read { db -> [(String, HTTP.PreparedRequest<OpenGroupAPI.DeleteInboxResponse>)] in
return try OpenGroup
.filter(OpenGroup.Columns.isActive == true)
.select(.server)
.distinct()
.asRequest(of: String.self)
.fetchSet(db)
.map { ($0, try OpenGroupAPI.preparedClearInbox(db, on: $0))}
}
.defaulting(to: [])
.compactMap { server, preparedRequest in
preparedRequest
.send(using: dependencies)
.map { _ in [server: true] }
.eraseToAnyPublisher()
}
)
.collect()
dependencies[singleton: .storage]
.readPublisher { db -> PreparedClearRequests in
let authInfo: SnodeAPI.AuthenticationInfo = try SnodeAPI.AuthenticationInfo(
db,
threadId: getUserHexEncodedPublicKey(db, using: dependencies),
using: dependencies
)
return (
try SnodeAPI.preparedDeleteAllMessages(
namespace: .all,
authInfo: authInfo,
using: dependencies
),
try OpenGroup
.filter(OpenGroup.Columns.isActive == true)
.select(.server)
.distinct()
.asRequest(of: String.self)
.fetchSet(db)
.map { server in
try OpenGroupAPI.preparedClearInbox(db, on: server)
.map { _, _ in server }
}
)
}
.subscribe(on: DispatchQueue.global(qos: .userInitiated), using: dependencies)
.flatMap { results in
dependencies[singleton: .storage]
.readPublisher(using: dependencies) { db in
try SnodeAPI.AuthenticationInfo(
db,
threadId: getUserHexEncodedPublicKey(db, using: dependencies),
using: dependencies
)
}
.flatMap { SnodeAPI.deleteAllMessages(namespace: .all, authInfo: $0) }
.map { results.reduce($0) { result, next in result.updated(with: next) } }
.flatMap { preparedRequests -> AnyPublisher<(HTTP.PreparedRequest<[String: Bool]>, [String]), Error> in
Publishers
.MergeMany(preparedRequests.inboxRequestInfo.map { $0.send(using: dependencies) })
.collect()
.map { response in (preparedRequests.deleteAll, response.map { $0.1 }) }
.eraseToAnyPublisher()
}
.flatMap { preparedDeleteAllRequest, clearedServers in
preparedDeleteAllRequest
.send(using: dependencies)
.map { _, data in
clearedServers.reduce(into: data) { result, next in result[next] = true }
}
}
.receive(on: DispatchQueue.main, using: dependencies)
.sinkUntilComplete(
receiveCompletion: { result in

@ -165,11 +165,17 @@ extension MessageSender {
groupIdentityPublicKey: String,
name: String,
displayPicture: SignalAttachment?,
members: [(String, Profile?)],
using dependencies: Dependencies = Dependencies()
) -> AnyPublisher<Void, Error> {
guard SessionId.Prefix(from: groupIdentityPublicKey) == .group else {
return Fail(error: MessageSenderError.invalidClosedGroupUpdate)
.eraseToAnyPublisher()
// FIXME: Fail with `MessageSenderError.invalidClosedGroupUpdate` once support for legacy groups is removed
return MessageSender.update(
legacyGroupPublicKey: groupIdentityPublicKey,
with: members.map { $0.0 }.asSet(),
name: name,
using: dependencies
)
}
return dependencies[singleton: .storage]

@ -221,11 +221,17 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView
) :
messageText
)
let publicKey: String = {
switch threadVariant {
case .contact, .legacyGroup, .group: return threadId
case .community: return getUserHexEncodedPublicKey(using: dependencies)
}
}()
shareNavController?.dismiss(animated: true, completion: nil)
ModalActivityIndicatorViewController.present(fromViewController: shareNavController!, canCancel: false, message: "vc_share_sending_message".localized()) { activityIndicator in
Storage.resumeDatabaseAccess()
Storage.resumeDatabaseAccess(using: dependencies)
/// When we prepare the message we set the timestamp to be the `SnodeAPI.currentOffsetTimestampMs()`
/// but won't actually have a value because the share extension won't have talked to a service node yet which can cause
@ -246,18 +252,15 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView
.eraseToAnyPublisher()
}
.subscribe(on: DispatchQueue.global(qos: .userInitiated))
.flatMap { _ in
.flatMap { _ -> AnyPublisher<Void, Error> in
SnodeAPI
.getSwarm(
for: {
switch threadVariant {
case .contact, .legacyGroup, .group: return threadId
case .community: return getUserHexEncodedPublicKey(using: dependencies)
}
}(),
using: dependencies
)
.tryFlatMapWithRandomSnode { SnodeAPI.getNetworkTime(from: $0, using: dependencies) }
.getSwarm(for: publicKey, using: dependencies)
.tryFlatMapWithRandomSnode { snode in
Just(try SnodeAPI.preparedGetNetworkTime(from: snode, using: dependencies))
.setFailureType(to: Error.self)
.eraseToAnyPublisher()
}
.map { $0.send(using: dependencies) }
.map { _ in () }
.eraseToAnyPublisher()
}
@ -353,7 +356,7 @@ final class ThreadPickerVC: UIViewController, UITableViewDataSource, UITableView
.receive(on: DispatchQueue.main)
.sinkUntilComplete(
receiveCompletion: { [weak self] result in
Storage.suspendDatabaseAccess()
Storage.suspendDatabaseAccess(using: dependencies)
activityIndicator.dismiss { }
switch result {

@ -4,7 +4,7 @@ import Foundation
import SessionUtilitiesKit
extension SnodeAPI {
public class DeleteAllBeforeRequest: SnodeAuthenticatedRequestBody {
public final class DeleteAllBeforeRequest: SnodeAuthenticatedRequestBody, UpdatableTimestamp {
enum CodingKeys: String, CodingKey {
case beforeMs = "before"
case namespace
@ -47,6 +47,17 @@ extension SnodeAPI {
try super.encode(to: encoder)
}
// MARK: - UpdatableTimestamp
public func with(timestampMs: UInt64) -> DeleteAllBeforeRequest {
return DeleteAllBeforeRequest(
beforeMs: self.beforeMs,
namespace: self.namespace,
authInfo: self.authInfo,
timestampMs: timestampMs
)
}
// MARK: - Abstract Methods
override func generateSignature(using dependencies: Dependencies) throws -> [UInt8] {

@ -4,7 +4,7 @@ import Foundation
import SessionUtilitiesKit
extension SnodeAPI {
public class DeleteAllMessagesRequest: SnodeAuthenticatedRequestBody {
public final class DeleteAllMessagesRequest: SnodeAuthenticatedRequestBody, UpdatableTimestamp {
enum CodingKeys: String, CodingKey {
case namespace
}
@ -45,6 +45,16 @@ extension SnodeAPI {
try super.encode(to: encoder)
}
// MARK: - UpdatableTimestamp
public func with(timestampMs: UInt64) -> DeleteAllMessagesRequest {
return DeleteAllMessagesRequest(
namespace: self.namespace,
authInfo: self.authInfo,
timestampMs: timestampMs
)
}
// MARK: - Abstract Methods
override func generateSignature(using dependencies: Dependencies) throws -> [UInt8] {

@ -37,3 +37,14 @@ public struct SnodeRequest<T: Encodable>: Encodable {
extension SnodeRequest: BatchRequestChildRetrievable where T: BatchRequestChildRetrievable {
public var requests: [HTTP.BatchRequest.Child] { body.requests }
}
// MARK: - UpdatableTimestamp
extension SnodeRequest: UpdatableTimestamp where T: UpdatableTimestamp {
public func with(timestampMs: UInt64) -> SnodeRequest<T> {
return SnodeRequest(
endpoint: self.endpoint,
body: self.body.with(timestampMs: timestampMs)
)
}
}

@ -36,9 +36,21 @@ public extension HTTP.PreparedRequest {
)
)
case let randomSnode as HTTP.RandomSnodeTarget:
case let snodeTarget as HTTP.SnodeTarget:
guard let payload: Data = request.httpBody else { throw HTTPError.invalidPreparedRequest }
return dependencies[singleton: .network]
.send(
.onionRequest(
payload,
to: snodeTarget.snode,
timeout: timeout
)
)
case let randomSnode as HTTP.RandomSnodeTarget:
guard let payload: Data = request.httpBody else { throw HTTPError.invalidPreparedRequest }
return SnodeAPI.getSwarm(for: randomSnode.publicKey, using: dependencies)
.tryFlatMapWithRandomSnode(retry: SnodeAPI.maxRetryCount) { snode in
dependencies[singleton: .network]
@ -51,6 +63,42 @@ public extension HTTP.PreparedRequest {
)
}
case let randomSnode as HTTP.RandomSnodeLatestNetworkTimeTarget:
guard request.httpBody != nil else { throw HTTPError.invalidPreparedRequest }
return SnodeAPI.getSwarm(for: randomSnode.publicKey, using: dependencies)
.tryFlatMapWithRandomSnode(retry: SnodeAPI.maxRetryCount) { snode in
try SnodeAPI
.preparedGetNetworkTime(from: snode, using: dependencies)
.send(using: dependencies)
.tryFlatMap { _, timestampMs in
guard
let updatedRequest: URLRequest = try? randomSnode
.urlRequestWithUpdatedTimestampMs(timestampMs, dependencies),
let payload: Data = updatedRequest.httpBody
else { throw HTTPError.invalidPreparedRequest }
return dependencies[singleton: .network]
.send(
.onionRequest(
payload,
to: snode,
timeout: timeout
)
)
.map { info, response -> (ResponseInfoType, Data?) in
(
SnodeAPI.LatestTimestampResponseInfo(
code: info.code,
headers: info.headers,
timestampMs: timestampMs
),
response
)
}
}
}
default: throw HTTPError.invalidPreparedRequest
}
}

@ -3,18 +3,66 @@
import Foundation
import SessionUtilitiesKit
// MARK: - SnodeTarget
internal extension HTTP {
struct SnodeTarget: RequestTarget, Equatable {
let snode: Snode
var url: URL? { URL(string: "snode:\(snode.x25519PublicKey)") }
var urlPathAndParamsString: String { return "" }
}
}
// MARK: - RandomSnodeTarget
internal extension HTTP {
struct RandomSnodeTarget: RequestTarget, Equatable {
let publicKey: String
let requiresLatestNetworkTime: Bool
var url: URL? { URL(string: "snode:\(publicKey)") }
var urlPathAndParamsString: String { return "" }
}
}
// MARK: - RandomSnodeLatestNetworkTimeTarget
internal extension HTTP {
struct RandomSnodeLatestNetworkTimeTarget: RequestTarget, Equatable {
let publicKey: String
let urlRequestWithUpdatedTimestampMs: ((UInt64, Dependencies) throws -> URLRequest)
var url: URL? { URL(string: "snode:\(publicKey)") }
var urlPathAndParamsString: String { return "" }
static func == (lhs: HTTP.RandomSnodeLatestNetworkTimeTarget, rhs: HTTP.RandomSnodeLatestNetworkTimeTarget) -> Bool {
lhs.publicKey == rhs.publicKey
}
}
}
// MARK: Request - SnodeTarget
public extension Request {
init(
method: HTTPMethod = .get,
endpoint: Endpoint,
snode: Snode,
headers: [HTTPHeader: String] = [:],
body: T? = nil
) {
self = Request(
method: method,
endpoint: endpoint,
target: HTTP.SnodeTarget(
snode: snode
),
headers: headers,
body: body
)
}
}
// MARK: Request - RandomSnodeTarget
public extension Request {
@ -29,8 +77,39 @@ public extension Request {
method: method,
endpoint: endpoint,
target: HTTP.RandomSnodeTarget(
publicKey: publicKey
),
headers: headers,
body: body
)
}
}
// MARK: Request - RandomSnodeLatestNetworkTimeTarget
public extension Request {
init(
method: HTTPMethod = .get,
endpoint: Endpoint,
publicKey: String,
headers: [HTTPHeader: String] = [:],
requiresLatestNetworkTime: Bool,
body: T? = nil
) where T: UpdatableTimestamp {
self = Request(
method: method,
endpoint: endpoint,
target: HTTP.RandomSnodeLatestNetworkTimeTarget(
publicKey: publicKey,
requiresLatestNetworkTime: false // TODO: Sort this out
urlRequestWithUpdatedTimestampMs: { timestampMs, dependencies in
try Request(
method: method,
endpoint: endpoint,
publicKey: publicKey,
headers: headers,
body: body?.with(timestampMs: timestampMs)
).generateUrlRequest(using: dependencies)
}
),
headers: headers,
body: body

@ -0,0 +1,18 @@
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
import Foundation
import SessionUtilitiesKit
public extension SnodeAPI {
struct LatestTimestampResponseInfo: ResponseInfoType {
public let code: Int
public let headers: [String: String]
public let timestampMs: UInt64
public init(code: Int, headers: [String: String], timestampMs: UInt64) {
self.code = code
self.headers = headers
self.timestampMs = timestampMs
}
}
}

@ -861,111 +861,73 @@ public final class SnodeAPI {
}
/// Clears all the user's data from their swarm. Returns a dictionary of snode public key to deletion confirmation.
public static func deleteAllMessages(
public static func preparedDeleteAllMessages(
namespace: SnodeAPI.Namespace,
authInfo: AuthenticationInfo,
using dependencies: Dependencies = Dependencies()
) -> AnyPublisher<[String: Bool], Error> {
return getSwarm(for: authInfo.publicKey, using: dependencies)
.tryFlatMapWithRandomSnode(retry: maxRetryCount) { snode -> AnyPublisher<[String: Bool], Error> in
getNetworkTime(from: snode)
.flatMap { timestampMs -> AnyPublisher<[String: Bool], Error> in
SnodeAPI
.send(
request: SnodeRequest(
endpoint: .deleteAll,
body: DeleteAllMessagesRequest(
namespace: namespace,
authInfo: authInfo,
timestampMs: timestampMs
)
),
to: snode,
associatedWith: authInfo.publicKey,
using: dependencies
)
.decoded(as: DeleteAllMessagesResponse.self, using: dependencies)
.tryMap { _, response -> [String: Bool] in
try response.validResultMap(
publicKey: authInfo.publicKey,
validationData: timestampMs,
using: dependencies
)
}
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
) throws -> HTTP.PreparedRequest<[String: Bool]> {
return try SnodeAPI
.prepareRequest(
request: Request(
endpoint: .deleteAll,
publicKey: authInfo.publicKey,
requiresLatestNetworkTime: true,
body: DeleteAllMessagesRequest(
namespace: namespace,
authInfo: authInfo,
timestampMs: UInt64(SnodeAPI.currentOffsetTimestampMs(using: dependencies))
)
),
responseType: DeleteAllMessagesResponse.self,
retryCount: maxRetryCount
)
.tryMap { info, response -> [String: Bool] in
guard let targetInfo: LatestTimestampResponseInfo = info as? LatestTimestampResponseInfo else {
throw HTTPError.invalidResponse
}
return try response.validResultMap(
publicKey: authInfo.publicKey,
validationData: targetInfo.timestampMs,
using: dependencies
)
}
}
/// Clears all the user's data from their swarm. Returns a dictionary of snode public key to deletion confirmation.
public static func deleteAllMessages(
public static func preparedDeleteAllMessages(
beforeMs: UInt64,
namespace: SnodeAPI.Namespace,
authInfo: AuthenticationInfo,
using dependencies: Dependencies = Dependencies()
) -> AnyPublisher<[String: Bool], Error> {
return getSwarm(for: authInfo.publicKey, using: dependencies)
.tryFlatMapWithRandomSnode(retry: maxRetryCount) { snode -> AnyPublisher<[String: Bool], Error> in
getNetworkTime(from: snode)
.flatMap { timestampMs -> AnyPublisher<[String: Bool], Error> in
SnodeAPI
.send(
request: SnodeRequest(
endpoint: .deleteAllBefore,
body: DeleteAllBeforeRequest(
beforeMs: beforeMs,
namespace: namespace,
authInfo: authInfo,
timestampMs: timestampMs
)
),
to: snode,
associatedWith: authInfo.publicKey,
using: dependencies
)
.decoded(as: DeleteAllBeforeResponse.self, using: dependencies)
.tryMap { _, response -> [String: Bool] in
try response.validResultMap(
publicKey: authInfo.publicKey,
validationData: beforeMs,
using: dependencies
)
}
.eraseToAnyPublisher()
}
.eraseToAnyPublisher()
}
}
// MARK: - Internal API
public static func getNetworkTime(
from snode: Snode,
using dependencies: Dependencies = Dependencies()
) -> AnyPublisher<UInt64, Error> {
return SnodeAPI
.send(
request: SnodeRequest<[String: String]>(
endpoint: .getInfo,
body: [:]
) throws -> HTTP.PreparedRequest<[String: Bool]> {
return try SnodeAPI
.prepareRequest(
request: Request(
endpoint: .deleteAllBefore,
publicKey: authInfo.publicKey,
requiresLatestNetworkTime: true,
body: DeleteAllBeforeRequest(
beforeMs: beforeMs,
namespace: namespace,
authInfo: authInfo,
timestampMs: UInt64(SnodeAPI.currentOffsetTimestampMs(using: dependencies))
)
),
to: snode,
associatedWith: nil,
using: dependencies
responseType: DeleteAllMessagesResponse.self,
retryCount: maxRetryCount
)
.decoded(as: GetNetworkTimestampResponse.self, using: dependencies)
.map { _, response in
// Assume we've fetched the networkTime in order to send a message to the specified snode, in
// which case we want to update the 'clockOffsetMs' value for subsequent requests
let offset = (Int64(response.timestamp) - Int64(floor(dependencies.dateNow.timeIntervalSince1970 * 1000)))
SnodeAPI.clockOffsetMs.mutate { $0 = offset }
return response.timestamp
.tryMap { _, response -> [String: Bool] in
try response.validResultMap(
publicKey: authInfo.publicKey,
validationData: beforeMs,
using: dependencies
)
}
.eraseToAnyPublisher()
}
// MARK: - Internal API
public static func preparedGetNetworkTime(
from snode: Snode,
using dependencies: Dependencies = Dependencies()
@ -974,7 +936,7 @@ public final class SnodeAPI {
.prepareRequest(
request: Request<SnodeRequest<[String: String]>, Endpoint>(
endpoint: .getInfo,
publicKey: snode.x25519PublicKey,
snode: snode,
body: [:]
),
responseType: GetNetworkTimestampResponse.self
@ -1441,4 +1403,38 @@ private extension Request {
)
)
}
init<B: Encodable>(
endpoint: SnodeAPI.Endpoint,
snode: Snode,
body: B
) where T == SnodeRequest<B>, Endpoint == SnodeAPI.Endpoint {
self = Request(
method: .post,
endpoint: endpoint,
snode: snode,
body: SnodeRequest<B>(
endpoint: endpoint,
body: body
)
)
}
init<B>(
endpoint: SnodeAPI.Endpoint,
publicKey: String,
requiresLatestNetworkTime: Bool,
body: B
) where T == SnodeRequest<B>, Endpoint == SnodeAPI.Endpoint, B: Encodable & UpdatableTimestamp {
self = Request(
method: .post,
endpoint: endpoint,
publicKey: publicKey,
requiresLatestNetworkTime: requiresLatestNetworkTime,
body: SnodeRequest<B>(
endpoint: endpoint,
body: body
)
)
}
}

@ -0,0 +1,7 @@
// Copyright © 2023 Rangeproof Pty Ltd. All rights reserved.
import Foundation
public protocol UpdatableTimestamp {
func with(timestampMs: UInt64) -> Self
}

@ -18,4 +18,3 @@ public extension HTTP {
}
}
}

Loading…
Cancel
Save