diff --git a/Example/example-app/Package.swift b/Example/example-app/Package.swift index 3b02bf979acbbd2d44d20cc04eca3ad9dc4a8ce9..55d546265e9cafd943d11e006d1d3ddcb298cad3 100644 --- a/Example/example-app/Package.swift +++ b/Example/example-app/Package.swift @@ -135,10 +135,19 @@ let package = Package( .target( name: "SessionFeature", dependencies: [ + .target(name: "ErrorFeature"), .product( name: "ComposableArchitecture", package: "swift-composable-architecture" ), + .product( + name: "ComposablePresentation", + package: "swift-composable-presentation" + ), + .product( + name: "ElixxirDAppsSDK", + package: "elixxir-dapps-sdk-swift" + ), ], swiftSettings: swiftSettings ), diff --git a/Example/example-app/Sources/AppFeature/App.swift b/Example/example-app/Sources/AppFeature/App.swift index 1353882a744740f111d93562021f892f3108d5a9..341cb46c6bbc7515e222c9e4157fab6f42c82160 100644 --- a/Example/example-app/Sources/AppFeature/App.swift +++ b/Example/example-app/Sources/AppFeature/App.swift @@ -29,6 +29,7 @@ extension AppEnvironment { ).eraseToAnyScheduler() return AppEnvironment( + makeId: UUID.init, hasClient: clientSubject.map { $0 != nil }.eraseToAnyPublisher(), mainScheduler: mainScheduler, landing: LandingEnvironment( @@ -40,7 +41,11 @@ extension AppEnvironment { mainScheduler: mainScheduler, error: ErrorEnvironment() ), - session: SessionEnvironment() + session: SessionEnvironment( + getClient: { clientSubject.value }, + bgScheduler: bgScheduler, + mainScheduler: mainScheduler + ) ) } } diff --git a/Example/example-app/Sources/AppFeature/AppFeature.swift b/Example/example-app/Sources/AppFeature/AppFeature.swift index b99904b08b9a7f800fb7d244e5e274f3f3715c57..347c13b151a70c09e0188a4e3cd5f4ac690d4c08 100644 --- a/Example/example-app/Sources/AppFeature/AppFeature.swift +++ b/Example/example-app/Sources/AppFeature/AppFeature.swift @@ -10,7 +10,8 @@ struct AppState: Equatable { case session(SessionState) } - var scene: Scene = .landing(LandingState()) + var id: UUID = UUID() + var scene: Scene = .landing(LandingState(id: UUID())) } extension AppState.Scene { @@ -45,6 +46,7 @@ enum AppAction: Equatable { } struct AppEnvironment { + var makeId: () -> UUID var hasClient: AnyPublisher<Bool, Never> var mainScheduler: AnySchedulerOf<DispatchQueue> var landing: LandingEnvironment @@ -55,20 +57,22 @@ let appReducer = Reducer<AppState, AppAction, AppEnvironment> { state, action, env in switch action { case .viewDidLoad: - struct HasClientEffectId: Hashable {} + struct HasClientEffectId: Hashable { + var id: UUID + } return env.hasClient .removeDuplicates() .map(AppAction.clientDidChange(hasClient:)) .receive(on: env.mainScheduler) .eraseToEffect() - .cancellable(id: HasClientEffectId(), cancelInFlight: true) + .cancellable(id: HasClientEffectId(id: state.id), cancelInFlight: true) case .clientDidChange(let hasClient): if hasClient { - let sessionState = state.scene.asSession ?? SessionState() + let sessionState = state.scene.asSession ?? SessionState(id: env.makeId()) state.scene = .session(sessionState) } else { - let landingState = state.scene.asLanding ?? LandingState() + let landingState = state.scene.asLanding ?? LandingState(id: env.makeId()) state.scene = .landing(landingState) } return .none @@ -95,6 +99,7 @@ let appReducer = Reducer<AppState, AppAction, AppEnvironment> #if DEBUG extension AppEnvironment { static let failing = AppEnvironment( + makeId: { fatalError() }, hasClient: Empty().eraseToAnyPublisher(), mainScheduler: .failing, landing: .failing, diff --git a/Example/example-app/Sources/LandingFeature/LandingFeature.swift b/Example/example-app/Sources/LandingFeature/LandingFeature.swift index dfca1d37c532b6cdbf682c3ea5465f4829e513e0..511b1107b0afed85ddcbf2a9a03c3b99841a055b 100644 --- a/Example/example-app/Sources/LandingFeature/LandingFeature.swift +++ b/Example/example-app/Sources/LandingFeature/LandingFeature.swift @@ -5,17 +5,20 @@ import ErrorFeature public struct LandingState: Equatable { public init( + id: UUID, hasStoredClient: Bool = false, isMakingClient: Bool = false, isRemovingClient: Bool = false, error: ErrorState? = nil ) { + self.id = id self.hasStoredClient = hasStoredClient self.isMakingClient = isMakingClient self.isRemovingClient = isRemovingClient self.error = error } + var id: UUID var hasStoredClient: Bool var isMakingClient: Bool var isRemovingClient: Bool diff --git a/Example/example-app/Sources/LandingFeature/LandingView.swift b/Example/example-app/Sources/LandingFeature/LandingView.swift index a4dd2b73face637d99e3615922a512d84d9994e5..4a45669a3538af966d54876187964d4697a59ee5 100644 --- a/Example/example-app/Sources/LandingFeature/LandingView.swift +++ b/Example/example-app/Sources/LandingFeature/LandingView.swift @@ -80,7 +80,7 @@ public struct LandingView_Previews: PreviewProvider { public static var previews: some View { NavigationView { LandingView(store: .init( - initialState: .init(), + initialState: .init(id: UUID()), reducer: .empty, environment: () )) diff --git a/Example/example-app/Sources/SessionFeature/NetworkFollowerStatusView.swift b/Example/example-app/Sources/SessionFeature/NetworkFollowerStatusView.swift new file mode 100644 index 0000000000000000000000000000000000000000..5d1ba1dee39fba281819f0865cfaecd73bae6038 --- /dev/null +++ b/Example/example-app/Sources/SessionFeature/NetworkFollowerStatusView.swift @@ -0,0 +1,44 @@ +import ElixxirDAppsSDK +import SwiftUI + +struct NetworkFollowerStatusView: View { + var status: NetworkFollowerStatus? + + var body: some View { + switch status { + case .stopped: + Label("Stopped", systemImage: "stop.fill") + + case .starting: + Label("Starting...", systemImage: "play") + + case .running: + Label("Running", systemImage: "play.fill") + + case .stopping: + Label("Stopping...", systemImage: "stop") + + case .unknown(let code): + Label("Status \(code)", systemImage: "questionmark") + + case .none: + Label("Unknown", systemImage: "questionmark") + } + } +} + +#if DEBUG +struct NetworkFollowerStatusView_Previews: PreviewProvider { + static var previews: some View { + Group { + NetworkFollowerStatusView(status: .stopped) + NetworkFollowerStatusView(status: .starting) + NetworkFollowerStatusView(status: .running) + NetworkFollowerStatusView(status: .stopping) + NetworkFollowerStatusView(status: .unknown(code: -1)) + NetworkFollowerStatusView(status: nil) + } + .previewLayout(.sizeThatFits) + } +} +#endif diff --git a/Example/example-app/Sources/SessionFeature/NetworkHealthStatusView.swift b/Example/example-app/Sources/SessionFeature/NetworkHealthStatusView.swift new file mode 100644 index 0000000000000000000000000000000000000000..e13cb6fc56378fc9717b4b7e5fe3469c0b7f3162 --- /dev/null +++ b/Example/example-app/Sources/SessionFeature/NetworkHealthStatusView.swift @@ -0,0 +1,33 @@ +import SwiftUI + +struct NetworkHealthStatusView: View { + var status: Bool? + + var body: some View { + switch status { + case .some(true): + Label("Healthy", systemImage: "wifi") + .foregroundColor(.green) + + case .some(false): + Label("Unhealthy", systemImage: "bolt.horizontal.fill") + .foregroundColor(.red) + + case .none: + Label("Unknown", systemImage: "questionmark") + } + } +} + +#if DEBUG +struct NetworkHealthStatusView_Previews: PreviewProvider { + static var previews: some View { + Group { + NetworkHealthStatusView(status: true) + NetworkHealthStatusView(status: false) + NetworkHealthStatusView(status: nil) + } + .previewLayout(.sizeThatFits) + } +} +#endif diff --git a/Example/example-app/Sources/SessionFeature/SessionFeature.swift b/Example/example-app/Sources/SessionFeature/SessionFeature.swift index 3b7dd16fe1aecf92658f3d9e2508b2fefc20927a..0756f738008e1ea1870ffd1a11a600fccab5e0ca 100644 --- a/Example/example-app/Sources/SessionFeature/SessionFeature.swift +++ b/Example/example-app/Sources/SessionFeature/SessionFeature.swift @@ -1,27 +1,145 @@ +import Combine import ComposableArchitecture +import ElixxirDAppsSDK +import ErrorFeature public struct SessionState: Equatable { - public init() {} + public init( + id: UUID, + networkFollowerStatus: NetworkFollowerStatus? = nil, + isNetworkHealthy: Bool? = nil, + error: ErrorState? = nil + ) { + self.id = id + self.networkFollowerStatus = networkFollowerStatus + self.isNetworkHealthy = isNetworkHealthy + self.error = error + } + + public var id: UUID + public var networkFollowerStatus: NetworkFollowerStatus? + public var isNetworkHealthy: Bool? + public var error: ErrorState? } public enum SessionAction: Equatable { case viewDidLoad + case updateNetworkFollowerStatus + case didUpdateNetworkFollowerStatus(NetworkFollowerStatus?) + case runNetworkFollower(Bool) + case networkFollowerDidFail(NSError) + case monitorNetworkHealth(Bool) + case didUpdateNetworkHealth(Bool?) + case didDismissError + case error(ErrorAction) } public struct SessionEnvironment { - public init() {} + public init( + getClient: @escaping () -> Client?, + bgScheduler: AnySchedulerOf<DispatchQueue>, + mainScheduler: AnySchedulerOf<DispatchQueue> + ) { + self.getClient = getClient + self.bgScheduler = bgScheduler + self.mainScheduler = mainScheduler + } + + public var getClient: () -> Client? + public var bgScheduler: AnySchedulerOf<DispatchQueue> + public var mainScheduler: AnySchedulerOf<DispatchQueue> } public let sessionReducer = Reducer<SessionState, SessionAction, SessionEnvironment> { state, action, env in switch action { case .viewDidLoad: + return .merge([ + .init(value: .updateNetworkFollowerStatus), + .init(value: .monitorNetworkHealth(true)), + ]) + + case .updateNetworkFollowerStatus: + return Effect.future { fulfill in + let status = env.getClient()?.networkFollower.status() + fulfill(.success(.didUpdateNetworkFollowerStatus(status))) + } + .subscribe(on: env.bgScheduler) + .receive(on: env.mainScheduler) + .eraseToEffect() + + case .didUpdateNetworkFollowerStatus(let status): + state.networkFollowerStatus = status + return .none + + case .runNetworkFollower(let start): + state.networkFollowerStatus = start ? .starting : .stopping + return Effect.run { subscriber in + do { + if start { + try env.getClient()?.networkFollower.start(timeoutMS: 30_000) + } else { + try env.getClient()?.networkFollower.stop() + } + } catch { + subscriber.send(.networkFollowerDidFail(error as NSError)) + } + let status = env.getClient()?.networkFollower.status() + subscriber.send(.didUpdateNetworkFollowerStatus(status)) + subscriber.send(completion: .finished) + return AnyCancellable {} + } + .subscribe(on: env.bgScheduler) + .receive(on: env.mainScheduler) + .eraseToEffect() + + case .networkFollowerDidFail(let error): + state.error = ErrorState(error: error) + return .none + + case .monitorNetworkHealth(let start): + struct MonitorEffectId: Hashable { + var id: UUID + } + let effectId = MonitorEffectId(id: state.id) + if start { + return Effect.run { subscriber in + var cancellable = env.getClient()?.monitorNetworkHealth { isHealthy in + subscriber.send(.didUpdateNetworkHealth(isHealthy)) + } + return AnyCancellable { + cancellable?.cancel() + } + } + .subscribe(on: env.bgScheduler) + .receive(on: env.mainScheduler) + .eraseToEffect() + .cancellable(id: effectId, cancelInFlight: true) + } else { + return Effect.cancel(id: effectId) + .subscribe(on: env.bgScheduler) + .eraseToEffect() + } + + case .didUpdateNetworkHealth(let isHealthy): + state.isNetworkHealthy = isHealthy + return .none + + case .didDismissError: + state.error = nil + return .none + + case .error(_): return .none } } #if DEBUG extension SessionEnvironment { - public static let failing = SessionEnvironment() + public static let failing = SessionEnvironment( + getClient: { .failing }, + bgScheduler: .failing, + mainScheduler: .failing + ) } #endif diff --git a/Example/example-app/Sources/SessionFeature/SessionView.swift b/Example/example-app/Sources/SessionFeature/SessionView.swift index eb1fcf0071a4e534f867c443b2913cbab1ec0779..395cfcb160067fedc31ec3ddd5200aa6960864e2 100644 --- a/Example/example-app/Sources/SessionFeature/SessionView.swift +++ b/Example/example-app/Sources/SessionFeature/SessionView.swift @@ -1,4 +1,7 @@ import ComposableArchitecture +import ComposablePresentation +import ElixxirDAppsSDK +import ErrorFeature import SwiftUI public struct SessionView: View { @@ -9,16 +12,58 @@ public struct SessionView: View { let store: Store<SessionState, SessionAction> struct ViewState: Equatable { - init(state: SessionState) {} + let networkFollowerStatus: NetworkFollowerStatus? + let isNetworkHealthy: Bool? + + init(state: SessionState) { + networkFollowerStatus = state.networkFollowerStatus + isNetworkHealthy = state.isNetworkHealthy + } } public var body: some View { WithViewStore(store.scope(state: ViewState.init)) { viewStore in - Text("SessionView") - .navigationTitle("Session") - .task { - viewStore.send(.viewDidLoad) + Form { + Section { + NetworkFollowerStatusView(status: viewStore.networkFollowerStatus) + + Button { + viewStore.send(.runNetworkFollower(true)) + } label: { + Text("Start") + } + .disabled(viewStore.networkFollowerStatus != .stopped) + + Button { + viewStore.send(.runNetworkFollower(false)) + } label: { + Text("Stop") + } + .disabled(viewStore.networkFollowerStatus != .running) + } header: { + Text("Network follower") + } + + Section { + NetworkHealthStatusView(status: viewStore.isNetworkHealthy) + } header: { + Text("Network health") } + } + .navigationTitle("Session") + .task { + viewStore.send(.viewDidLoad) + } + .sheet( + store.scope( + state: \.error, + action: SessionAction.error + ), + onDismiss: { + viewStore.send(.didDismissError) + }, + content: ErrorView.init(store:) + ) } } } @@ -27,7 +72,7 @@ public struct SessionView: View { public struct SessionView_Previews: PreviewProvider { public static var previews: some View { SessionView(store: .init( - initialState: .init(), + initialState: .init(id: UUID()), reducer: .empty, environment: () )) diff --git a/Example/example-app/Tests/AppFeatureTests/AppFeatureTests.swift b/Example/example-app/Tests/AppFeatureTests/AppFeatureTests.swift index 96a0b07759825f391e32584051116f09acf55934..ce5891ed1d1e147abdca7390420e2888e82321a5 100644 --- a/Example/example-app/Tests/AppFeatureTests/AppFeatureTests.swift +++ b/Example/example-app/Tests/AppFeatureTests/AppFeatureTests.swift @@ -7,10 +7,12 @@ import XCTest final class AppFeatureTests: XCTestCase { func testViewDidLoad() throws { + let newId = UUID() let hasClient = PassthroughSubject<Bool, Never>() let mainScheduler = DispatchQueue.test var env = AppEnvironment.failing + env.makeId = { newId } env.hasClient = hasClient.eraseToAnyPublisher() env.mainScheduler = mainScheduler.eraseToAnyScheduler() @@ -31,7 +33,7 @@ final class AppFeatureTests: XCTestCase { mainScheduler.advance() store.receive(.clientDidChange(hasClient: true)) { - $0.scene = .session(SessionState()) + $0.scene = .session(SessionState(id: newId)) } hasClient.send(true) @@ -41,7 +43,7 @@ final class AppFeatureTests: XCTestCase { mainScheduler.advance() store.receive(.clientDidChange(hasClient: false)) { - $0.scene = .landing(LandingState()) + $0.scene = .landing(LandingState(id: newId)) } hasClient.send(completion: .finished) diff --git a/Example/example-app/Tests/LandingFeatureTests/LandingFeatureTests.swift b/Example/example-app/Tests/LandingFeatureTests/LandingFeatureTests.swift index df9791e7de8c81a4a43f21a11e16f188afa044f7..c5b56f988cd96cf0f8a8c9e03ab2afad9ef885cc 100644 --- a/Example/example-app/Tests/LandingFeatureTests/LandingFeatureTests.swift +++ b/Example/example-app/Tests/LandingFeatureTests/LandingFeatureTests.swift @@ -9,7 +9,7 @@ final class LandingFeatureTests: XCTestCase { env.clientStorage.hasStoredClient = { true } let store = TestStore( - initialState: LandingState(), + initialState: LandingState(id: UUID()), reducer: landingReducer, environment: env ) @@ -33,7 +33,7 @@ final class LandingFeatureTests: XCTestCase { env.mainScheduler = mainScheduler.eraseToAnyScheduler() let store = TestStore( - initialState: LandingState(), + initialState: LandingState(id: UUID()), reducer: landingReducer, environment: env ) @@ -68,7 +68,7 @@ final class LandingFeatureTests: XCTestCase { env.mainScheduler = mainScheduler.eraseToAnyScheduler() let store = TestStore( - initialState: LandingState(), + initialState: LandingState(id: UUID()), reducer: landingReducer, environment: env ) @@ -101,7 +101,7 @@ final class LandingFeatureTests: XCTestCase { env.mainScheduler = mainScheduler.eraseToAnyScheduler() let store = TestStore( - initialState: LandingState(), + initialState: LandingState(id: UUID()), reducer: landingReducer, environment: env ) @@ -133,7 +133,7 @@ final class LandingFeatureTests: XCTestCase { env.mainScheduler = mainScheduler.eraseToAnyScheduler() let store = TestStore( - initialState: LandingState(), + initialState: LandingState(id: UUID()), reducer: landingReducer, environment: env ) @@ -167,7 +167,7 @@ final class LandingFeatureTests: XCTestCase { env.mainScheduler = mainScheduler.eraseToAnyScheduler() let store = TestStore( - initialState: LandingState(), + initialState: LandingState(id: UUID()), reducer: landingReducer, environment: env ) diff --git a/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift b/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift index 5ed11e32b14f6ee73da18a53cee600418122f87c..2ada840d7d9d02bdb1c8732836502ad15d15be33 100644 --- a/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift +++ b/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift @@ -1,15 +1,158 @@ import ComposableArchitecture +import ElixxirDAppsSDK +import ErrorFeature import XCTest @testable import SessionFeature final class SessionFeatureTests: XCTestCase { - func testViewDidLoad() throws { + func testViewDidLoad() { + var networkFollowerStatus: NetworkFollowerStatus! + var didStartMonitoringNetworkHealth = 0 + var didStopMonitoringNetworkHealth = 0 + var networkHealthCallback: ((Bool) -> Void)! + let bgScheduler = DispatchQueue.test + let mainScheduler = DispatchQueue.test + + var env = SessionEnvironment.failing + env.getClient = { + var client = Client.failing + client.networkFollower.status.status = { networkFollowerStatus } + client.monitorNetworkHealth.listen = { callback in + networkHealthCallback = callback + didStartMonitoringNetworkHealth += 1 + return Cancellable { + didStopMonitoringNetworkHealth += 1 + } + } + return client + } + env.bgScheduler = bgScheduler.eraseToAnyScheduler() + env.mainScheduler = mainScheduler.eraseToAnyScheduler() + let store = TestStore( - initialState: SessionState(), + initialState: SessionState(id: UUID()), reducer: sessionReducer, - environment: .failing + environment: env ) store.send(.viewDidLoad) + + store.receive(.updateNetworkFollowerStatus) + store.receive(.monitorNetworkHealth(true)) + + networkFollowerStatus = .stopped + bgScheduler.advance() + mainScheduler.advance() + + store.receive(.didUpdateNetworkFollowerStatus(.stopped)) { + $0.networkFollowerStatus = .stopped + } + + XCTAssertEqual(didStartMonitoringNetworkHealth, 1) + XCTAssertEqual(didStopMonitoringNetworkHealth, 0) + + networkHealthCallback(true) + bgScheduler.advance() + mainScheduler.advance() + + store.receive(.didUpdateNetworkHealth(true)) { + $0.isNetworkHealthy = true + } + + store.send(.monitorNetworkHealth(false)) + + bgScheduler.advance() + + XCTAssertEqual(didStartMonitoringNetworkHealth, 1) + XCTAssertEqual(didStopMonitoringNetworkHealth, 1) + } + + func testStartStopNetworkFollower() { + var networkFollowerStatus: NetworkFollowerStatus! + var didStartNetworkFollowerWithTimeout = [Int]() + var didStopNetworkFollower = 0 + var networkFollowerStartError: NSError? + let bgScheduler = DispatchQueue.test + let mainScheduler = DispatchQueue.test + + var env = SessionEnvironment.failing + env.getClient = { + var client = Client.failing + client.networkFollower.status.status = { + networkFollowerStatus + } + client.networkFollower.start.start = { + didStartNetworkFollowerWithTimeout.append($0) + if let error = networkFollowerStartError { + throw error + } + } + client.networkFollower.stop.stop = { + didStopNetworkFollower += 1 + } + return client + } + env.bgScheduler = bgScheduler.eraseToAnyScheduler() + env.mainScheduler = mainScheduler.eraseToAnyScheduler() + + let store = TestStore( + initialState: SessionState(id: UUID()), + reducer: sessionReducer, + environment: env + ) + + store.send(.runNetworkFollower(true)) { + $0.networkFollowerStatus = .starting + } + + networkFollowerStatus = .running + bgScheduler.advance() + mainScheduler.advance() + + XCTAssertEqual(didStartNetworkFollowerWithTimeout, [30_000]) + XCTAssertEqual(didStopNetworkFollower, 0) + + store.receive(.didUpdateNetworkFollowerStatus(.running)) { + $0.networkFollowerStatus = .running + } + + store.send(.runNetworkFollower(false)) { + $0.networkFollowerStatus = .stopping + } + + networkFollowerStatus = .stopped + bgScheduler.advance() + mainScheduler.advance() + + XCTAssertEqual(didStartNetworkFollowerWithTimeout, [30_000]) + XCTAssertEqual(didStopNetworkFollower, 1) + + store.receive(.didUpdateNetworkFollowerStatus(.stopped)) { + $0.networkFollowerStatus = .stopped + } + + store.send(.runNetworkFollower(true)) { + $0.networkFollowerStatus = .starting + } + + networkFollowerStartError = NSError(domain: "test", code: 1234) + networkFollowerStatus = .stopped + bgScheduler.advance() + mainScheduler.advance() + + XCTAssertEqual(didStartNetworkFollowerWithTimeout, [30_000, 30_000]) + XCTAssertEqual(didStopNetworkFollower, 1) + + store.receive(.networkFollowerDidFail(networkFollowerStartError!)) { + $0.error = ErrorState(error: networkFollowerStartError!) + } + + store.receive(.didUpdateNetworkFollowerStatus(.stopped)) { + $0.networkFollowerStatus = .stopped + } + + store.send(.didDismissError) { + $0.error = nil + } } } diff --git a/Sources/ElixxirDAppsSDK/ConnectionMaker.swift b/Sources/ElixxirDAppsSDK/ConnectionMaker.swift index 4baa7618e129896d093d22a0b40b0dfb7fbf620a..34d7c14b0eaa9f41b5bb959f2fa760e42b271b88 100644 --- a/Sources/ElixxirDAppsSDK/ConnectionMaker.swift +++ b/Sources/ElixxirDAppsSDK/ConnectionMaker.swift @@ -1,12 +1,12 @@ import Bindings public struct ConnectionMaker { - public var connect: (Bool, Data, Data) throws -> Connection + public var connect: (Bool, Data, Identity) throws -> Connection public func callAsFunction( withAuthentication: Bool, recipientContact: Data, - myIdentity: Data + myIdentity: Identity ) throws -> Connection { try connect(withAuthentication, recipientContact, myIdentity) } @@ -15,18 +15,20 @@ public struct ConnectionMaker { extension ConnectionMaker { public static func live(bindingsClient: BindingsClient) -> ConnectionMaker { ConnectionMaker { withAuthentication, recipientContact, myIdentity in + let encoder = JSONEncoder() + let myIdentityData = try encoder.encode(myIdentity) if withAuthentication { return Connection.live( bindingsAuthenticatedConnection: try bindingsClient.connect( withAuthentication: recipientContact, - myIdentity: myIdentity + myIdentity: myIdentityData ) ) } else { return Connection.live( bindingsConnection: try bindingsClient.connect( recipientContact, - myIdentity: myIdentity + myIdentity: myIdentityData ) ) } diff --git a/Sources/ElixxirDAppsSDK/ContactFactsProvider.swift b/Sources/ElixxirDAppsSDK/ContactFactsProvider.swift index e2adebfbbb6b2e7143cc4abe074ee352f8bc400b..2029dc6f306aa74ef1fd48da93ed5038723e5e7b 100644 --- a/Sources/ElixxirDAppsSDK/ContactFactsProvider.swift +++ b/Sources/ElixxirDAppsSDK/ContactFactsProvider.swift @@ -1,9 +1,9 @@ import Bindings public struct ContactFactsProvider { - public var get: (Data) throws -> Data + public var get: (Data) throws -> [Fact] - public func callAsFunction(contact: Data) throws -> Data { + public func callAsFunction(contact: Data) throws -> [Fact] { try get(contact) } } @@ -11,13 +11,15 @@ public struct ContactFactsProvider { extension ContactFactsProvider { public static let live = ContactFactsProvider { contact in var error: NSError? - let facts = BindingsGetFactsFromContact(contact, &error) + let factsData = BindingsGetFactsFromContact(contact, &error) if let error = error { throw error } - guard let facts = facts else { + guard let factsData = factsData else { fatalError("BindingsGetFactsFromContact returned `nil` without providing error") } + let decoder = JSONDecoder() + let facts = try decoder.decode([Fact].self, from: factsData) return facts } } diff --git a/Sources/ElixxirDAppsSDK/ContactFactsSetter.swift b/Sources/ElixxirDAppsSDK/ContactFactsSetter.swift index 0fca1df3f70537edc4e8fe62800d6ae093942f88..8dd0ed98e4ed1e8449b1df3c628c71798784de23 100644 --- a/Sources/ElixxirDAppsSDK/ContactFactsSetter.swift +++ b/Sources/ElixxirDAppsSDK/ContactFactsSetter.swift @@ -1,17 +1,22 @@ import Bindings public struct ContactFactsSetter { - public var set: (Data, Data) throws -> Data + public var set: (Data, [Fact]) throws -> Data - public func callAsFunction(contact: Data, facts: Data) throws -> Data { + public func callAsFunction( + contact: Data, + facts: [Fact] + ) throws -> Data { try set(contact, facts) } } extension ContactFactsSetter { public static let live = ContactFactsSetter { contact, facts in + let encoder = JSONEncoder() + let factsData = try encoder.encode(facts) var error: NSError? - let updatedContact = BindingsSetFactsOnContact(contact, facts, &error) + let updatedContact = BindingsSetFactsOnContact(contact, factsData, &error) if let error = error { throw error } diff --git a/Sources/ElixxirDAppsSDK/ContactFromIdentityProvider.swift b/Sources/ElixxirDAppsSDK/ContactFromIdentityProvider.swift index 1643a0cdc4a6b2979d67f0112e4f5cc0398859da..292f217d8c28f556f6599e4a4bf4fbde6248081c 100644 --- a/Sources/ElixxirDAppsSDK/ContactFromIdentityProvider.swift +++ b/Sources/ElixxirDAppsSDK/ContactFromIdentityProvider.swift @@ -1,16 +1,21 @@ import Bindings public struct ContactFromIdentityProvider { - public var get: (Data) throws -> Data + public var get: (Identity) throws -> Data - public func callAsFunction(identity: Data) throws -> Data { + public func callAsFunction(identity: Identity) throws -> Data { try get(identity) } } extension ContactFromIdentityProvider { public static func live(bindingsClient: BindingsClient) -> ContactFromIdentityProvider { - ContactFromIdentityProvider(get: bindingsClient.getContactFromIdentity(_:)) + ContactFromIdentityProvider { identity in + let encoder = JSONEncoder() + let identityData = try encoder.encode(identity) + let contactData = try bindingsClient.getContactFromIdentity(identityData) + return contactData + } } } diff --git a/Sources/ElixxirDAppsSDK/IdentityMaker.swift b/Sources/ElixxirDAppsSDK/IdentityMaker.swift index 91e7eeed7c4d90d73896c100a8416471581d7564..863e180e8343e87808984d4dee709e9c92fea766 100644 --- a/Sources/ElixxirDAppsSDK/IdentityMaker.swift +++ b/Sources/ElixxirDAppsSDK/IdentityMaker.swift @@ -1,9 +1,9 @@ import Bindings public struct IdentityMaker { - public var make: () throws -> Data + public var make: () throws -> Identity - public func callAsFunction() throws -> Data { + public func callAsFunction() throws -> Identity { try make() } } @@ -11,7 +11,9 @@ public struct IdentityMaker { extension IdentityMaker { public static func live(bindingsClient: BindingsClient) -> IdentityMaker { IdentityMaker { - try bindingsClient.makeIdentity() + let data = try bindingsClient.makeIdentity() + let decoder = JSONDecoder() + return try decoder.decode(Identity.self, from: data) } } } diff --git a/Sources/ElixxirDAppsSDK/MessageDeliveryWaiter.swift b/Sources/ElixxirDAppsSDK/MessageDeliveryWaiter.swift index 92b6ab9673ee1f75910f6919e96dc7d089563228..84c19ce7a14fd55d7cbb28aff665f6b6103c2297 100644 --- a/Sources/ElixxirDAppsSDK/MessageDeliveryWaiter.swift +++ b/Sources/ElixxirDAppsSDK/MessageDeliveryWaiter.swift @@ -2,26 +2,28 @@ import Bindings public struct MessageDeliveryWaiter { public enum Result: Equatable { - case delivered(roundResults: Data) + case delivered(roundResults: [Int]) case notDelivered(timedOut: Bool) } - public var wait: (Data, Int, @escaping (Result) -> Void) throws -> Void + public var wait: (MessageSendReport, Int, @escaping (Result) -> Void) throws -> Void public func callAsFunction( - roundList: Data, + report: MessageSendReport, timeoutMS: Int, callback: @escaping (Result) -> Void - ) throws -> Void { - try wait(roundList, timeoutMS, callback) + ) throws { + try wait(report, timeoutMS, callback) } } extension MessageDeliveryWaiter { public static func live(bindingsClient: BindingsClient) -> MessageDeliveryWaiter { - MessageDeliveryWaiter { roundList, timeoutMS, callback in + MessageDeliveryWaiter { report, timeoutMS, callback in + let encoder = JSONEncoder() + let reportData = try encoder.encode(report) try bindingsClient.wait( - forMessageDelivery: roundList, + forMessageDelivery: reportData, mdc: Callback(onCallback: callback), timeoutMS: timeoutMS ) @@ -38,8 +40,14 @@ private final class Callback: NSObject, BindingsMessageDeliveryCallbackProtocol let onCallback: (MessageDeliveryWaiter.Result) -> Void func eventCallback(_ delivered: Bool, timedOut: Bool, roundResults: Data?) { - if delivered, !timedOut, let roundResults = roundResults { - return onCallback(.delivered(roundResults: roundResults)) + if delivered, !timedOut, let roundResultsData = roundResults { + let decoder = JSONDecoder() + do { + let roundResults = try decoder.decode([Int].self, from: roundResultsData) + return onCallback(.delivered(roundResults: roundResults)) + } catch { + fatalError("BindingsMessageDeliveryCallback roundResults decoding error: \(error)") + } } if !delivered, roundResults == nil { return onCallback(.notDelivered(timedOut: timedOut)) diff --git a/Sources/ElixxirDAppsSDK/MessageListener.swift b/Sources/ElixxirDAppsSDK/MessageListener.swift index bed6b8c10517be2fd6d073f2e32b8d941436a795..dc2bfd1ce9a7c8b1ec55cf29ee457a0c450398a4 100644 --- a/Sources/ElixxirDAppsSDK/MessageListener.swift +++ b/Sources/ElixxirDAppsSDK/MessageListener.swift @@ -1,12 +1,12 @@ import Bindings public struct MessageListener { - public var listen: (Int, String, @escaping (Data) -> Void) -> Void + public var listen: (Int, String, @escaping (Message) -> Void) -> Void public func callAsFunction( messageType: Int, listenerName: String = "MessageListener", - callback: @escaping (Data) -> Void + callback: @escaping (Message) -> Void ) { listen(messageType, listenerName, callback) } @@ -39,18 +39,25 @@ extension MessageListener { } private class Listener: NSObject, BindingsListenerProtocol { - init(listenerName: String, onHear: @escaping (Data) -> Void) { + init(listenerName: String, onHear: @escaping (Message) -> Void) { self.listenerName = listenerName self.onHear = onHear super.init() } let listenerName: String - let onHear: (Data) -> Void + let onHear: (Message) -> Void + let decoder = JSONDecoder() func hear(_ item: Data?) { - guard let item = item else { return } - onHear(item) + guard let item = item else { + fatalError("BindingsListenerProtocol.hear received `nil`") + } + do { + onHear(try decoder.decode(Message.self, from: item)) + } catch { + fatalError("Message decoding failed with error: \(error)") + } } func name() -> String { diff --git a/Sources/ElixxirDAppsSDK/MessageSender.swift b/Sources/ElixxirDAppsSDK/MessageSender.swift index 888159b46e503b74ed83cd02b181793317a960ab..4e493b132666a40997a30f63e52dd58f7b3f63af 100644 --- a/Sources/ElixxirDAppsSDK/MessageSender.swift +++ b/Sources/ElixxirDAppsSDK/MessageSender.swift @@ -1,12 +1,12 @@ import Bindings public struct MessageSender { - public var send: (Int, Data) throws -> Data + public var send: (Int, Data) throws -> MessageSendReport public func callAsFunction( messageType: Int, payload: Data - ) throws -> Data { + ) throws -> MessageSendReport { try send(messageType, payload) } } @@ -28,7 +28,10 @@ extension MessageSender { sendE2E: @escaping (Int, Data) throws -> Data ) -> MessageSender { MessageSender { messageType, payload in - try sendE2E(messageType, payload) + let reportData = try sendE2E(messageType, payload) + let decoder = JSONDecoder() + let report = try decoder.decode(MessageSendReport.self, from: reportData) + return report } } } diff --git a/Sources/ElixxirDAppsSDK/RestlikeRequestSender.swift b/Sources/ElixxirDAppsSDK/RestlikeRequestSender.swift index fda472aa16a9854c2cb4d4376766d24a04151cd6..efe42bc5210035e22e0157e5aec4307d811981a2 100644 --- a/Sources/ElixxirDAppsSDK/RestlikeRequestSender.swift +++ b/Sources/ElixxirDAppsSDK/RestlikeRequestSender.swift @@ -1,13 +1,13 @@ import Bindings public struct RestlikeRequestSender { - public var send: (Int, Int, Data) throws -> Data + public var send: (Int, Int, RestlikeMessage) throws -> RestlikeMessage public func callAsFunction( clientId: Int, connectionId: Int, - request: Data - ) throws -> Data { + request: RestlikeMessage + ) throws -> RestlikeMessage { try send(clientId, connectionId, request) } } @@ -15,20 +15,24 @@ public struct RestlikeRequestSender { extension RestlikeRequestSender { public static func live(authenticated: Bool) -> RestlikeRequestSender { RestlikeRequestSender { clientId, connectionId, request in + let encoder = JSONEncoder() + let requestData = try encoder.encode(request) var error: NSError? - let response: Data? + let responseData: Data? if authenticated { - response = BindingsRestlikeRequestAuth(clientId, connectionId, request, &error) + responseData = BindingsRestlikeRequestAuth(clientId, connectionId, requestData, &error) } else { - response = BindingsRestlikeRequest(clientId, connectionId, request, &error) + responseData = BindingsRestlikeRequest(clientId, connectionId, requestData, &error) } if let error = error { throw error } - guard let response = response else { + guard let responseData = responseData else { let functionName = "BindingsRestlikeRequest\(authenticated ? "Auth" : "")" fatalError("\(functionName) returned `nil` without providing error") } + let decoder = JSONDecoder() + let response = try decoder.decode(RestlikeMessage.self, from: responseData) return response } }