-
Notifications
You must be signed in to change notification settings - Fork 191
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #511 from WalletConnect/pairing-api
[Pairing] Pairing API
- Loading branch information
Showing
23 changed files
with
418 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import Foundation | ||
import XCTest | ||
import WalletConnectUtils | ||
@testable import WalletConnectKMS | ||
import WalletConnectRelay | ||
import Combine | ||
import WalletConnectNetworking | ||
@testable import WalletConnectPairing | ||
|
||
|
||
final class PairingTests: XCTestCase { | ||
var appPairingClient: PairingClient! | ||
var walletPairingClient: PairingClient! | ||
|
||
var appPushClient: PushClient! | ||
var walletPushClient: PushClient! | ||
|
||
var pairingStorage: PairingStorage! | ||
|
||
private var publishers = [AnyCancellable]() | ||
|
||
override func setUp() { | ||
(appPairingClient, appPushClient) = makeClients(prefix: "🤖 App") | ||
(walletPairingClient, walletPushClient) = makeClients(prefix: "🐶 Wallet") | ||
} | ||
|
||
func makeClients(prefix: String) -> (PairingClient, PushClient) { | ||
let keychain = KeychainStorageMock() | ||
let logger = ConsoleLogger(suffix: prefix, loggingLevel: .debug) | ||
let projectId = "3ca2919724fbfa5456a25194e369a8b4" | ||
let relayClient = RelayClient(relayHost: URLConfig.relayHost, projectId: projectId, keychainStorage: keychain, socketFactory: SocketFactory(), logger: logger) | ||
|
||
let pairingClient = PairingClientFactory.create(logger: logger, keyValueStorage: RuntimeKeyValueStorage(), keychainStorage: keychain, relayClient: relayClient) | ||
|
||
let pushClient = PushClientFactory.create(logger: logger, keyValueStorage: RuntimeKeyValueStorage(), keychainStorage: keychain, relayClient: relayClient, pairingClient: pairingClient) | ||
return (pairingClient, pushClient) | ||
|
||
} | ||
|
||
func testProposePushOnPairing() async throws { | ||
let exp = expectation(description: "testProposePushOnPairing") | ||
|
||
walletPushClient.proposalPublisher.sink { _ in | ||
exp.fulfill() | ||
}.store(in: &publishers) | ||
|
||
let uri = try await appPairingClient.create() | ||
|
||
try await walletPairingClient.pair(uri: uri) | ||
|
||
try await appPushClient.propose(topic: uri.topic) | ||
|
||
wait(for: [exp], timeout: 2) | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import Foundation | ||
import WalletConnectUtils | ||
import WalletConnectRelay | ||
import WalletConnectNetworking | ||
import Combine | ||
|
||
public class PairingClient: PairingRegisterer { | ||
private let walletPairService: WalletPairService | ||
private let appPairService: AppPairService | ||
public let socketConnectionStatusPublisher: AnyPublisher<SocketConnectionStatus, Never> | ||
private let logger: ConsoleLogging | ||
private let networkingInteractor: NetworkInteracting | ||
private let pairingRequestsSubscriber: PairingRequestsSubscriber | ||
|
||
init(appPairService: AppPairService, | ||
networkingInteractor: NetworkInteracting, | ||
logger: ConsoleLogging, | ||
walletPairService: WalletPairService, | ||
pairingRequestsSubscriber: PairingRequestsSubscriber, | ||
socketConnectionStatusPublisher: AnyPublisher<SocketConnectionStatus, Never> | ||
) { | ||
self.appPairService = appPairService | ||
self.walletPairService = walletPairService | ||
self.networkingInteractor = networkingInteractor | ||
self.socketConnectionStatusPublisher = socketConnectionStatusPublisher | ||
self.logger = logger | ||
self.pairingRequestsSubscriber = pairingRequestsSubscriber | ||
} | ||
/// For wallet to establish a pairing | ||
/// Wallet should call this function in order to accept peer's pairing proposal and be able to subscribe for future requests. | ||
/// - Parameter uri: Pairing URI that is commonly presented as a QR code by a dapp or delivered with universal linking. | ||
/// | ||
/// Throws Error: | ||
/// - When URI is invalid format or missing params | ||
/// - When topic is already in use | ||
public func pair(uri: WalletConnectURI) async throws { | ||
try await walletPairService.pair(uri) | ||
} | ||
|
||
public func create() async throws -> WalletConnectURI { | ||
return try await appPairService.create() | ||
} | ||
|
||
public func register(method: ProtocolMethod) { | ||
pairingRequestsSubscriber.subscribeForRequest(method) | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import Foundation | ||
import WalletConnectRelay | ||
import WalletConnectUtils | ||
import WalletConnectKMS | ||
import WalletConnectNetworking | ||
|
||
public struct PairingClientFactory { | ||
|
||
public static func create(relayClient: RelayClient) -> PairingClient { | ||
let logger = ConsoleLogger(loggingLevel: .off) | ||
let keyValueStorage = UserDefaults.standard | ||
let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk") | ||
return PairingClientFactory.create(logger: logger, keyValueStorage: keyValueStorage, keychainStorage: keychainStorage, relayClient: relayClient) | ||
} | ||
|
||
static func create(logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, relayClient: RelayClient) -> PairingClient { | ||
let kms = KeyManagementService(keychain: keychainStorage) | ||
let serializer = Serializer(kms: kms) | ||
let history = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage) | ||
let networkingInteractor = NetworkingInteractor(relayClient: relayClient, serializer: serializer, logger: logger, rpcHistory: history) | ||
let pairingStore = PairingStorage(storage: SequenceStore<WCPairing>(store: .init(defaults: keyValueStorage, identifier: ""))) | ||
let appPairService = AppPairService(networkingInteractor: networkingInteractor, kms: kms, pairingStorage: pairingStore) | ||
let walletPairService = WalletPairService(networkingInteractor: networkingInteractor, kms: kms, pairingStorage: pairingStore) | ||
let pairingRequestsSubscriber = PairingRequestsSubscriber(networkingInteractor: networkingInteractor, pairingStorage: pairingStore, logger: logger) | ||
|
||
return PairingClient( | ||
appPairService: appPairService, | ||
networkingInteractor: networkingInteractor, | ||
logger: logger, | ||
walletPairService: walletPairService, | ||
pairingRequestsSubscriber: pairingRequestsSubscriber, | ||
socketConnectionStatusPublisher: relayClient.socketConnectionStatusPublisher | ||
) | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import Foundation | ||
import WalletConnectNetworking | ||
|
||
public protocol PairingRegisterer { | ||
func register(method: ProtocolMethod) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import Foundation | ||
import Combine | ||
import JSONRPC | ||
import WalletConnectUtils | ||
import WalletConnectKMS | ||
import WalletConnectNetworking | ||
|
||
|
||
public class PushProposer { | ||
private let networkingInteractor: NetworkInteracting | ||
private let kms: KeyManagementServiceProtocol | ||
private let logger: ConsoleLogging | ||
|
||
init(networkingInteractor: NetworkInteracting, | ||
kms: KeyManagementServiceProtocol, | ||
logger: ConsoleLogging) { | ||
self.networkingInteractor = networkingInteractor | ||
self.kms = kms | ||
self.logger = logger | ||
} | ||
|
||
func request(topic: String, params: AnyCodable) async throws { | ||
let protocolMethod = PushProposeProtocolMethod() | ||
let request = RPCRequest(method: protocolMethod.method, params: params) | ||
try await networkingInteractor.requestNetworkAck(request, topic: topic, protocolMethod: protocolMethod) | ||
} | ||
} |
36 changes: 36 additions & 0 deletions
36
Sources/WalletConnectPairing/PairingRequestsSubscriber.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
import Foundation | ||
import Combine | ||
import WalletConnectUtils | ||
import WalletConnectNetworking | ||
|
||
public class PairingRequestsSubscriber { | ||
private let networkingInteractor: NetworkInteracting | ||
private let pairingStorage: PairingStorage | ||
private var publishers = Set<AnyCancellable>() | ||
|
||
init(networkingInteractor: NetworkInteracting, pairingStorage: PairingStorage, logger: ConsoleLogging) { | ||
self.networkingInteractor = networkingInteractor | ||
self.pairingStorage = pairingStorage | ||
} | ||
|
||
func subscribeForRequest(_ protocolMethod: ProtocolMethod) { | ||
networkingInteractor.requestPublisher | ||
// Pairing requests only | ||
.filter { [unowned self] payload in | ||
return pairingStorage.hasPairing(forTopic: payload.topic) | ||
} | ||
// Wrong method | ||
.filter { payload in | ||
return payload.request.method != protocolMethod.method | ||
} | ||
// Respond error | ||
.sink { [unowned self] topic, request in | ||
Task(priority: .high) { | ||
// TODO - spec tag | ||
try await networkingInteractor.respondError(topic: topic, requestId: request.id!, protocolMethod: protocolMethod, reason: PairError.methodUnsupported) | ||
} | ||
|
||
}.store(in: &publishers) | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import Foundation | ||
import JSONRPC | ||
import Combine | ||
import WalletConnectKMS | ||
import WalletConnectUtils | ||
import WalletConnectNetworking | ||
|
||
public class PushClient { | ||
|
||
private var publishers = Set<AnyCancellable>() | ||
|
||
let requestPublisherSubject = PassthroughSubject<(topic: String, params: PushRequestParams), Never>() | ||
|
||
var proposalPublisher: AnyPublisher<(topic: String, params: PushRequestParams), Never> { | ||
requestPublisherSubject.eraseToAnyPublisher() | ||
} | ||
|
||
public let logger: ConsoleLogging | ||
|
||
private let pushProposer: PushProposer | ||
private let networkInteractor: NetworkInteracting | ||
private let pairingRegisterer: PairingRegisterer | ||
|
||
init(networkInteractor: NetworkInteracting, | ||
logger: ConsoleLogging, | ||
kms: KeyManagementServiceProtocol, | ||
pushProposer: PushProposer, | ||
pairingRegisterer: PairingRegisterer) { | ||
self.networkInteractor = networkInteractor | ||
self.logger = logger | ||
self.pushProposer = pushProposer | ||
self.pairingRegisterer = pairingRegisterer | ||
|
||
setupPairingSubscriptions() | ||
} | ||
|
||
public func propose(topic: String) async throws { | ||
try await pushProposer.request(topic: topic, params: AnyCodable(PushRequestParams())) | ||
} | ||
} | ||
|
||
private extension PushClient { | ||
|
||
func setupPairingSubscriptions() { | ||
let protocolMethod = PushProposeProtocolMethod() | ||
|
||
pairingRegisterer.register(method: protocolMethod) | ||
|
||
networkInteractor.responseErrorSubscription(on: protocolMethod) | ||
.sink { [unowned self] (payload: ResponseSubscriptionErrorPayload<PushRequestParams>) in | ||
logger.error(payload.error.localizedDescription) | ||
}.store(in: &publishers) | ||
|
||
networkInteractor.requestSubscription(on: protocolMethod) | ||
.sink { [unowned self] (payload: RequestSubscriptionPayload<PushRequestParams>) in | ||
requestPublisherSubject.send((payload.topic, payload.request)) | ||
}.store(in: &publishers) | ||
} | ||
} |
Oops, something went wrong.