Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ABW-1315] Radix Connect requset to CE and response from CE #444

Merged
merged 29 commits into from
Apr 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ package.addModules([
featureSuffixDroppedFromFolderName: true,
dependencies: [
"FactorSourcesClient",
"RadixConnectClient",
],
tests: .no
),
Expand Down Expand Up @@ -506,6 +507,7 @@ package.addModules([
dependencies: [
"RadixConnect",
"P2PLinksClient",
.product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
],
tests: .yes()
),
Expand Down Expand Up @@ -610,14 +612,14 @@ package.addModules([
.package(url: "https://github.com/siteline/SwiftUI-Introspect", from: "0.1.4")
},
.product(name: "NavigationTransitions", package: "swiftui-navigation-transitions", condition: .when(platforms: [.iOS])) {
.package(url: "https://github.com/davdroman/swiftui-navigation-transitions", from: "0.1.0")
.package(url: "https://github.com/davdroman/swiftui-navigation-transitions", exact: "0.9.0")
},
.product(name: "NukeUI", package: "Nuke") {
.package(url: "https://github.com/kean/Nuke", from: "11.3.1")
},
"Resources",
.product(name: "SwiftUINavigation", package: "swiftui-navigation") {
.package(url: "https://github.com/pointfreeco/swiftui-navigation", from: "0.4.3")
.package(url: "https://github.com/pointfreeco/swiftui-navigation", exact: "0.7.1")
},
.product(name: "TextBuilder", package: "TextBuilder") {
.package(url: "https://github.com/davdroman/TextBuilder", from: "2.2.0")
Expand Down Expand Up @@ -804,19 +806,19 @@ package.addModules([
.package(url: "https://github.com/JohnSundell/CollectionConcurrencyKit.git", from: "0.1.0")
},
.product(name: "CustomDump", package: "swift-custom-dump") {
.package(url: "https://github.com/pointfreeco/swift-custom-dump", from: "0.6.1")
.package(url: "https://github.com/pointfreeco/swift-custom-dump", exact: "0.9.1")
},
.product(name: "Dependencies", package: "swift-dependencies") {
.package(url: "https://github.com/pointfreeco/swift-dependencies", from: "0.1.1")
.package(url: "https://github.com/pointfreeco/swift-dependencies", exact: "0.2.0")
},
.product(name: "DependenciesAdditions", package: "swift-dependencies-additions") {
.package(url: "https://github.com/tgrapperon/swift-dependencies-additions", from: "0.2.0")
.package(url: "https://github.com/tgrapperon/swift-dependencies-additions", exact: "0.3.0")
},
.product(name: "Either", package: "swift-either") {
.package(url: "https://github.com/pointfreeco/swift-either", branch: "main")
},
.product(name: "IdentifiedCollections", package: "swift-identified-collections") {
.package(url: "https://github.com/pointfreeco/swift-identified-collections", from: "0.6.0")
.package(url: "https://github.com/pointfreeco/swift-identified-collections", exact: "0.7.0")
},
.product(name: "KeychainAccess", package: "KeychainAccess") {
.package(url: "https://github.com/kishikawakatsumi/KeychainAccess", from: "4.2.2")
Expand All @@ -825,16 +827,16 @@ package.addModules([
.package(url: "https://github.com/mxcl/LegibleError", from: "1.0.6")
},
.product(name: "NonEmpty", package: "swift-nonempty") {
.package(url: "https://github.com/pointfreeco/swift-nonempty", from: "0.4.0")
.package(url: "https://github.com/pointfreeco/swift-nonempty", exact: "0.4.0")
},
.product(name: "SwiftLogConsoleColors", package: "swift-log-console-colors") {
.package(url: "https://github.com/nneuberger1/swift-log-console-colors", from: "1.0.3")
},
.product(name: "Tagged", package: "swift-tagged") {
.package(url: "https://github.com/pointfreeco/swift-tagged", from: "0.7.0")
.package(url: "https://github.com/pointfreeco/swift-tagged", exact: "0.10.0")
},
.product(name: "Validated", package: "swift-validated") {
.package(url: "https://github.com/pointfreeco/swift-validated", from: "0.2.1")
.package(url: "https://github.com/pointfreeco/swift-validated", exact: "0.2.1")
},
],
tests: .yes(dependencies: [])
Expand Down
4 changes: 2 additions & 2 deletions Sources/Clients/ROLAClient/ROLAClient+Interface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ public struct ROLAClient: Sendable, DependencyKey {

// MARK: ROLAClient.PerformWellKnownFileCheck
extension ROLAClient {
public typealias PerformDappDefinitionVerification = @Sendable (P2P.FromDapp.WalletInteraction.Metadata) async throws -> Void
public typealias PerformWellKnownFileCheck = @Sendable (P2P.FromDapp.WalletInteraction.Metadata) async throws -> Void
public typealias PerformDappDefinitionVerification = @Sendable (P2P.Dapp.Request.Metadata) async throws -> Void
public typealias PerformWellKnownFileCheck = @Sendable (P2P.Dapp.Request.Metadata) async throws -> Void
}

extension DependencyValues {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Clients/ROLAClient/ROLAFailure.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public enum ROLAFailure: Sendable, LocalizedError, Equatable {
}
}

public var errorKindAndMessage: (errorKind: P2P.ToDapp.WalletInteractionFailureResponse.ErrorType, message: String?) {
public var errorKindAndMessage: (errorKind: P2P.Dapp.Response.WalletInteractionFailureResponse.ErrorType, message: String?) {
switch self {
case .wrongAccountType:
return (errorKind: .wrongAccountType, message: errorDescription)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ public struct RadixConnectClient: DependencyKey, Sendable {
public var addP2PWithPassword: AddP2PWithPassword

public var receiveMessages: ReceiveMessages
public var sendMessage: SendMessage

public var sendResponse: SendResponse
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A response is for now typically a response back to a Dapp for some "wallet interaction".

public var sendRequest: SendRequest
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A request is a new message, typically a request to Connector extension to execute some Ledger Hardware device command, recipient RTC peer is unknown, see SendStrategy below.

}

extension RadixConnectClient {
Expand All @@ -39,6 +41,9 @@ extension RadixConnectClient {
public typealias AddP2PWithPassword = @Sendable (ConnectionPassword) async throws -> Void
public typealias DeleteP2PLinkByPassword = @Sendable (ConnectionPassword) async throws -> Void

public typealias ReceiveMessages = @Sendable () async -> AsyncStream<P2P.RTCIncomingMessageResult>
public typealias SendMessage = @Sendable (P2P.RTCOutgoingMessage) async throws -> Void
public typealias ReceiveMessages = @Sendable () async -> AnyAsyncSequence<P2P.RTCIncomingMessage>

public typealias SendRequest = @Sendable (_ request: P2P.RTCOutgoingMessage.Request, _ sendStrategy: P2P.RTCOutgoingMessage.Request.SendStrategy) async throws -> Void

public typealias SendResponse = @Sendable (_ response: P2P.RTCOutgoingMessage.Response, _ origin: P2P.RTCRoute) async throws -> Void
}
34 changes: 31 additions & 3 deletions Sources/Clients/RadixConnectClient/RadixConnectClient+Live.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ClientPrelude
import ComposableArchitecture // actually CasePaths... but CI if we do `import CasePaths` 🤷‍♂️
import Network
import P2PLinksClient
import RadixConnect
Expand Down Expand Up @@ -51,14 +52,41 @@ extension RadixConnectClient {
addP2PWithPassword: { password in
try await rtcClients.connect(password, waitsForConnectionToBeEstablished: true)
},
receiveMessages: { await rtcClients.incomingMessages },
sendMessage: { outgoingMsg in
try await rtcClients.sendMessage(outgoingMsg)
receiveMessages: { await rtcClients.incomingMessages() },
sendResponse: { response, route in
try await rtcClients.sendResponse(response, to: route)
},
sendRequest: { request, strategy in
try await rtcClients.sendRequest(request, strategy: strategy)
}
)
}()
}

extension AsyncSequence where AsyncIterator: Sendable, Element == P2P.RTCIncomingMessage {
func compactMap<Case>(
_ casePath: CasePath<P2P.RTCMessageFromPeer, Case>
) async -> AnyAsyncSequence<P2P.RTCIncomingMessageContainer<Case>> {
compactMap { $0.unpackMap(casePath.extract) }
.share()
.eraseToAnyAsyncSequence()
}
}

extension RadixConnectClient {
public func receiveRequests<Case>(
_ casePath: CasePath<P2P.RTCMessageFromPeer.Request, Case>
) async -> AnyAsyncSequence<P2P.RTCIncomingMessageContainer<Case>> {
await receiveMessages().compactMap(/P2P.RTCMessageFromPeer.request .. casePath)
}

public func receiveResponses<Case>(
_ casePath: CasePath<P2P.RTCMessageFromPeer.Response, Case>
) async -> AnyAsyncSequence<P2P.RTCIncomingMessageContainer<Case>> {
await receiveMessages().compactMap(/P2P.RTCMessageFromPeer.response .. casePath)
}
}

// MARK: - LocalNetworkAuthorization
/// Source: https://stackoverflow.com/a/67758105/705761
private final class LocalNetworkAuthorization: NSObject, @unchecked Sendable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ extension RadixConnectClient: TestDependencyKey {
deleteP2PLinkByPassword: unimplemented("\(Self.self).deleteP2PLinkByPassword"),
addP2PWithPassword: unimplemented("\(Self.self).addP2PWithPassword"),
receiveMessages: unimplemented("\(Self.self).receiveMessages"),
sendMessage: unimplemented("\(Self.self).sendMessage")
sendResponse: unimplemented("\(Self.self).sendResponse"),
sendRequest: unimplemented("\(Self.self).sendRequest")
)
}

Expand All @@ -28,8 +29,9 @@ extension RadixConnectClient {
storeP2PLink: { _ in },
deleteP2PLinkByPassword: { _ in },
addP2PWithPassword: { _ in },
receiveMessages: { AsyncStream<P2P.RTCIncomingMessageResult>(unfolding: { nil }) },
sendMessage: { _ in }
receiveMessages: { AsyncLazySequence([]).eraseToAnyAsyncSequence() },
sendResponse: { _, _ in },
sendRequest: { _, _ in }
)
}
#endif // DEBUG
2 changes: 1 addition & 1 deletion Sources/Clients/TransactionClient/TransactionFailure.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public enum TransactionFailure: Sendable, LocalizedError, Equatable {
}

extension TransactionFailure {
public var errorKindAndMessage: (errorKind: P2P.ToDapp.WalletInteractionFailureResponse.ErrorType, message: String?) {
public var errorKindAndMessage: (errorKind: P2P.Dapp.Response.WalletInteractionFailureResponse.ErrorType, message: String?) {
switch self {
case let .failedToPrepareForTXSigning(error), let .failedToPrepareTXReview(.failedSigning(error)):
switch error {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import RadixConnectModels

extension P2P {
/// Recipient of sender of an RTC message
public struct RTCRoute: Sendable, Hashable {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A since long missing tuple with the information on HOW to send an RTC message.

/// The PerPeerPairConnection password.
public let connectionId: ConnectionPassword
/// ID to a specific peer **connection** for some PerPeerPairConnection.
public let peerConnectionId: PeerConnectionID

public init(connectionId: ConnectionPassword, peerConnectionId: PeerConnectionID) {
self.connectionId = connectionId
self.peerConnectionId = peerConnectionId
}
}

/// An incoming message over RTC from some `route`, might have failed
/// or succeeded to receive and decode, which is why this contains a
/// `result` and not an `P2P.RTCMessageFromPeer` directly.
public typealias RTCIncomingMessage = RTCIncomingMessageContainer<P2P.RTCMessageFromPeer>

/// An incoming Dapp Request over RTC from some `route`, might have failed
/// or succeeded to receive and decode, which is why this contains a
/// `result` and not an `P2P.Dapp.Request` directly.
public typealias RTCIncomingDappRequest = RTCIncomingMessageContainer<P2P.Dapp.Request>

/// An incoming message over RTC from some `route`, might have failed
/// or succeeded to receive and decode, which is why this contains a
/// `result` and not an `P2P.RTCMessageFromPeer` directly.
public struct RTCIncomingMessageContainer<Success: Sendable & Hashable>: Sendable, Hashable {
public let result: Result<Success, Error>
public let route: RTCRoute
public init(result: Result<Success, Error>, route: RTCRoute) {
self.result = result
self.route = route
}
}
}

extension P2P.RTCIncomingMessageContainer {
public static func == (lhs: Self, rhs: Self) -> Bool {
guard lhs.route == rhs.route else {
return false
}
switch (lhs.result, rhs.result) {
case let (.failure(lhsFailure), .failure(rhsFailure)):
// FIXME: strongly type messages? to an Error type which is Hashable?
return String(describing: lhsFailure) == String(describing: rhsFailure)
case let (.success(lhsSuccess), .success(rhsSuccess)):
return lhsSuccess == rhsSuccess

default: return false
}
}

public func hash(into hasher: inout Hasher) {
hasher.combine(route)
switch result {
case let .failure(error):
hasher.combine(String(describing: error))
case let .success(success):
hasher.combine(success)
}
}

public func unpackMap<NewSuccess>(
_ transform: (Success) -> NewSuccess?
) -> P2P.RTCIncomingMessageContainer<NewSuccess>? {
switch result {
case let .failure(error): return .init(result: .failure(error), route: route)
case let .success(value):
guard let transformed = transform(value) else {
return nil
}
return .init(result: .success(transformed), route: route)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import RadixConnectModels

// MARK: - P2P.RTCMessageFromPeer
extension P2P {
/// A successfully received and decoded message from a peer
/// either a `response` or a `request`.
public enum RTCMessageFromPeer: Sendable, Hashable {
/// A response from a peer to some request we have sent over RTC
case response(Response)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@GhenadieVP I was able to remove the RTCRoute from here, was no needed.


/// A request coming from some peer over RTC
case request(Request)

/// A response from a peer to some request we have sent.
public enum Response: Sendable, Hashable, Equatable, Decodable {
case connectorExtension(P2P.ConnectorExtension.Response)
}

public enum Request: Sendable, Hashable, Equatable, Decodable {
case dapp(P2P.Dapp.Request)
}
}
}

extension P2P.RTCMessageFromPeer.Request {
public init(from decoder: Decoder) throws {
self = try .dapp(.init(from: decoder))
}
}

extension P2P.RTCMessageFromPeer.Response {
public init(from decoder: Decoder) throws {
self = try .connectorExtension(.init(from: decoder))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import RadixConnectModels

// MARK: - P2P.RTCOutgoingMessage
extension P2P {
/// A message to be sent to some peer over WebRTC
/// it might be either a response to some original
/// incoming request, or it might be a request initiated
/// by our wallet.
public enum RTCOutgoingMessage: Sendable, Hashable {
/// A response to some request that we received from `origin`,
/// we will use `origin` to identify the RTC channel to send over.
case response(Response, origin: RTCRoute)

/// A request initiated by us, sent over RTC using `sendStrategy`.
case request(Request, sendStrategy: Request.SendStrategy)

/// A response to some request that we received from `origin`,
/// we will use `origin` to identify the RTC channel to send over.
public enum Response: Sendable, Hashable, Encodable {
/// Response back to Dapps
case dapp(P2P.Dapp.Response)
}

/// A request initiated by us, sent over RTC using `SendStrategy`.
public enum Request: Sendable, Hashable, Encodable {
/// Describes the strategy used to find a RTC peer to send a request to.
public enum SendStrategy: Sendable, Hashable, Equatable {
/// Sends a request to ALL P2PLinks
case broadcastToAllPeers
}

/// e.g. for Ledger Nano interaction, `PeerConnectionID` is not known
case connectorExtension(P2P.ConnectorExtension.Request)
}
}
}

extension P2P.RTCOutgoingMessage.Response {
public func encode(to encoder: Encoder) throws {
switch self {
case let .dapp(response):
try response.encode(to: encoder)
}
}
}
Loading