From 9f8fef65dbf0f2c5b2b32cd9b30cae2496fd8f1e Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Mon, 6 Jun 2022 11:47:22 +0200
Subject: [PATCH 01/16] Add client getter to SessionEnvironment

---
 Example/example-app/Package.swift                   |  4 ++++
 Example/example-app/Sources/AppFeature/App.swift    |  4 +++-
 .../Sources/SessionFeature/SessionFeature.swift     | 13 +++++++++++--
 3 files changed, 18 insertions(+), 3 deletions(-)

diff --git a/Example/example-app/Package.swift b/Example/example-app/Package.swift
index 3b02bf97..6277f619 100644
--- a/Example/example-app/Package.swift
+++ b/Example/example-app/Package.swift
@@ -139,6 +139,10 @@ let package = Package(
           name: "ComposableArchitecture",
           package: "swift-composable-architecture"
         ),
+        .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 1353882a..1af9cbbe 100644
--- a/Example/example-app/Sources/AppFeature/App.swift
+++ b/Example/example-app/Sources/AppFeature/App.swift
@@ -40,7 +40,9 @@ extension AppEnvironment {
         mainScheduler: mainScheduler,
         error: ErrorEnvironment()
       ),
-      session: SessionEnvironment()
+      session: SessionEnvironment(
+        getClient: { clientSubject.value }
+      )
     )
   }
 }
diff --git a/Example/example-app/Sources/SessionFeature/SessionFeature.swift b/Example/example-app/Sources/SessionFeature/SessionFeature.swift
index 3b7dd16f..c98f2d4d 100644
--- a/Example/example-app/Sources/SessionFeature/SessionFeature.swift
+++ b/Example/example-app/Sources/SessionFeature/SessionFeature.swift
@@ -1,4 +1,5 @@
 import ComposableArchitecture
