From a209b6ceda3e3de882b2ff6925dc26467f78b7b2 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 14 Nov 2024 17:10:43 -0600 Subject: [PATCH 1/7] Async location witness --- .../Witness/Location/LocationWitness.swift | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 Sources/XyoClient/Witness/Location/LocationWitness.swift diff --git a/Sources/XyoClient/Witness/Location/LocationWitness.swift b/Sources/XyoClient/Witness/Location/LocationWitness.swift new file mode 100644 index 0000000..43c895e --- /dev/null +++ b/Sources/XyoClient/Witness/Location/LocationWitness.swift @@ -0,0 +1,21 @@ +import CoreLocation +import Foundation + +open class LocationWitness: AbstractAsyncWitness { + private let locationService = LocationService() + + override open func observe(completion: @escaping ([Payload]?, Error?) -> Void) { + locationService.requestAuthorization() + locationService.requestLocation { result in + DispatchQueue.main.async { + switch result { + case .success(let location): + let payload = LocationPayload(location) + completion([payload], nil) + case .failure(let error): + completion(nil, error) + } + } + } + } +} From 2e66d0e005c5fb68b7157c2101cb5cec76c698c9 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 14 Nov 2024 17:13:08 -0600 Subject: [PATCH 2/7] Encode timestamp as epoch --- Sources/XyoClient/Witness/Location/LocationPayload.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/XyoClient/Witness/Location/LocationPayload.swift b/Sources/XyoClient/Witness/Location/LocationPayload.swift index 030a4fa..2345d9b 100644 --- a/Sources/XyoClient/Witness/Location/LocationPayload.swift +++ b/Sources/XyoClient/Witness/Location/LocationPayload.swift @@ -46,7 +46,7 @@ open class LocationPayload: Payload { } try container.encode(self.location.speed, forKey: .speed) try container.encode(self.location.speedAccuracy, forKey: .speedAccuracy) - try container.encode(self.location.timestamp, forKey: .timestamp) + try container.encode(Int(self.location.timestamp.timeIntervalSince1970 * 1000), forKey: .timestamp) try container.encode(self.location.verticalAccuracy, forKey: .verticalAccuracy) } From 0951c9b1255070e3e34166d26f2b1e98d4129dab Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 14 Nov 2024 18:45:12 -0600 Subject: [PATCH 3/7] Sync Witness --- Sources/XyoClient/Witness/AbstractWitness.swift | 2 +- .../Witness/{Witness.swift => WitnessSync.swift} | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) rename Sources/XyoClient/Witness/{Witness.swift => WitnessSync.swift} (57%) diff --git a/Sources/XyoClient/Witness/AbstractWitness.swift b/Sources/XyoClient/Witness/AbstractWitness.swift index f00a659..bb63185 100644 --- a/Sources/XyoClient/Witness/AbstractWitness.swift +++ b/Sources/XyoClient/Witness/AbstractWitness.swift @@ -1,6 +1,6 @@ import Foundation -open class AbstractWitness: AbstractModule, Witness { +open class AbstractWitness: AbstractModule, WitnessSync { open func observe() -> [Payload] { preconditionFailure("This method must be overridden") } diff --git a/Sources/XyoClient/Witness/Witness.swift b/Sources/XyoClient/Witness/WitnessSync.swift similarity index 57% rename from Sources/XyoClient/Witness/Witness.swift rename to Sources/XyoClient/Witness/WitnessSync.swift index 36720a2..2ca7579 100644 --- a/Sources/XyoClient/Witness/Witness.swift +++ b/Sources/XyoClient/Witness/WitnessSync.swift @@ -1,10 +1,15 @@ -public protocol Witness { +public protocol WitnessProtocol {} + + +public protocol WitnessSync: WitnessProtocol { func observe() -> [Payload] } -public protocol WitnessAsync { +public protocol WitnessAsync: WitnessProtocol { func observe(completion: @escaping ([Payload]?, Error?) -> Void) @available(iOS 15, *) func observe() async throws -> [Payload] } + + From 462348e8559718c87272f4a2cfe64a000847c597 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 14 Nov 2024 18:54:15 -0600 Subject: [PATCH 4/7] Add callback insert method for Archivist --- .../Api/Archivist/ArchivistApiClient.swift | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/Sources/XyoClient/Api/Archivist/ArchivistApiClient.swift b/Sources/XyoClient/Api/Archivist/ArchivistApiClient.swift index 20d6bc7..3957457 100644 --- a/Sources/XyoClient/Api/Archivist/ArchivistApiClient.swift +++ b/Sources/XyoClient/Api/Archivist/ArchivistApiClient.swift @@ -30,6 +30,60 @@ public class XyoArchivistApiClient { self.queryAccount = account ?? Account() } + public func insert( + payloads: [Payload], + completion: @escaping ([Payload]?, Error?) -> Void + ) { + do { + // Build QueryBoundWitness + let (bw, signed) = try BoundWitnessBuilder() + .payloads(payloads) + .signer(self.queryAccount) + .query(XyoArchivistApiClient.ArchivistInsertQuery) + .build() + + // Perform the request + AF.request( + self.url, + method: .post, + parameters: ModuleQueryResult(bw: bw, payloads: signed), + encoder: JSONParameterEncoder.default + ) + .validate() + .responseData { response in + switch response.result { + case .success(let responseData): + do { + // Decode the response data + let decodedResponse = try JSONDecoder().decode( + ApiResponseEnvelope.self, from: responseData + ) + + // Check if the response data matches the expected result + if decodedResponse.data?.bw.payload_hashes.count == payloads.count { + // Return the payloads array in case of success + completion(payloads, nil) + } else { + // Return an empty array if the counts don't match + completion([], nil) + } + } catch { + // Pass any decoding errors to the completion handler + completion(nil, error) + } + + case .failure(let error): + // Pass any request errors to the completion handler + completion(nil, error) + } + } + + } catch { + // Handle synchronous errors (like errors from the BoundWitnessBuilder) + completion(nil, error) + } + } + @available(iOS 15, *) public func insert(payloads: [Payload]) async throws -> [Payload] { // Build QueryBoundWitness From dffcfd2d0a63f6489198196b257a5a3899eb9179 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 14 Nov 2024 18:58:35 -0600 Subject: [PATCH 5/7] Protocol conformance for witnesses --- Sources/XyoClient/Panel/Panel.swift | 10 +++++----- Sources/XyoClient/Witness/AbstractWitness.swift | 7 ++++++- Sources/XyoClient/Witness/Basic/BasicWitness.swift | 2 +- Sources/XyoClient/Witness/Event/EventWitness.swift | 2 +- .../Witness/SystemInfo/SystemInfoWitness.swift | 2 +- .../Witness/{WitnessSync.swift => Witness.swift} | 9 ++------- Tests/XyoClientTests/Panel.swift | 2 +- 7 files changed, 17 insertions(+), 17 deletions(-) rename Sources/XyoClient/Witness/{WitnessSync.swift => Witness.swift} (57%) diff --git a/Sources/XyoClient/Panel/Panel.swift b/Sources/XyoClient/Panel/Panel.swift index 7412ceb..ff8577c 100644 --- a/Sources/XyoClient/Panel/Panel.swift +++ b/Sources/XyoClient/Panel/Panel.swift @@ -6,13 +6,13 @@ public enum XyoPanelError: Error { public class XyoPanel { - public init(archivists: [XyoArchivistApiClient], witnesses: [AbstractWitness]) { + public init(archivists: [XyoArchivistApiClient], witnesses: [AbstractSyncWitness]) { self._archivists = archivists self._witnesses = witnesses } public convenience init( - archive: String? = nil, apiDomain: String? = nil, witnesses: [AbstractWitness]? = nil, + archive: String? = nil, apiDomain: String? = nil, witnesses: [AbstractSyncWitness]? = nil, token: String? = nil ) { let apiConfig = XyoArchivistApiConfig( @@ -23,7 +23,7 @@ public class XyoPanel { public convenience init(observe: (() -> XyoEventPayload?)?) { if observe != nil { - var witnesses = [AbstractWitness]() + var witnesses = [AbstractSyncWitness]() if let observe = observe { witnesses.append(XyoEventWitness(observe)) @@ -38,9 +38,9 @@ public class XyoPanel { public typealias XyoPanelReportCallback = (([String]) -> Void) private var _archivists: [XyoArchivistApiClient] - private var _witnesses: [AbstractWitness] + private var _witnesses: [AbstractSyncWitness] private var _previous_hash: String? - + @available(iOS 15, *) public func report() async throws -> [Payload] diff --git a/Sources/XyoClient/Witness/AbstractWitness.swift b/Sources/XyoClient/Witness/AbstractWitness.swift index bb63185..6712b1e 100644 --- a/Sources/XyoClient/Witness/AbstractWitness.swift +++ b/Sources/XyoClient/Witness/AbstractWitness.swift @@ -1,6 +1,6 @@ import Foundation -open class AbstractWitness: AbstractModule, WitnessSync { +open class AbstractSyncWitness: AbstractModule, WitnessSync { open func observe() -> [Payload] { preconditionFailure("This method must be overridden") } @@ -26,3 +26,8 @@ open class AbstractAsyncWitness: AbstractModule, WitnessAsync { } } } + +public protocol Witness {} + +extension AbstractSyncWitness: Witness {} +extension AbstractAsyncWitness: Witness {} diff --git a/Sources/XyoClient/Witness/Basic/BasicWitness.swift b/Sources/XyoClient/Witness/Basic/BasicWitness.swift index 2fd06c7..ec1acc9 100644 --- a/Sources/XyoClient/Witness/Basic/BasicWitness.swift +++ b/Sources/XyoClient/Witness/Basic/BasicWitness.swift @@ -1,6 +1,6 @@ import Foundation -open class BasicWitness: AbstractWitness { +open class BasicWitness: AbstractSyncWitness { public typealias TPayloadOut = Payload diff --git a/Sources/XyoClient/Witness/Event/EventWitness.swift b/Sources/XyoClient/Witness/Event/EventWitness.swift index dd59e42..5e5e12e 100644 --- a/Sources/XyoClient/Witness/Event/EventWitness.swift +++ b/Sources/XyoClient/Witness/Event/EventWitness.swift @@ -1,6 +1,6 @@ import Foundation -open class XyoEventWitness: AbstractWitness { +open class XyoEventWitness: AbstractSyncWitness { public init(_ observer: @escaping ObserverClosure) { _observer = observer diff --git a/Sources/XyoClient/Witness/SystemInfo/SystemInfoWitness.swift b/Sources/XyoClient/Witness/SystemInfo/SystemInfoWitness.swift index 5814dd0..9053cb7 100644 --- a/Sources/XyoClient/Witness/SystemInfo/SystemInfoWitness.swift +++ b/Sources/XyoClient/Witness/SystemInfo/SystemInfoWitness.swift @@ -1,6 +1,6 @@ import Foundation -open class SystemInfoWitness: AbstractWitness { +open class SystemInfoWitness: AbstractSyncWitness { var allowPathMonitor: Bool diff --git a/Sources/XyoClient/Witness/WitnessSync.swift b/Sources/XyoClient/Witness/Witness.swift similarity index 57% rename from Sources/XyoClient/Witness/WitnessSync.swift rename to Sources/XyoClient/Witness/Witness.swift index 2ca7579..abc0600 100644 --- a/Sources/XyoClient/Witness/WitnessSync.swift +++ b/Sources/XyoClient/Witness/Witness.swift @@ -1,15 +1,10 @@ -public protocol WitnessProtocol {} - - -public protocol WitnessSync: WitnessProtocol { +public protocol WitnessSync { func observe() -> [Payload] } -public protocol WitnessAsync: WitnessProtocol { +public protocol WitnessAsync { func observe(completion: @escaping ([Payload]?, Error?) -> Void) @available(iOS 15, *) func observe() async throws -> [Payload] } - - diff --git a/Tests/XyoClientTests/Panel.swift b/Tests/XyoClientTests/Panel.swift index fd2b018..b3bb9de 100644 --- a/Tests/XyoClientTests/Panel.swift +++ b/Tests/XyoClientTests/Panel.swift @@ -17,7 +17,7 @@ final class PanelTests: XCTestCase { let apiDomain = XyoPanel.Defaults.apiDomain let archive = XyoPanel.Defaults.apiModule let account = Account() - let witness = AbstractWitness(account: account) + let witness = AbstractSyncWitness(account: account) let panel = XyoPanel(archive: archive, apiDomain: apiDomain, witnesses: [witness]) XCTAssertNotNil(account) XCTAssertNotNil(panel) From 6935764741a002f1359a9f262f52c2d808c97d82 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 14 Nov 2024 19:04:14 -0600 Subject: [PATCH 6/7] WitnessModule --- Sources/XyoClient/Panel/Panel.swift | 8 ++++---- Sources/XyoClient/Witness/Basic/BasicWitness.swift | 2 +- Sources/XyoClient/Witness/Event/EventWitness.swift | 2 +- .../XyoClient/Witness/Location/LocationWitness.swift | 2 +- .../Witness/SystemInfo/SystemInfoWitness.swift | 2 +- .../{AbstractWitness.swift => WitnessModule.swift} | 11 ++++------- Tests/XyoClientTests/Panel.swift | 2 +- 7 files changed, 13 insertions(+), 16 deletions(-) rename Sources/XyoClient/Witness/{AbstractWitness.swift => WitnessModule.swift} (77%) diff --git a/Sources/XyoClient/Panel/Panel.swift b/Sources/XyoClient/Panel/Panel.swift index ff8577c..319fecb 100644 --- a/Sources/XyoClient/Panel/Panel.swift +++ b/Sources/XyoClient/Panel/Panel.swift @@ -6,13 +6,13 @@ public enum XyoPanelError: Error { public class XyoPanel { - public init(archivists: [XyoArchivistApiClient], witnesses: [AbstractSyncWitness]) { + public init(archivists: [XyoArchivistApiClient], witnesses: [WitnessModuleSync]) { self._archivists = archivists self._witnesses = witnesses } public convenience init( - archive: String? = nil, apiDomain: String? = nil, witnesses: [AbstractSyncWitness]? = nil, + archive: String? = nil, apiDomain: String? = nil, witnesses: [WitnessModuleSync]? = nil, token: String? = nil ) { let apiConfig = XyoArchivistApiConfig( @@ -23,7 +23,7 @@ public class XyoPanel { public convenience init(observe: (() -> XyoEventPayload?)?) { if observe != nil { - var witnesses = [AbstractSyncWitness]() + var witnesses = [WitnessModuleSync]() if let observe = observe { witnesses.append(XyoEventWitness(observe)) @@ -38,7 +38,7 @@ public class XyoPanel { public typealias XyoPanelReportCallback = (([String]) -> Void) private var _archivists: [XyoArchivistApiClient] - private var _witnesses: [AbstractSyncWitness] + private var _witnesses: [WitnessModuleSync] private var _previous_hash: String? @available(iOS 15, *) diff --git a/Sources/XyoClient/Witness/Basic/BasicWitness.swift b/Sources/XyoClient/Witness/Basic/BasicWitness.swift index ec1acc9..1d733da 100644 --- a/Sources/XyoClient/Witness/Basic/BasicWitness.swift +++ b/Sources/XyoClient/Witness/Basic/BasicWitness.swift @@ -1,6 +1,6 @@ import Foundation -open class BasicWitness: AbstractSyncWitness { +open class BasicWitness: WitnessModuleSync { public typealias TPayloadOut = Payload diff --git a/Sources/XyoClient/Witness/Event/EventWitness.swift b/Sources/XyoClient/Witness/Event/EventWitness.swift index 5e5e12e..6cb8c54 100644 --- a/Sources/XyoClient/Witness/Event/EventWitness.swift +++ b/Sources/XyoClient/Witness/Event/EventWitness.swift @@ -1,6 +1,6 @@ import Foundation -open class XyoEventWitness: AbstractSyncWitness { +open class XyoEventWitness: WitnessModuleSync { public init(_ observer: @escaping ObserverClosure) { _observer = observer diff --git a/Sources/XyoClient/Witness/Location/LocationWitness.swift b/Sources/XyoClient/Witness/Location/LocationWitness.swift index 43c895e..ee32847 100644 --- a/Sources/XyoClient/Witness/Location/LocationWitness.swift +++ b/Sources/XyoClient/Witness/Location/LocationWitness.swift @@ -1,7 +1,7 @@ import CoreLocation import Foundation -open class LocationWitness: AbstractAsyncWitness { +open class LocationWitness: WitnessModuleAsync { private let locationService = LocationService() override open func observe(completion: @escaping ([Payload]?, Error?) -> Void) { diff --git a/Sources/XyoClient/Witness/SystemInfo/SystemInfoWitness.swift b/Sources/XyoClient/Witness/SystemInfo/SystemInfoWitness.swift index 9053cb7..381715e 100644 --- a/Sources/XyoClient/Witness/SystemInfo/SystemInfoWitness.swift +++ b/Sources/XyoClient/Witness/SystemInfo/SystemInfoWitness.swift @@ -1,6 +1,6 @@ import Foundation -open class SystemInfoWitness: AbstractSyncWitness { +open class SystemInfoWitness: WitnessModuleSync { var allowPathMonitor: Bool diff --git a/Sources/XyoClient/Witness/AbstractWitness.swift b/Sources/XyoClient/Witness/WitnessModule.swift similarity index 77% rename from Sources/XyoClient/Witness/AbstractWitness.swift rename to Sources/XyoClient/Witness/WitnessModule.swift index 6712b1e..81cca61 100644 --- a/Sources/XyoClient/Witness/AbstractWitness.swift +++ b/Sources/XyoClient/Witness/WitnessModule.swift @@ -1,12 +1,14 @@ import Foundation -open class AbstractSyncWitness: AbstractModule, WitnessSync { +public protocol WitnessModule: Module {} + +open class WitnessModuleSync: AbstractModule, WitnessSync, WitnessModule { open func observe() -> [Payload] { preconditionFailure("This method must be overridden") } } -open class AbstractAsyncWitness: AbstractModule, WitnessAsync { +open class WitnessModuleAsync: AbstractModule, WitnessAsync, WitnessModule { open func observe(completion: @escaping ([Payload]?, Error?) -> Void) { preconditionFailure("This method must be overridden") } @@ -26,8 +28,3 @@ open class AbstractAsyncWitness: AbstractModule, WitnessAsync { } } } - -public protocol Witness {} - -extension AbstractSyncWitness: Witness {} -extension AbstractAsyncWitness: Witness {} diff --git a/Tests/XyoClientTests/Panel.swift b/Tests/XyoClientTests/Panel.swift index b3bb9de..a1530f7 100644 --- a/Tests/XyoClientTests/Panel.swift +++ b/Tests/XyoClientTests/Panel.swift @@ -17,7 +17,7 @@ final class PanelTests: XCTestCase { let apiDomain = XyoPanel.Defaults.apiDomain let archive = XyoPanel.Defaults.apiModule let account = Account() - let witness = AbstractSyncWitness(account: account) + let witness = WitnessModuleSync(account: account) let panel = XyoPanel(archive: archive, apiDomain: apiDomain, witnesses: [witness]) XCTAssertNotNil(account) XCTAssertNotNil(panel) From 3a6b401f81a1a2515d025354d606cb735c73f027 Mon Sep 17 00:00:00 2001 From: Joel Carter Date: Thu, 14 Nov 2024 19:10:30 -0600 Subject: [PATCH 7/7] handle sync/async witnessing --- Sources/XyoClient/Panel/Panel.swift | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/Sources/XyoClient/Panel/Panel.swift b/Sources/XyoClient/Panel/Panel.swift index 319fecb..f0341cd 100644 --- a/Sources/XyoClient/Panel/Panel.swift +++ b/Sources/XyoClient/Panel/Panel.swift @@ -38,21 +38,40 @@ public class XyoPanel { public typealias XyoPanelReportCallback = (([String]) -> Void) private var _archivists: [XyoArchivistApiClient] - private var _witnesses: [WitnessModuleSync] + private var _witnesses: [WitnessModule] private var _previous_hash: String? @available(iOS 15, *) public func report() async throws -> [Payload] { - let payloads = self._witnesses.map { witness in - witness.observe() - }.flatMap({ $0 }) + var payloads: [Payload] = [] + + // Collect payloads from both synchronous and asynchronous witnesses + for witness in _witnesses { + if let syncWitness = witness as? WitnessSync { + // For synchronous witnesses, call the sync `observe` method directly + payloads.append(contentsOf: syncWitness.observe()) + } else if let asyncWitness = witness as? WitnessAsync { + // For asynchronous witnesses, call the async `observe` method using `await` + do { + let asyncPayloads = try await asyncWitness.observe() + payloads.append(contentsOf: asyncPayloads) + } catch { + print("Error observing async witness: \(error)") + // Handle error as needed, possibly continue or throw + } + } + } + + // Build the BoundWitness let (bw, _) = try BoundWitnessBuilder() .payloads(payloads) - .signers(self._witnesses.map({ $0.account })) + .signers(self._witnesses.map { $0.account }) .build(_previous_hash) self._previous_hash = bw._hash + + // Collect results from archivists using async tasks var allResults: [[Payload]] = [] await withTaskGroup(of: [Payload]?.self) { group in for instance in _archivists {