diff --git a/Example/IntegrationTests/Auth/AuthTests.swift b/Example/IntegrationTests/Auth/AuthTests.swift index 90f110912..5ab58d0ad 100644 --- a/Example/IntegrationTests/Auth/AuthTests.swift +++ b/Example/IntegrationTests/Auth/AuthTests.swift @@ -76,7 +76,7 @@ final class AuthTests: XCTestCase { let uri = try! await appPairingClient.create() try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic) - try! await walletPairingClient.pair(uri: uri) + try? await walletPairingClient.pair(uri: uri) walletAuthClient.authRequestPublisher.sink { _ in requestExpectation.fulfill() }.store(in: &publishers) @@ -88,7 +88,7 @@ final class AuthTests: XCTestCase { let uri = try! await appPairingClient.create() try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic) - try! await walletPairingClient.pair(uri: uri) + try? await walletPairingClient.pair(uri: uri) walletAuthClient.authRequestPublisher.sink { [unowned self] request in Task(priority: .high) { let signerFactory = DefaultSignerFactory() @@ -126,7 +126,7 @@ final class AuthTests: XCTestCase { resources: nil ), topic: uri.topic) - try! await walletPairingClient.pair(uri: uri) + try? await walletPairingClient.pair(uri: uri) walletAuthClient.authRequestPublisher.sink { [unowned self] request in Task(priority: .high) { let signature = CacaoSignature(t: .eip1271, s: eip1271Signature) @@ -147,7 +147,7 @@ final class AuthTests: XCTestCase { let uri = try! await appPairingClient.create() try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic) - try! await walletPairingClient.pair(uri: uri) + try? await walletPairingClient.pair(uri: uri) walletAuthClient.authRequestPublisher.sink { [unowned self] request in Task(priority: .high) { let signature = CacaoSignature(t: .eip1271, s: eip1271Signature) @@ -168,7 +168,7 @@ final class AuthTests: XCTestCase { let uri = try! await appPairingClient.create() try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic) - try! await walletPairingClient.pair(uri: uri) + try? await walletPairingClient.pair(uri: uri) walletAuthClient.authRequestPublisher.sink { [unowned self] request in Task(priority: .high) { try! await walletAuthClient.reject(requestId: request.0.id) @@ -189,7 +189,7 @@ final class AuthTests: XCTestCase { let uri = try! await appPairingClient.create() try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic) - try! await walletPairingClient.pair(uri: uri) + try? await walletPairingClient.pair(uri: uri) walletAuthClient.authRequestPublisher.sink { [unowned self] request in Task(priority: .high) { let invalidSignature = "438effc459956b57fcd9f3dac6c675f9cee88abf21acab7305e8e32aa0303a883b06dcbd956279a7a2ca21ffa882ff55cc22e8ab8ec0f3fe90ab45f306938cfa1b" diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift index 6d8005a6c..a2b49e07b 100644 --- a/Example/IntegrationTests/Pairing/PairingTests.swift +++ b/Example/IntegrationTests/Pairing/PairingTests.swift @@ -110,7 +110,7 @@ final class PairingTests: XCTestCase { let expectation = expectation(description: "expects ping response") makeWalletClients() let uri = try! await appPairingClient.create() - try! await walletPairingClient.pair(uri: uri) + try? await walletPairingClient.pair(uri: uri) try! await walletPairingClient.ping(topic: uri.topic) walletPairingClient.pingResponsePublisher .sink { topic in @@ -131,7 +131,7 @@ final class PairingTests: XCTestCase { let uri = try! await appPairingClient.create() - try! await walletPairingClient.pair(uri: uri) + try? await walletPairingClient.pair(uri: uri) try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic) diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift index 226f5b80d..0216a3db6 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift @@ -20,18 +20,14 @@ final class SessionProposalInteractor { let supportedChains = [Blockchain("eip155:1")!, Blockchain("eip155:137")!] let supportedAccounts = [Account(blockchain: Blockchain("eip155:1")!, address: ETHSigner.address)!, Account(blockchain: Blockchain("eip155:137")!, address: ETHSigner.address)!] */ - do { - let sessionNamespaces = try AutoNamespaces.build( - sessionProposal: proposal, - chains: Array(supportedChains), - methods: Array(supportedMethods), - events: Array(supportedEvents), - accounts: supportedAccounts - ) - try await Web3Wallet.instance.approve(proposalId: proposal.id, namespaces: sessionNamespaces, sessionProperties: proposal.sessionProperties) - } catch { - print(error) - } + let sessionNamespaces = try AutoNamespaces.build( + sessionProposal: proposal, + chains: Array(supportedChains), + methods: Array(supportedMethods), + events: Array(supportedEvents), + accounts: supportedAccounts + ) + try await Web3Wallet.instance.approve(proposalId: proposal.id, namespaces: sessionNamespaces, sessionProperties: proposal.sessionProperties) } func reject(proposal: Session.Proposal) async throws { diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift index 117db225c..ad02dd7a4 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalPresenter.swift @@ -11,6 +11,9 @@ final class SessionProposalPresenter: ObservableObject { let sessionProposal: Session.Proposal let verified: Bool? + @Published var showError = false + @Published var errorMessage = "Error" + private var disposeBag = Set() init( @@ -30,14 +33,24 @@ final class SessionProposalPresenter: ObservableObject { @MainActor func onApprove() async throws { - try await interactor.approve(proposal: sessionProposal, account: importAccount.account) - router.dismiss() + do { + try await interactor.approve(proposal: sessionProposal, account: importAccount.account) + router.dismiss() + } catch { + errorMessage = error.localizedDescription + showError.toggle() + } } @MainActor func onReject() async throws { - try await interactor.reject(proposal: sessionProposal) - router.dismiss() + do { + try await interactor.reject(proposal: sessionProposal) + router.dismiss() + } catch { + errorMessage = error.localizedDescription + showError.toggle() + } } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift index 97717d9b5..8082ce1ee 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalView.swift @@ -146,6 +146,9 @@ struct SessionProposalView: View { Spacer() } } + .alert(presenter.errorMessage, isPresented: $presenter.showError) { + Button("OK", role: .cancel) {} + } .edgesIgnoringSafeArea(.all) } //private func sessionProposalView(chain: String) -> some View { diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift index 917d1dc94..c8a5baee1 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletPresenter.swift @@ -40,12 +40,7 @@ final class WalletPresenter: ObservableObject { func onAppear() { showPairingLoading = app.requestSent removePairingIndicator() - - let proposals = interactor.getPendingProposals() - if let proposal = proposals.last { - router.present(sessionProposal: proposal.proposal, importAccount: importAccount, sessionContext: proposal.context) - } - + let pendingRequests = interactor.getPendingRequests() if let request = pendingRequests.first(where: { $0.context != nil }) { router.present(sessionRequest: request.request, importAccount: importAccount, sessionContext: request.context) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Web3Inbox/Web3InboxViewController.swift b/Example/WalletApp/PresentationLayer/Wallet/Web3Inbox/Web3InboxViewController.swift index 7868a8274..d79c8b4ea 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Web3Inbox/Web3InboxViewController.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Web3Inbox/Web3InboxViewController.swift @@ -31,7 +31,7 @@ final class Web3InboxViewController: UIViewController { } @objc func refreshTapped() { - webView?.reload() + Web3Inbox.instance.reload() } @objc func getUrlPressed(_ sender: UIBarItem) { diff --git a/Sources/Auth/AuthClientFactory.swift b/Sources/Auth/AuthClientFactory.swift index 1faf925ff..dbddf2a53 100644 --- a/Sources/Auth/AuthClientFactory.swift +++ b/Sources/Auth/AuthClientFactory.swift @@ -48,7 +48,7 @@ public struct AuthClientFactory { let appRespondSubscriber = AppRespondSubscriber(networkingInteractor: networkingClient, logger: logger, rpcHistory: history, signatureVerifier: signatureVerifier, pairingRegisterer: pairingRegisterer, messageFormatter: messageFormatter) let walletErrorResponder = WalletErrorResponder(networkingInteractor: networkingClient, logger: logger, kms: kms, rpcHistory: history) let walletRequestSubscriber = WalletRequestSubscriber(networkingInteractor: networkingClient, logger: logger, kms: kms, walletErrorResponder: walletErrorResponder, pairingRegisterer: pairingRegisterer, verifyClient: verifyClient, verifyContextStore: verifyContextStore) - let walletRespondService = WalletRespondService(networkingInteractor: networkingClient, logger: logger, kms: kms, rpcHistory: history, verifyContextStore: verifyContextStore, walletErrorResponder: walletErrorResponder) + let walletRespondService = WalletRespondService(networkingInteractor: networkingClient, logger: logger, kms: kms, rpcHistory: history, verifyContextStore: verifyContextStore, walletErrorResponder: walletErrorResponder, pairingRegisterer: pairingRegisterer) let pendingRequestsProvider = PendingRequestsProvider(rpcHistory: history, verifyContextStore: verifyContextStore) return AuthClient( diff --git a/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift b/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift index 02ee542d9..c2a7030c0 100644 --- a/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift +++ b/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift @@ -37,10 +37,7 @@ class WalletRequestSubscriber { .sink { [unowned self] (payload: RequestSubscriptionPayload) in logger.debug("WalletRequestSubscriber: Received request") - pairingRegisterer.activate( - pairingTopic: payload.topic, - peerMetadata: payload.request.requester.metadata - ) + pairingRegisterer.setReceived(pairingTopic: payload.topic) let request = AuthRequest(id: payload.id, topic: payload.topic, payload: payload.request.payloadParams) diff --git a/Sources/Auth/Services/Wallet/WalletRespondService.swift b/Sources/Auth/Services/Wallet/WalletRespondService.swift index 8d5e78b0c..a06a16027 100644 --- a/Sources/Auth/Services/Wallet/WalletRespondService.swift +++ b/Sources/Auth/Services/Wallet/WalletRespondService.swift @@ -11,19 +11,24 @@ actor WalletRespondService { private let verifyContextStore: CodableStore private let logger: ConsoleLogging private let walletErrorResponder: WalletErrorResponder + private let pairingRegisterer: PairingRegisterer - init(networkingInteractor: NetworkInteracting, - logger: ConsoleLogging, - kms: KeyManagementService, - rpcHistory: RPCHistory, - verifyContextStore: CodableStore, - walletErrorResponder: WalletErrorResponder) { + init( + networkingInteractor: NetworkInteracting, + logger: ConsoleLogging, + kms: KeyManagementService, + rpcHistory: RPCHistory, + verifyContextStore: CodableStore, + walletErrorResponder: WalletErrorResponder, + pairingRegisterer: PairingRegisterer + ) { self.networkingInteractor = networkingInteractor self.logger = logger self.kms = kms self.rpcHistory = rpcHistory self.verifyContextStore = verifyContextStore self.walletErrorResponder = walletErrorResponder + self.pairingRegisterer = pairingRegisterer } func respond(requestId: RPCID, signature: CacaoSignature, account: Account) async throws { @@ -34,10 +39,16 @@ actor WalletRespondService { let header = CacaoHeader(t: "eip4361") let payload = try authRequestParams.payloadParams.cacaoPayload(address: account.address) - let responseParams = AuthResponseParams(h: header, p: payload, s: signature) + let responseParams = AuthResponseParams(h: header, p: payload, s: signature) let response = RPCResponse(id: requestId, result: responseParams) try await networkingInteractor.respond(topic: topic, response: response, protocolMethod: AuthRequestProtocolMethod(), envelopeType: .type1(pubKey: keys.publicKey.rawRepresentation)) + + pairingRegisterer.activate( + pairingTopic: topic, + peerMetadata: authRequestParams.requester.metadata + ) + verifyContextStore.delete(forKey: requestId.string) } diff --git a/Sources/WalletConnectNetworking/NetworkInteracting.swift b/Sources/WalletConnectNetworking/NetworkInteracting.swift index 507e82603..54987272c 100644 --- a/Sources/WalletConnectNetworking/NetworkInteracting.swift +++ b/Sources/WalletConnectNetworking/NetworkInteracting.swift @@ -13,7 +13,8 @@ public protocol NetworkInteracting { func respond(topic: String, response: RPCResponse, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws func respondSuccess(topic: String, requestId: RPCID, protocolMethod: ProtocolMethod, envelopeType: Envelope.EnvelopeType) async throws func respondError(topic: String, requestId: RPCID, protocolMethod: ProtocolMethod, reason: Reason, envelopeType: Envelope.EnvelopeType) async throws - + func handleHistoryRequest(topic: String, request: RPCRequest) + func requestSubscription( on request: ProtocolMethod ) -> AnyPublisher, Never> diff --git a/Sources/WalletConnectNetworking/NetworkingInteractor.swift b/Sources/WalletConnectNetworking/NetworkingInteractor.swift index d216f9974..e92cfa3ea 100644 --- a/Sources/WalletConnectNetworking/NetworkingInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkingInteractor.swift @@ -162,6 +162,10 @@ public class NetworkingInteractor: NetworkInteracting { logger.debug("Networking Interactor - Received unknown object type from networking relay") } } + + public func handleHistoryRequest(topic: String, request: RPCRequest) { + requestPublisherSubject.send((topic, request, Data(), Date(), nil)) + } private func handleRequest(topic: String, request: RPCRequest, decryptedPayload: Data, publishedAt: Date, derivedTopic: String?) { do { diff --git a/Sources/WalletConnectNotify/Types/Payload/NotifyDeleteResponsePayload.swift b/Sources/WalletConnectNotify/Types/Payload/NotifyDeleteResponsePayload.swift index 338ff414e..83be1586c 100644 --- a/Sources/WalletConnectNotify/Types/Payload/NotifyDeleteResponsePayload.swift +++ b/Sources/WalletConnectNotify/Types/Payload/NotifyDeleteResponsePayload.swift @@ -7,8 +7,6 @@ struct NotifyDeleteResponsePayload: JWTClaimsCodable { let iat: UInt64 /// Timestamp when JWT must expire let exp: UInt64 - /// Key server URL - let ksu: String /// Description of action intent. Must be equal to `notify_delete_response` let act: String? @@ -38,25 +36,11 @@ struct NotifyDeleteResponsePayload: JWTClaimsCodable { } } - let keyserver: URL let selfPubKey: DIDKey let subscriptionHash: String let app: String - init( - keyserver: URL, - selfPubKey: DIDKey, - subscriptionHash: String, - app: String - ) { - self.keyserver = keyserver - self.selfPubKey = selfPubKey - self.subscriptionHash = subscriptionHash - self.app = app - } - init(claims: Claims) throws { - self.keyserver = try claims.ksu.asURL() self.selfPubKey = try DIDKey(did: claims.aud) self.subscriptionHash = claims.sub self.app = claims.app @@ -66,7 +50,6 @@ struct NotifyDeleteResponsePayload: JWTClaimsCodable { return Claims( iat: defaultIat(), exp: expiry(days: 1), - ksu: keyserver.absoluteString, act: Claims.action, iss: iss, aud: selfPubKey.did(variant: .ED25519), diff --git a/Sources/WalletConnectNotify/Types/Payload/NotifyMessagePayload.swift b/Sources/WalletConnectNotify/Types/Payload/NotifyMessagePayload.swift index e932f80fe..cf7b5687e 100644 --- a/Sources/WalletConnectNotify/Types/Payload/NotifyMessagePayload.swift +++ b/Sources/WalletConnectNotify/Types/Payload/NotifyMessagePayload.swift @@ -7,8 +7,6 @@ struct NotifyMessagePayload: JWTClaimsCodable { let iat: UInt64 /// Timestamp when JWT must expire let exp: UInt64 - /// Key server URL - let ksu: String /// Action intent (must be `notify_message`) let act: String? @@ -41,31 +39,13 @@ struct NotifyMessagePayload: JWTClaimsCodable { } let castServerPubKey: DIDKey - let keyserver: URL let account: Account let subscriptionId: String let app: String let message: NotifyMessage - init( - castServerPubKey: DIDKey, - keyserver: URL, - account: Account, - subscriptionId: String, - app: String, - message: NotifyMessage - ) { - self.castServerPubKey = castServerPubKey - self.keyserver = keyserver - self.account = account - self.subscriptionId = subscriptionId - self.app = app - self.message = message - } - init(claims: Claims) throws { self.castServerPubKey = try DIDKey(did: claims.iss) - self.keyserver = try claims.ksu.asURL() self.account = try DIDPKH(did: claims.aud).account self.subscriptionId = claims.sub self.app = claims.app @@ -76,7 +56,6 @@ struct NotifyMessagePayload: JWTClaimsCodable { return Claims( iat: defaultIat(), exp: expiry(days: 1), - ksu: keyserver.absoluteString, act: Claims.action, iss: castServerPubKey.multibase(variant: .ED25519), aud: account.did, diff --git a/Sources/WalletConnectNotify/Types/Payload/NotifySubscriptionResponsePayload.swift b/Sources/WalletConnectNotify/Types/Payload/NotifySubscriptionResponsePayload.swift index b330882c1..f972f345b 100644 --- a/Sources/WalletConnectNotify/Types/Payload/NotifySubscriptionResponsePayload.swift +++ b/Sources/WalletConnectNotify/Types/Payload/NotifySubscriptionResponsePayload.swift @@ -7,8 +7,6 @@ struct NotifySubscriptionResponsePayload: JWTClaimsCodable { let iat: UInt64 /// timestamp when jwt must expire let exp: UInt64 - /// Key server URL - let ksu: String /// Description of action intent. Must be equal to "notify_subscription_response" let act: String? @@ -38,13 +36,11 @@ struct NotifySubscriptionResponsePayload: JWTClaimsCodable { } } - let keyserver: URL let selfPubKey: DIDKey let publicKey: DIDKey let app: String init(claims: Claims) throws { - self.keyserver = try claims.ksu.asURL() self.selfPubKey = try DIDKey(did: claims.aud) self.publicKey = try DIDKey(did: claims.sub) self.app = claims.app @@ -54,7 +50,6 @@ struct NotifySubscriptionResponsePayload: JWTClaimsCodable { return Claims( iat: defaultIat(), exp: expiry(days: 1), - ksu: keyserver.absoluteString, act: Claims.action, iss: iss, aud: selfPubKey.did(variant: .ED25519), diff --git a/Sources/WalletConnectNotify/Types/Payload/NotifyUpdateResponsePayload.swift b/Sources/WalletConnectNotify/Types/Payload/NotifyUpdateResponsePayload.swift index db607ffea..2153c7f9b 100644 --- a/Sources/WalletConnectNotify/Types/Payload/NotifyUpdateResponsePayload.swift +++ b/Sources/WalletConnectNotify/Types/Payload/NotifyUpdateResponsePayload.swift @@ -7,8 +7,6 @@ struct NotifyUpdateResponsePayload: JWTClaimsCodable { let iat: UInt64 /// timestamp when jwt must expire let exp: UInt64 - /// Key server URL - let ksu: String /// Description of action intent. Must be equal to "notify_update_response" let act: String? @@ -38,13 +36,11 @@ struct NotifyUpdateResponsePayload: JWTClaimsCodable { } } - let keyserver: URL let selfPubKey: DIDKey let subscriptionHash: String let app: String init(claims: Claims) throws { - self.keyserver = try claims.ksu.asURL() self.selfPubKey = try DIDKey(did: claims.aud) self.subscriptionHash = claims.sub self.app = claims.app @@ -54,7 +50,6 @@ struct NotifyUpdateResponsePayload: JWTClaimsCodable { return Claims( iat: defaultIat(), exp: expiry(days: 1), - ksu: keyserver.absoluteString, act: Claims.action, iss: iss, aud: selfPubKey.did(variant: .ED25519), diff --git a/Sources/WalletConnectPairing/PairingClientFactory.swift b/Sources/WalletConnectPairing/PairingClientFactory.swift index 9706702af..ba49bb6af 100644 --- a/Sources/WalletConnectPairing/PairingClientFactory.swift +++ b/Sources/WalletConnectPairing/PairingClientFactory.swift @@ -12,8 +12,9 @@ public struct PairingClientFactory { public static func create(logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, networkingClient: NetworkingInteractor) -> PairingClient { let pairingStore = PairingStorage(storage: SequenceStore(store: .init(defaults: keyValueStorage, identifier: PairStorageIdentifiers.pairings.rawValue))) let kms = KeyManagementService(keychain: keychainStorage) + let history = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage) let appPairService = AppPairService(networkingInteractor: networkingClient, kms: kms, pairingStorage: pairingStore) - let walletPairService = WalletPairService(networkingInteractor: networkingClient, kms: kms, pairingStorage: pairingStore) + let walletPairService = WalletPairService(networkingInteractor: networkingClient, kms: kms, pairingStorage: pairingStore, history: history) let pairingRequestsSubscriber = PairingRequestsSubscriber(networkingInteractor: networkingClient, pairingStorage: pairingStore, logger: logger) let pairingsProvider = PairingsProvider(pairingStorage: pairingStore) let cleanupService = PairingCleanupService(pairingStore: pairingStore, kms: kms) diff --git a/Sources/WalletConnectPairing/Services/Wallet/WalletPairService.swift b/Sources/WalletConnectPairing/Services/Wallet/WalletPairService.swift index d14d82e3c..c962b8a41 100644 --- a/Sources/WalletConnectPairing/Services/Wallet/WalletPairService.swift +++ b/Sources/WalletConnectPairing/Services/Wallet/WalletPairService.swift @@ -9,18 +9,23 @@ actor WalletPairService { let networkingInteractor: NetworkInteracting let kms: KeyManagementServiceProtocol private let pairingStorage: WCPairingStorage + private let history: RPCHistory - init(networkingInteractor: NetworkInteracting, - kms: KeyManagementServiceProtocol, - pairingStorage: WCPairingStorage) { + init( + networkingInteractor: NetworkInteracting, + kms: KeyManagementServiceProtocol, + pairingStorage: WCPairingStorage, + history: RPCHistory + ) { self.networkingInteractor = networkingInteractor self.kms = kms self.pairingStorage = pairingStorage + self.history = history } func pair(_ uri: WalletConnectURI) async throws { - guard !hasPairing(for: uri.topic) else { - throw Errors.pairingAlreadyExist(topic: uri.topic) + guard try !pairingHasPendingRequest(for: uri.topic) else { + return } let pairing = WCPairing(uri: uri) @@ -39,9 +44,23 @@ actor WalletPairService { // MARK: - Private functions extension WalletPairService { - func hasPairing(for topic: String) -> Bool { - if let pairing = pairingStorage.getPairing(forTopic: topic) { - return pairing.requestReceived + func pairingHasPendingRequest(for topic: String) throws -> Bool { + guard let pairing = pairingStorage.getPairing(forTopic: topic), pairing.requestReceived else { + return false + } + + if pairing.active { + throw Errors.pairingAlreadyExist(topic: topic) + } + + let pendingRequests = history.getPending() + .compactMap { record -> RPCRequest? in + (record.topic == pairing.topic) ? record.request : nil + } + + if let pendingRequest = pendingRequests.first { + networkingInteractor.handleHistoryRequest(topic: topic, request: pendingRequest) + return true } return false } @@ -65,7 +84,7 @@ extension WalletPairService { extension WalletPairService.Errors: LocalizedError { var errorDescription: String? { switch self { - case .pairingAlreadyExist(let topic): return "Pairing with topic (\(topic)) already exists. Use 'Web3Wallet.instance.getPendingProposals()' or 'Web3Wallet.instance.getPendingRequests()' in order to receive proposals or requests." + case .pairingAlreadyExist(let topic): return "Pairing with topic (\(topic)) is already active" case .networkNotConnected: return "Pairing failed. You seem to be offline" } } diff --git a/Sources/WalletConnectRelay/PackageConfig.json b/Sources/WalletConnectRelay/PackageConfig.json index b0a94210e..e32e2e114 100644 --- a/Sources/WalletConnectRelay/PackageConfig.json +++ b/Sources/WalletConnectRelay/PackageConfig.json @@ -1 +1 @@ -{"version": "1.7.1"} +{"version": "1.7.2"} diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index 3071e12e6..dd77da949 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -9,6 +9,7 @@ final class ApproveEngine { case pairingNotFound case sessionNotFound case agreementMissingOrInvalid + case networkNotConnected } var onSessionProposal: ((Session.Proposal, VerifyContext?) -> Void)? @@ -63,6 +64,11 @@ final class ApproveEngine { guard let payload = try proposalPayloadsStore.get(key: proposerPubKey) else { throw Errors.wrongRequestParams } + + let networkConnectionStatus = await resolveNetworkConnectionStatus() + guard networkConnectionStatus == .connected else { + throw Errors.networkNotConnected + } let proposal = payload.request let pairingTopic = payload.topic @@ -300,6 +306,11 @@ private extension ApproveEngine { pairingRegisterer.setReceived(pairingTopic: payload.topic) + if let verifyContext = try? verifyContextStore.get(key: proposal.proposer.publicKey) { + onSessionProposal?(proposal.publicRepresentation(pairingTopic: payload.topic), verifyContext) + return + } + Task(priority: .high) { let assertionId = payload.decryptedPayload.sha256().toHexString() do { @@ -373,4 +384,28 @@ private extension ApproveEngine { } onSessionSettle?(session.publicRepresentation()) } + + func resolveNetworkConnectionStatus() async -> NetworkConnectionStatus { + return await withCheckedContinuation { continuation in + let cancellable = networkingInteractor.networkConnectionStatusPublisher.sink { value in + continuation.resume(returning: value) + } + + Task(priority: .high) { + await withTaskCancellationHandler { + cancellable.cancel() + } onCancel: { } + } + } + } +} + +// MARK: - LocalizedError +extension ApproveEngine.Errors: LocalizedError { + var errorDescription: String? { + switch self { + case .networkNotConnected: return "Action failed. You seem to be offline" + default: return "" + } + } } diff --git a/Sources/Web3Inbox/Web3InboxClient.swift b/Sources/Web3Inbox/Web3InboxClient.swift index d6277cb4e..7475cd74e 100644 --- a/Sources/Web3Inbox/Web3InboxClient.swift +++ b/Sources/Web3Inbox/Web3InboxClient.swift @@ -74,6 +74,10 @@ public final class Web3InboxClient { public func register(deviceToken: Data) async throws { try await notifyClient.register(deviceToken: deviceToken) } + + public func reload() { + webviewSubscriber.reload(webView) + } } // MARK: - Privates diff --git a/Sources/Web3Inbox/WebView/WebViewRequestSubscriber.swift b/Sources/Web3Inbox/WebView/WebViewRequestSubscriber.swift index 4add0cd58..49fc5a151 100644 --- a/Sources/Web3Inbox/WebView/WebViewRequestSubscriber.swift +++ b/Sources/Web3Inbox/WebView/WebViewRequestSubscriber.swift @@ -45,6 +45,10 @@ final class WebViewRequestSubscriber: NSObject, WKScriptMessageHandler { } } } + + func reload(_ webView: WKWebView) { + webView.load(URLRequest(url: url)) + } } extension WebViewRequestSubscriber: WKUIDelegate { @@ -55,7 +59,7 @@ extension WebViewRequestSubscriber: WKUIDelegate { func webView(_ webView: WKWebView, requestMediaCapturePermissionFor origin: WKSecurityOrigin, initiatedByFrame frame: WKFrameInfo, type: WKMediaCaptureType, decisionHandler: @escaping (WKPermissionDecision) -> Void) { decisionHandler(.grant) } - + #endif } @@ -63,17 +67,10 @@ extension WebViewRequestSubscriber: WKNavigationDelegate { func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { - guard - let from = webView.url, - let to = navigationAction.request.url - else { return decisionHandler(.cancel) } - - if from.absoluteString.contains("/login") || to.absoluteString.contains("/login") { - decisionHandler(.cancel) - webView.load(URLRequest(url: url)) - } else { + if navigationAction.request.url == url { decisionHandler(.allow) + } else { + decisionHandler(.cancel) } } } - diff --git a/Tests/AuthTests/WalletRequestSubscriberTests.swift b/Tests/AuthTests/WalletRequestSubscriberTests.swift index 2508ae587..1fba88977 100644 --- a/Tests/AuthTests/WalletRequestSubscriberTests.swift +++ b/Tests/AuthTests/WalletRequestSubscriberTests.swift @@ -57,7 +57,7 @@ class WalletRequestSubscriberTests: XCTestCase { pairingRegisterer.subject.send(payload) wait(for: [messageExpectation], timeout: defaultTimeout) - XCTAssertTrue(pairingRegisterer.isActivateCalled) + XCTAssertTrue(pairingRegisterer.isReceivedCalled) XCTAssertEqual(requestPayload, expectedPayload) XCTAssertEqual(requestId, expectedRequestId) } diff --git a/Tests/TestingUtils/Mocks/PairingRegistererMock.swift b/Tests/TestingUtils/Mocks/PairingRegistererMock.swift index 6aa18183f..3c21567c6 100644 --- a/Tests/TestingUtils/Mocks/PairingRegistererMock.swift +++ b/Tests/TestingUtils/Mocks/PairingRegistererMock.swift @@ -7,6 +7,7 @@ public class PairingRegistererMock: PairingRegisterer where Reque public let subject = PassthroughSubject, Never>() public var isActivateCalled: Bool = false + public var isReceivedCalled: Bool = false public func register(method: ProtocolMethod) -> AnyPublisher, Never> where RequestParams: Decodable, RequestParams: Encodable { subject.eraseToAnyPublisher() as! AnyPublisher, Never> @@ -21,6 +22,6 @@ public class PairingRegistererMock: PairingRegisterer where Reque } public func setReceived(pairingTopic: String) { - + isReceivedCalled = true } } diff --git a/Tests/TestingUtils/NetworkingInteractorMock.swift b/Tests/TestingUtils/NetworkingInteractorMock.swift index 175e6aed0..8764e7675 100644 --- a/Tests/TestingUtils/NetworkingInteractorMock.swift +++ b/Tests/TestingUtils/NetworkingInteractorMock.swift @@ -15,6 +15,7 @@ public class NetworkingInteractorMock: NetworkInteracting { private(set) var didRespondError = false private(set) var didCallSubscribe = false private(set) var didCallUnsubscribe = false + private(set) var didCallHandleHistoryRequest = false private(set) var didRespondOnTopic: String? private(set) var lastErrorCode = -1 @@ -90,6 +91,10 @@ public class NetworkingInteractorMock: NetworkInteracting { subscriptions.append(topic) didCallSubscribe = true } + + public func handleHistoryRequest(topic: String, request: JSONRPC.RPCRequest) { + didCallHandleHistoryRequest = true + } func didSubscribe(to topic: String) -> Bool { subscriptions.contains { $0 == topic } diff --git a/Tests/WalletConnectPairingTests/WalletPairServiceTests.swift b/Tests/WalletConnectPairingTests/WalletPairServiceTests.swift index facccf6b4..4f88e9c94 100644 --- a/Tests/WalletConnectPairingTests/WalletPairServiceTests.swift +++ b/Tests/WalletConnectPairingTests/WalletPairServiceTests.swift @@ -11,12 +11,14 @@ final class WalletPairServiceTestsTests: XCTestCase { var networkingInteractor: NetworkingInteractorMock! var storageMock: WCPairingStorageMock! var cryptoMock: KeyManagementServiceMock! + var rpcHistory: RPCHistory! override func setUp() { networkingInteractor = NetworkingInteractorMock() storageMock = WCPairingStorageMock() cryptoMock = KeyManagementServiceMock() - service = WalletPairService(networkingInteractor: networkingInteractor, kms: cryptoMock, pairingStorage: storageMock) + rpcHistory = RPCHistoryFactory.createForNetwork(keyValueStorage: RuntimeKeyValueStorage()) + service = WalletPairService(networkingInteractor: networkingInteractor, kms: cryptoMock, pairingStorage: storageMock, history: rpcHistory) } func testPairWhenNetworkNotConnectedThrows() async { @@ -25,13 +27,18 @@ final class WalletPairServiceTestsTests: XCTestCase { await XCTAssertThrowsErrorAsync(try await service.pair(uri)) } - func testPairOnSameURIWhenRequestReceivedThrows() async { + func testPairOnSameUriPresentsRequest() async { + let rpcRequest = RPCRequest(method: "session_propose", id: 1234) + let uri = WalletConnectURI.stub() try! await service.pair(uri) var pairing = storageMock.getPairing(forTopic: uri.topic) pairing?.receivedRequest() storageMock.setPairing(pairing!) - await XCTAssertThrowsErrorAsync(try await service.pair(uri)) + try! rpcHistory.set(rpcRequest, forTopic: uri.topic, emmitedBy: .local) + + try! await service.pair(uri) + XCTAssertTrue(networkingInteractor.didCallHandleHistoryRequest) } func testPair() async {