+import ElixxirDAppsSDK
 
 public struct SessionState: Equatable {
   public init() {}
@@ -9,7 +10,13 @@ public enum SessionAction: Equatable {
 }
 
 public struct SessionEnvironment {
-  public init() {}
+  public init(
+    getClient: @escaping () -> Client?
+  ) {
+    self.getClient = getClient
+  }
+
+  public var getClient: () -> Client?
 }
 
 public let sessionReducer = Reducer<SessionState, SessionAction, SessionEnvironment>
@@ -22,6 +29,8 @@ public let sessionReducer = Reducer<SessionState, SessionAction, SessionEnvironm
 
 #if DEBUG
 extension SessionEnvironment {
-  public static let failing = SessionEnvironment()
+  public static let failing = SessionEnvironment(
+    getClient: { .failing }
+  )
 }
 #endif
-- 
GitLab


From 44bd113210cb0ee246b96cf1cf73d58d7269a7f2 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Mon, 6 Jun 2022 12:01:43 +0200
Subject: [PATCH 02/16] Add id to feature states

---
 Example/example-app/Sources/AppFeature/App.swift  |  1 +
 .../Sources/AppFeature/AppFeature.swift           | 15 ++++++++++-----
 .../Sources/LandingFeature/LandingFeature.swift   |  3 +++
 .../Sources/LandingFeature/LandingView.swift      |  2 +-
 .../Sources/SessionFeature/SessionFeature.swift   |  8 +++++++-
 .../Sources/SessionFeature/SessionView.swift      |  2 +-
 .../Tests/AppFeatureTests/AppFeatureTests.swift   |  6 ++++--
 .../LandingFeatureTests/LandingFeatureTests.swift | 12 ++++++------
 .../SessionFeatureTests/SessionFeatureTests.swift |  2 +-
 9 files changed, 34 insertions(+), 17 deletions(-)

diff --git a/Example/example-app/Sources/AppFeature/App.swift b/Example/example-app/Sources/AppFeature/App.swift
index 1af9cbbe..ed136fb1 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(
diff --git a/Example/example-app/Sources/AppFeature/AppFeature.swift b/Example/example-app/Sources/AppFeature/AppFeature.swift
index b99904b0..347c13b1 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 dfca1d37..511b1107 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 a4dd2b73..4a45669a 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/SessionFeature.swift b/Example/example-app/Sources/SessionFeature/SessionFeature.swift
index c98f2d4d..2e0d9c96 100644
--- a/Example/example-app/Sources/SessionFeature/SessionFeature.swift
+++ b/Example/example-app/Sources/SessionFeature/SessionFeature.swift
@@ -2,7 +2,13 @@ import ComposableArchitecture
 import ElixxirDAppsSDK
 
 public struct SessionState: Equatable {
-  public init() {}
+  public init(
+    id: UUID
+  ) {
+    self.id = id
+  }
+
+  public var id: UUID
 }
 
 public enum SessionAction: Equatable {
diff --git a/Example/example-app/Sources/SessionFeature/SessionView.swift b/Example/example-app/Sources/SessionFeature/SessionView.swift
index eb1fcf00..cbf8934e 100644
--- a/Example/example-app/Sources/SessionFeature/SessionView.swift
+++ b/Example/example-app/Sources/SessionFeature/SessionView.swift
@@ -27,7 +27,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 96a0b077..ce5891ed 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 df9791e7..c5b56f98 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 5ed11e32..1e13e0d1 100644
--- a/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift
+++ b/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift
@@ -5,7 +5,7 @@ import XCTest
 final class SessionFeatureTests: XCTestCase {
   func testViewDidLoad() throws {
     let store = TestStore(
-      initialState: SessionState(),
+      initialState: SessionState(id: UUID()),
       reducer: sessionReducer,
       environment: .failing
     )
-- 
GitLab


From 503cba3faf1b8f13da57b8946633fbc14f0d2a82 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Mon, 6 Jun 2022 12:55:54 +0200
Subject: [PATCH 03/16] Add NetworkFollowerStatusView

---
 .../NetworkFollowerStatusView.swift           | 44 +++++++++++++++++++
 1 file changed, 44 insertions(+)
 create mode 100644 Example/example-app/Sources/SessionFeature/NetworkFollowerStatusView.swift

diff --git a/Example/example-app/Sources/SessionFeature/NetworkFollowerStatusView.swift b/Example/example-app/Sources/SessionFeature/NetworkFollowerStatusView.swift
new file mode 100644
index 00000000..5d1ba1de
--- /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
-- 
GitLab


From 8ab5b3b57abe9de0d03bc6b655cd4b85f636e14c Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Mon, 6 Jun 2022 13:14:46 +0200
Subject: [PATCH 04/16] Start & stop network follower from SessionView

---
 Example/example-app/Package.swift             |   5 +
 .../example-app/Sources/AppFeature/App.swift  |   4 +-
 .../SessionFeature/SessionFeature.swift       |  72 ++++++++++-
 .../Sources/SessionFeature/SessionView.swift  |  47 ++++++-
 .../SessionFeatureTests.swift                 | 118 +++++++++++++++++-
 5 files changed, 235 insertions(+), 11 deletions(-)

diff --git a/Example/example-app/Package.swift b/Example/example-app/Package.swift
index 6277f619..55d54626 100644
--- a/Example/example-app/Package.swift
+++ b/Example/example-app/Package.swift
@@ -135,10 +135,15 @@ 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"
diff --git a/Example/example-app/Sources/AppFeature/App.swift b/Example/example-app/Sources/AppFeature/App.swift
index ed136fb1..341cb46c 100644
--- a/Example/example-app/Sources/AppFeature/App.swift
+++ b/Example/example-app/Sources/AppFeature/App.swift
@@ -42,7 +42,9 @@ extension AppEnvironment {
         error: ErrorEnvironment()
       ),
       session: SessionEnvironment(
-        getClient: { clientSubject.value }
+        getClient: { clientSubject.value },
+        bgScheduler: bgScheduler,
+        mainScheduler: mainScheduler
       )
     )
   }
diff --git a/Example/example-app/Sources/SessionFeature/SessionFeature.swift b/Example/example-app/Sources/SessionFeature/SessionFeature.swift
index 2e0d9c96..b07a14cc 100644
--- a/Example/example-app/Sources/SessionFeature/SessionFeature.swift
+++ b/Example/example-app/Sources/SessionFeature/SessionFeature.swift
@@ -1,34 +1,98 @@
+import Combine
 import ComposableArchitecture
 import ElixxirDAppsSDK
+import ErrorFeature
 
 public struct SessionState: Equatable {
   public init(
-    id: UUID
+    id: UUID,
+    networkFollowerStatus: NetworkFollowerStatus? = nil,
+    error: ErrorState? = nil
   ) {
     self.id = id
+    self.networkFollowerStatus = networkFollowerStatus
+    self.error = error
   }
 
   public var id: UUID
+  public var networkFollowerStatus: NetworkFollowerStatus?
+  public var error: ErrorState?
 }
 
 public enum SessionAction: Equatable {
   case viewDidLoad
+  case updateNetworkFollowerStatus
+  case didUpdateNetworkFollowerStatus(NetworkFollowerStatus?)
+  case runNetworkFollower(Bool)
+  case networkFollowerDidFail(NSError)
+  case error(ErrorAction)
+  case didDismissError
 }
 
 public struct SessionEnvironment {
   public init(
-    getClient: @escaping () -> Client?
+    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),
+    ])
+
+  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 .didDismissError:
+    state.error = nil
     return .none
   }
 }
@@ -36,7 +100,9 @@ public let sessionReducer = Reducer<SessionState, SessionAction, SessionEnvironm
 #if DEBUG
 extension SessionEnvironment {
   public static let failing = SessionEnvironment(
-    getClient: { .failing }
+    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 cbf8934e..74b4d130 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,50 @@ public struct SessionView: View {
   let store: Store<SessionState, SessionAction>
 
   struct ViewState: Equatable {
-    init(state: SessionState) {}
+    let networkFollowerStatus: NetworkFollowerStatus?
+
+    init(state: SessionState) {
+      networkFollowerStatus = state.networkFollowerStatus
+    }
   }
 
   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")
         }
+      }
+      .navigationTitle("Session")
+      .task {
+        viewStore.send(.viewDidLoad)
+      }
+      .sheet(
+        store.scope(
+          state: \.error,
+          action: SessionAction.error
+        ),
+        onDismiss: {
+          viewStore.send(.didDismissError)
+        },
+        content: ErrorView.init(store:)
+      )
     }
   }
 }
diff --git a/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift b/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift
index 1e13e0d1..092573ed 100644
--- a/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift
+++ b/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift
@@ -1,15 +1,129 @@
 import ComposableArchitecture
+import ElixxirDAppsSDK
+import ErrorFeature
 import XCTest
 @testable import SessionFeature
 
 final class SessionFeatureTests: XCTestCase {
-  func testViewDidLoad() throws {
+  func testViewDidLoad() {
+    var networkFollowerStatus: NetworkFollowerStatus!
+    let bgScheduler = DispatchQueue.test
+    let mainScheduler = DispatchQueue.test
+
+    var env = SessionEnvironment.failing
+    env.getClient = {
+      var client = Client.failing
+      client.networkFollower.status.status = { networkFollowerStatus }
+      return client
+    }
+    env.bgScheduler = bgScheduler.eraseToAnyScheduler()
+    env.mainScheduler = mainScheduler.eraseToAnyScheduler()
+
     let store = TestStore(
       initialState: SessionState(id: UUID()),
       reducer: sessionReducer,
-      environment: .failing
+      environment: env
     )
 
     store.send(.viewDidLoad)
+
+    store.receive(.updateNetworkFollowerStatus)
+
+    networkFollowerStatus = .stopped
+    bgScheduler.advance()
+    mainScheduler.advance()
+
+    store.receive(.didUpdateNetworkFollowerStatus(.stopped)) {
+      $0.networkFollowerStatus = .stopped
+    }
+  }
+
+  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
+    }
   }
 }
-- 
GitLab


From 52611a24e52bde6e201ff5ccd3c53f5d0fa1880f Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Mon, 6 Jun 2022 13:44:50 +0200
Subject: [PATCH 05/16] Add NetworkHealthStatusView

---
 .../NetworkHealthStatusView.swift             | 33 +++++++++++++++++++
 1 file changed, 33 insertions(+)
 create mode 100644 Example/example-app/Sources/SessionFeature/NetworkHealthStatusView.swift

diff --git a/Example/example-app/Sources/SessionFeature/NetworkHealthStatusView.swift b/Example/example-app/Sources/SessionFeature/NetworkHealthStatusView.swift
new file mode 100644
index 00000000..e13cb6fc
--- /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
-- 
GitLab


From 47d1a2b33c59807bc67e90357c7d44914d08854f Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Mon, 6 Jun 2022 13:55:14 +0200
Subject: [PATCH 06/16] Monitor network health in SessionFeature

---
 .../SessionFeature/SessionFeature.swift       | 39 ++++++++++++++++++-
 .../Sources/SessionFeature/SessionView.swift  |  8 ++++
 .../SessionFeatureTests.swift                 | 29 ++++++++++++++
 3 files changed, 75 insertions(+), 1 deletion(-)

diff --git a/Example/example-app/Sources/SessionFeature/SessionFeature.swift b/Example/example-app/Sources/SessionFeature/SessionFeature.swift
index b07a14cc..0756f738 100644
--- a/Example/example-app/Sources/SessionFeature/SessionFeature.swift
+++ b/Example/example-app/Sources/SessionFeature/SessionFeature.swift
@@ -7,15 +7,18 @@ public struct SessionState: Equatable {
   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?
 }
 
@@ -25,8 +28,10 @@ public enum SessionAction: Equatable {
   case didUpdateNetworkFollowerStatus(NetworkFollowerStatus?)
   case runNetworkFollower(Bool)
   case networkFollowerDidFail(NSError)
-  case error(ErrorAction)
+  case monitorNetworkHealth(Bool)
+  case didUpdateNetworkHealth(Bool?)
   case didDismissError
+  case error(ErrorAction)
 }
 
 public struct SessionEnvironment {
@@ -51,6 +56,7 @@ public let sessionReducer = Reducer<SessionState, SessionAction, SessionEnvironm
   case .viewDidLoad:
     return .merge([
       .init(value: .updateNetworkFollowerStatus),
+      .init(value: .monitorNetworkHealth(true)),
     ])
 
   case .updateNetworkFollowerStatus:
@@ -91,9 +97,40 @@ public let sessionReducer = Reducer<SessionState, SessionAction, SessionEnvironm
     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
   }
 }
 
diff --git a/Example/example-app/Sources/SessionFeature/SessionView.swift b/Example/example-app/Sources/SessionFeature/SessionView.swift
index 74b4d130..395cfcb1 100644
--- a/Example/example-app/Sources/SessionFeature/SessionView.swift
+++ b/Example/example-app/Sources/SessionFeature/SessionView.swift
@@ -13,9 +13,11 @@ public struct SessionView: View {
 
   struct ViewState: Equatable {
     let networkFollowerStatus: NetworkFollowerStatus?
+    let isNetworkHealthy: Bool?
 
     init(state: SessionState) {
       networkFollowerStatus = state.networkFollowerStatus
+      isNetworkHealthy = state.isNetworkHealthy
     }
   }
 
@@ -41,6 +43,12 @@ public struct SessionView: View {
         } header: {
           Text("Network follower")
         }
+
+        Section {
+          NetworkHealthStatusView(status: viewStore.isNetworkHealthy)
+        } header: {
+          Text("Network health")
+        }
       }
       .navigationTitle("Session")
       .task {
diff --git a/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift b/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift
index 092573ed..2ada840d 100644
--- a/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift
+++ b/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift
@@ -7,6 +7,9 @@ import XCTest
 final class SessionFeatureTests: XCTestCase {
   func testViewDidLoad() {
     var networkFollowerStatus: NetworkFollowerStatus!
+    var didStartMonitoringNetworkHealth = 0
+    var didStopMonitoringNetworkHealth = 0
+    var networkHealthCallback: ((Bool) -> Void)!
     let bgScheduler = DispatchQueue.test
     let mainScheduler = DispatchQueue.test
 
@@ -14,6 +17,13 @@ final class SessionFeatureTests: XCTestCase {
     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()
@@ -28,6 +38,7 @@ final class SessionFeatureTests: XCTestCase {
     store.send(.viewDidLoad)
 
     store.receive(.updateNetworkFollowerStatus)
+    store.receive(.monitorNetworkHealth(true))
 
     networkFollowerStatus = .stopped
     bgScheduler.advance()
@@ -36,6 +47,24 @@ final class SessionFeatureTests: XCTestCase {
     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() {
-- 
GitLab


From fd86cea6245d58fc31e9befd3f76d2af66720223 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Tue, 7 Jun 2022 11:04:34 +0200
Subject: [PATCH 07/16] Update IdentityMaker to return Identity

---
 Sources/ElixxirDAppsSDK/IdentityMaker.swift | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/Sources/ElixxirDAppsSDK/IdentityMaker.swift b/Sources/ElixxirDAppsSDK/IdentityMaker.swift
index 91e7eeed..863e180e 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)
     }
   }
 }
-- 
GitLab


From c6042614b2865361ee8a272dd26097528c1691b5 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Tue, 7 Jun 2022 11:27:38 +0200
Subject: [PATCH 08/16] Update ConnectionMaker to accept Identity model

---
 Sources/ElixxirDAppsSDK/ConnectionMaker.swift | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/Sources/ElixxirDAppsSDK/ConnectionMaker.swift b/Sources/ElixxirDAppsSDK/ConnectionMaker.swift
index 4baa7618..34d7c14b 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
           )
         )
       }
-- 
GitLab


From 04d241f266bb39a5b65e54d9924887571d625b96 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Tue, 7 Jun 2022 11:35:30 +0200
Subject: [PATCH 09/16] Use Message model in MessageListener

---
 Sources/ElixxirDAppsSDK/MessageListener.swift | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/Sources/ElixxirDAppsSDK/MessageListener.swift b/Sources/ElixxirDAppsSDK/MessageListener.swift
index bed6b8c1..dc2bfd1c 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 {
-- 
GitLab


From 5c954babb949fcb08e8343daedce402cadfa9758 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Tue, 7 Jun 2022 11:39:05 +0200
Subject: [PATCH 10/16] Use RestlikeMessage model in RestlikeRequestSender

---
 .../RestlikeRequestSender.swift                | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

diff --git a/Sources/ElixxirDAppsSDK/RestlikeRequestSender.swift b/Sources/ElixxirDAppsSDK/RestlikeRequestSender.swift
index fda472aa..efe42bc5 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
     }
   }
-- 
GitLab


From 3e1a515f6214542866d5de49cf66206c377f62ed Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Tue, 7 Jun 2022 11:53:13 +0200
Subject: [PATCH 11/16] Use MessageSendReport in MessageDeliveryWaiter

---
 .../ElixxirDAppsSDK/MessageDeliveryWaiter.swift    | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/Sources/ElixxirDAppsSDK/MessageDeliveryWaiter.swift b/Sources/ElixxirDAppsSDK/MessageDeliveryWaiter.swift
index 92b6ab96..38283a47 100644
--- a/Sources/ElixxirDAppsSDK/MessageDeliveryWaiter.swift
+++ b/Sources/ElixxirDAppsSDK/MessageDeliveryWaiter.swift
@@ -6,22 +6,24 @@ public struct MessageDeliveryWaiter {
     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
       )
-- 
GitLab


From 37629e49f1a9a0980071ebffd408a3e04c5ed762 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Tue, 7 Jun 2022 11:55:06 +0200
Subject: [PATCH 12/16] Use MessageSendReport in MessageSender

---
 Sources/ElixxirDAppsSDK/MessageSender.swift | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/Sources/ElixxirDAppsSDK/MessageSender.swift b/Sources/ElixxirDAppsSDK/MessageSender.swift
index 888159b4..4e493b13 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
     }
   }
 }
-- 
GitLab


From d331d62826073682917001898c2a4592c39722af Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Tue, 7 Jun 2022 12:02:15 +0200
Subject: [PATCH 13/16] Use [Int] in MessageDeliveryWaiter result

---
 Sources/ElixxirDAppsSDK/MessageDeliveryWaiter.swift | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/Sources/ElixxirDAppsSDK/MessageDeliveryWaiter.swift b/Sources/ElixxirDAppsSDK/MessageDeliveryWaiter.swift
index 38283a47..84c19ce7 100644
--- a/Sources/ElixxirDAppsSDK/MessageDeliveryWaiter.swift
+++ b/Sources/ElixxirDAppsSDK/MessageDeliveryWaiter.swift
@@ -2,7 +2,7 @@ import Bindings
 
 public struct MessageDeliveryWaiter {
   public enum Result: Equatable {
-    case delivered(roundResults: Data)
+    case delivered(roundResults: [Int])
     case notDelivered(timedOut: Bool)
   }
 
@@ -40,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))
-- 
GitLab


From 18bc435a56b876e4f5535909d43777bc8b2f7ec4 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Tue, 7 Jun 2022 12:04:20 +0200
Subject: [PATCH 14/16] Use Identity model in ContactFromIdentityProvider

---
 .../ElixxirDAppsSDK/ContactFromIdentityProvider.swift | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/Sources/ElixxirDAppsSDK/ContactFromIdentityProvider.swift b/Sources/ElixxirDAppsSDK/ContactFromIdentityProvider.swift
index 1643a0cd..292f217d 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
+    }
   }
 }
 
-- 
GitLab


From e8194482e842365f3d2abbf6bdf0437815386cc9 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Tue, 7 Jun 2022 12:10:01 +0200
Subject: [PATCH 15/16] Use Fact model in ContactFactsProvider

---
 Sources/ElixxirDAppsSDK/ContactFactsProvider.swift | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/Sources/ElixxirDAppsSDK/ContactFactsProvider.swift b/Sources/ElixxirDAppsSDK/ContactFactsProvider.swift
index e2adebfb..2029dc6f 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
   }
 }
-- 
GitLab


From 8bdd389799cc895ff9ac2cd95105d7ff29acd59a Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Tue, 7 Jun 2022 12:10:12 +0200
Subject: [PATCH 16/16] Use Fact model in ContactFactsSetter

---
 Sources/ElixxirDAppsSDK/ContactFactsSetter.swift | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/Sources/ElixxirDAppsSDK/ContactFactsSetter.swift b/Sources/ElixxirDAppsSDK/ContactFactsSetter.swift
index 0fca1df3..8dd0ed98 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
     }
-- 
GitLab