From c27cb3f8ffb55b3e3f41c6b02df7ee3f2d9b2acc Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 18 May 2023 07:53:38 +0200 Subject: [PATCH 01/33] savepoint --- .../wc_notifyPropose/NotifyProposer.swift | 37 +++++++++++++++++++ .../ProtocolMethods/File.swift | 12 ++++++ .../RPCRequests/NotifyProposeParams.swift | 9 +++++ 3 files changed, 58 insertions(+) create mode 100644 Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposer.swift create mode 100644 Sources/WalletConnectPush/ProtocolMethods/File.swift create mode 100644 Sources/WalletConnectPush/RPCRequests/NotifyProposeParams.swift diff --git a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposer.swift b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposer.swift new file mode 100644 index 000000000..b657f0d99 --- /dev/null +++ b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposer.swift @@ -0,0 +1,37 @@ + +import Foundation + +class NotifyProposer { + private let networkingInteractor: NetworkInteracting + private let kms: KeyManagementService + private let logger: ConsoleLogging + private let metadata: AppMetadata + + init(networkingInteractor: NetworkInteracting, + kms: KeyManagementService, + appMetadata: AppMetadata, + logger: ConsoleLogging) { + self.networkingInteractor = networkingInteractor + self.kms = kms + self.metadata = appMetadata + self.logger = logger + } + + func propose(topic: String, account: Account) async throws { + logger.debug("NotifyProposer: Sending Notify Proposal") + let protocolMethod = NotifyProposeProtocolMethod() + let publicKey = try kms.createX25519KeyPair() + let responseTopic = publicKey.rawRepresentation.sha256().toHexString() + try kms.setPublicKey(publicKey: publicKey, for: responseTopic) + + + let scope = NotificationScope.allCases.map{$0.rawValue}.joined(separator: " ") + + + let params = NotifyProposeParams(publicKey: publicKey.hexRepresentation, metadata: metadata, account: account, scope: scope) + let request = RPCRequest(method: protocolMethod.method, params: params) + try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) + try await networkingInteractor.subscribe(topic: responseTopic) + } + +} diff --git a/Sources/WalletConnectPush/ProtocolMethods/File.swift b/Sources/WalletConnectPush/ProtocolMethods/File.swift new file mode 100644 index 000000000..4daef1e7d --- /dev/null +++ b/Sources/WalletConnectPush/ProtocolMethods/File.swift @@ -0,0 +1,12 @@ +// + +import Foundation + +struct NotifyProposeProtocolMethod: ProtocolMethod { + let method: String = "wc_notifyPropose" + + let requestConfig: RelayConfig = RelayConfig(tag: 4010, prompt: true, ttl: 86400) + + let responseConfig: RelayConfig = RelayConfig(tag: 4011, prompt: true, ttl: 86400) +} + diff --git a/Sources/WalletConnectPush/RPCRequests/NotifyProposeParams.swift b/Sources/WalletConnectPush/RPCRequests/NotifyProposeParams.swift new file mode 100644 index 000000000..9ca025876 --- /dev/null +++ b/Sources/WalletConnectPush/RPCRequests/NotifyProposeParams.swift @@ -0,0 +1,9 @@ + +import Foundation + +struct NotifyProposeParams: Codable { + let publicKey: String + let metadata: AppMetadata + let account: Account + let scope: String +} From ad284c564a1f97f9563108311431f2bc84b53b60 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Fri, 19 May 2023 14:35:13 +0200 Subject: [PATCH 02/33] NotifyProposeResponseSubscriber savepoint --- .../NotifyProposeResponseSubscriber.swift | 55 +++++++++++++++++++ .../ProtocolMethods/File.swift | 2 +- 2 files changed, 56 insertions(+), 1 deletion(-) create mode 100644 Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift diff --git a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift new file mode 100644 index 000000000..c150043d5 --- /dev/null +++ b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift @@ -0,0 +1,55 @@ + +import Foundation +import Combine + +class NotifyProposeResponseSubscriber { + private let networkingInteractor: NetworkInteracting + private let kms: KeyManagementServiceProtocol + private let logger: ConsoleLogging + var onResponse: ((_ id: RPCID, _ result: Result) -> Void)? + private var publishers = [AnyCancellable]() + + init(networkingInteractor: NetworkInteracting, + kms: KeyManagementServiceProtocol, + logger: ConsoleLogging) { + self.networkingInteractor = networkingInteractor + self.kms = kms + self.logger = logger + subscribeForProposalResponse() + } + + + private func subscribeForProposalResponse() { + let protocolMethod = NotifyProposeProtocolMethod() + networkingInteractor.responseSubscription(on: protocolMethod) + .sink { [unowned self] (payload: ResponseSubscriptionPayload) in + logger.debug("Received Notify Proposal response") + Task(priority: .userInitiated) { + do { + let pushSubscription = try await handleResponse(payload: payload) + onResponse?(payload.id, .success(result)) + } catch { + logger.error(error) + } + } + }.store(in: &publishers) + } + + + func handleResponse(payload: ResponseSubscriptionPayload) async throws -> PushSubscription { + let jwt = payload.response.jwtString + let (_, claims) = try SubscriptionJWTPayload.decodeAndVerify(from: payload.response) + logger.debug("subscriptionAuth JWT validated") + + let expiry = Date(timeIntervalSince1970: TimeInterval(claims.exp)) + + let updateTopic = jwt.data(using: .utf8)?.sha256().hexString + + + Subscription should have reference to update topic + let pushSubscription = PushSubscription(topic: subscriptionTopic, account: payload.request.account, relay: relay, metadata: metadata, scope: [:], expiry: expiry) + + } + + +} diff --git a/Sources/WalletConnectPush/ProtocolMethods/File.swift b/Sources/WalletConnectPush/ProtocolMethods/File.swift index 4daef1e7d..027293d19 100644 --- a/Sources/WalletConnectPush/ProtocolMethods/File.swift +++ b/Sources/WalletConnectPush/ProtocolMethods/File.swift @@ -3,7 +3,7 @@ import Foundation struct NotifyProposeProtocolMethod: ProtocolMethod { - let method: String = "wc_notifyPropose" + let method: String = "wc_pushPropose" let requestConfig: RelayConfig = RelayConfig(tag: 4010, prompt: true, ttl: 86400) From 7da99cfef62c40a9147fa28a48e2e98053a56038 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 22 May 2023 15:13:13 +0200 Subject: [PATCH 03/33] Update notify propose responder --- .../Web3Inbox/Web3InboxViewController.swift | 2 + .../NotifyProposeResponseSubscriber.swift | 23 +++++--- .../NotifyProposeResponder.swift | 57 +++++++++++++++++++ .../PushSubscribeRequester.swift | 15 +++-- .../Client/Wallet/WalletPushClient.swift | 5 ++ ...wift => NotifyProposeProtocolMethod.swift} | 0 6 files changed, 85 insertions(+), 17 deletions(-) create mode 100644 Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift rename Sources/WalletConnectPush/ProtocolMethods/{File.swift => NotifyProposeProtocolMethod.swift} (100%) diff --git a/Example/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxViewController.swift b/Example/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxViewController.swift index 5e1111b53..05aef8a73 100644 --- a/Example/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxViewController.swift +++ b/Example/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxViewController.swift @@ -30,9 +30,11 @@ final class Web3InboxViewController: UIViewController { private extension Web3InboxViewController { func onSing(_ message: String) -> SigningResult { + let privateKey = Data(hex: importAccount.privateKey) let signer = MessageSignerFactory(signerFactory: DefaultSignerFactory()).create() let signature = try! signer.sign(message: message, privateKey: privateKey, type: .eip191) + return .signed(signature) } } diff --git a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift index c150043d5..d20942288 100644 --- a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift +++ b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift @@ -4,17 +4,24 @@ import Combine class NotifyProposeResponseSubscriber { private let networkingInteractor: NetworkInteracting + private let metadata: AppMetadata private let kms: KeyManagementServiceProtocol private let logger: ConsoleLogging - var onResponse: ((_ id: RPCID, _ result: Result) -> Void)? + var proposalResponsePublisher: AnyPublisher, Never> { + proposalResponsePublisherSubject.eraseToAnyPublisher() + } + private let proposalResponsePublisherSubject = PassthroughSubject, Never>() + private var publishers = [AnyCancellable]() init(networkingInteractor: NetworkInteracting, kms: KeyManagementServiceProtocol, - logger: ConsoleLogging) { + logger: ConsoleLogging, + metadata: AppMetadata) { self.networkingInteractor = networkingInteractor self.kms = kms self.logger = logger + self.metadata = metadata subscribeForProposalResponse() } @@ -27,7 +34,7 @@ class NotifyProposeResponseSubscriber { Task(priority: .userInitiated) { do { let pushSubscription = try await handleResponse(payload: payload) - onResponse?(payload.id, .success(result)) + proposalResponsePublisherSubject.send(.success(pushSubscription)) } catch { logger.error(error) } @@ -35,20 +42,18 @@ class NotifyProposeResponseSubscriber { }.store(in: &publishers) } - + /// Implemented only for integration testing purpose, dapp client is not supported func handleResponse(payload: ResponseSubscriptionPayload) async throws -> PushSubscription { - let jwt = payload.response.jwtString let (_, claims) = try SubscriptionJWTPayload.decodeAndVerify(from: payload.response) logger.debug("subscriptionAuth JWT validated") let expiry = Date(timeIntervalSince1970: TimeInterval(claims.exp)) - let updateTopic = jwt.data(using: .utf8)?.sha256().hexString - + let updateTopic = "update_topic" - Subscription should have reference to update topic - let pushSubscription = PushSubscription(topic: subscriptionTopic, account: payload.request.account, relay: relay, metadata: metadata, scope: [:], expiry: expiry) + let relay = RelayProtocolOptions(protocol: "irn", data: nil) + return PushSubscription(topic: updateTopic, account: payload.request.account, relay: relay, metadata: metadata, scope: [:], expiry: expiry) } diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift new file mode 100644 index 000000000..7a8a4124f --- /dev/null +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift @@ -0,0 +1,57 @@ + +import Foundation +import Combine + +class NotifyProposeResponder { + enum Errors: Error { + case recordForIdNotFound + case malformedRequestParams + } + private let networkingInteractor: NetworkInteracting + private let kms: KeyManagementServiceProtocol + private let logger: ConsoleLogging + private let pushSubscribeRequester: PushSubscribeRequester + private let rpcHistory: RPCHistory + + private var publishers = [AnyCancellable]() + + init(networkingInteractor: NetworkInteracting, + kms: KeyManagementServiceProtocol, + logger: ConsoleLogging, + pushSubscribeRequester: PushSubscribeRequester, + rpcHistory: RPCHistory) { + self.networkingInteractor = networkingInteractor + self.kms = kms + self.logger = logger + self.pushSubscribeRequester = pushSubscribeRequester + self.rpcHistory = rpcHistory + } + + func respond(requestId: RPCID, onSign: @escaping SigningCallback) async throws { + + guard let requestRecord = rpcHistory.get(recordId: requestId) else { throw Errors.recordForIdNotFound } + let proposal = try requestRecord.request.params!.get(NotifyProposeParams.self) + + let subscriptionAuthWrapper = try await pushSubscribeRequester.subscribe(metadata: proposal.metadata, account: proposal.account, onSign: onSign) + + guard let peerPublicKey = try? AgreementPublicKey(hex: proposal.publicKey) else { + throw Errors.malformedRequestParams + } + + let responseTopic = peerPublicKey.rawRepresentation.sha256().toHexString() + + let keys = try generateAgreementKeys(peerPublicKey: peerPublicKey) + + let response = RPCResponse(id: requestId, result: subscriptionAuthWrapper) + + let protocolMethod = NotifyProposeProtocolMethod() + + try await networkingInteractor.respond(topic: responseTopic, response: response, protocolMethod: protocolMethod, envelopeType: .type1(pubKey: keys.publicKey.rawRepresentation)) + } + + private func generateAgreementKeys(peerPublicKey: AgreementPublicKey) throws -> AgreementKeys { + let selfPubKey = try kms.createX25519KeyPair() + let keys = try kms.performKeyAgreement(selfPublicKey: selfPubKey, peerPublicKey: peerPublicKey.hexRepresentation) + return keys + } +} diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift index 49217c554..ef97096bd 100644 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift @@ -34,7 +34,7 @@ class PushSubscribeRequester { self.dappsMetadataStore = dappsMetadataStore } - func subscribe(metadata: AppMetadata, account: Account, onSign: @escaping SigningCallback) async throws { + @discardableResult func subscribe(metadata: AppMetadata, account: Account, onSign: @escaping SigningCallback) async throws -> SubscriptionJWTPayload.Wrapper { let dappUrl = metadata.url @@ -56,16 +56,17 @@ class PushSubscribeRequester { try kms.setAgreementSecret(keysY, topic: responseTopic) logger.debug("setting symm key for response topic \(responseTopic)") - - let request = try createJWTRequest(subscriptionAccount: account, dappUrl: dappUrl) - let protocolMethod = PushSubscribeProtocolMethod() + let subscriptionAuthWrapper = try createJWTWrapper(subscriptionAccount: account, dappUrl: dappUrl) + let request = RPCRequest(method: protocolMethod.method, params: subscriptionAuthWrapper) + logger.debug("PushSubscribeRequester: subscribing to response topic: \(responseTopic)") try await networkingInteractor.subscribe(topic: responseTopic) try await networkingInteractor.request(request, topic: subscribeTopic, protocolMethod: protocolMethod, envelopeType: .type1(pubKey: keysY.publicKey.rawRepresentation)) + return subscriptionAuthWrapper } private func resolvePublicKey(dappUrl: String) async throws -> AgreementPublicKey { @@ -86,13 +87,11 @@ class PushSubscribeRequester { return keys } - private func createJWTRequest(subscriptionAccount: Account, dappUrl: String) throws -> RPCRequest { - let protocolMethod = PushSubscribeProtocolMethod().method + private func createJWTWrapper(subscriptionAccount: Account, dappUrl: String) throws -> SubscriptionJWTPayload.Wrapper { let jwtPayload = SubscriptionJWTPayload(keyserver: keyserverURL, subscriptionAccount: subscriptionAccount, dappUrl: dappUrl, scope: "") - let wrapper = try identityClient.signAndCreateWrapper( + return try identityClient.signAndCreateWrapper( payload: jwtPayload, account: subscriptionAccount ) - return RPCRequest(method: protocolMethod, params: wrapper) } } diff --git a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift index 2740cb252..dbf87fb0d 100644 --- a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift +++ b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift @@ -143,6 +143,11 @@ private extension WalletPushClient { requestPublisherSubject.send((id: payload.id, account: payload.request.account, metadata: payload.request.metadata)) }.store(in: &publishers) + pairingRegisterer.register(method: NotifyProposeProtocolMethod()) + .sink { [unowned self] (payload: RequestSubscriptionPayload) in + requestPublisherSubject.send((id: payload.id, account: payload.request.account, metadata: payload.request.metadata)) + }.store(in: &publishers) + pushMessageSubscriber.onPushMessage = { [unowned self] pushMessageRecord in pushMessagePublisherSubject.send(pushMessageRecord) } diff --git a/Sources/WalletConnectPush/ProtocolMethods/File.swift b/Sources/WalletConnectPush/ProtocolMethods/NotifyProposeProtocolMethod.swift similarity index 100% rename from Sources/WalletConnectPush/ProtocolMethods/File.swift rename to Sources/WalletConnectPush/ProtocolMethods/NotifyProposeProtocolMethod.swift From 8dc58d5682219b21971c043caf410b430b61f72c Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 22 May 2023 15:51:25 +0200 Subject: [PATCH 04/33] add dependencies to clients --- Example/IntegrationTests/Push/PushTests.swift | 4 ++++ .../Client/Dapp/DappPushClient.swift | 16 +++++++++++++++- .../Client/Dapp/DappPushClientFactory.swift | 6 +++++- .../Client/Wallet/WalletPushClient.swift | 5 +++++ 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/Example/IntegrationTests/Push/PushTests.swift b/Example/IntegrationTests/Push/PushTests.swift index 3be34d4f2..efb2a5f1b 100644 --- a/Example/IntegrationTests/Push/PushTests.swift +++ b/Example/IntegrationTests/Push/PushTests.swift @@ -127,6 +127,10 @@ final class PushTests: XCTestCase { wait(for: [expectation], timeout: InputConfig.defaultTimeout) } + func testPushPropose() async { + + } + func testWalletRejectsPushRequest() async { let expectation = expectation(description: "expects dapp to receive error response") diff --git a/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift b/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift index d267b68f1..0acdbe338 100644 --- a/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift +++ b/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift @@ -10,6 +10,10 @@ public class DappPushClient { responsePublisherSubject.eraseToAnyPublisher() } + var proposalResponsePublisher: AnyPublisher, Never> { + return notifyProposeResponseSubscriber.proposalResponsePublisher + } + private let deleteSubscriptionPublisherSubject = PassthroughSubject() public var deleteSubscriptionPublisher: AnyPublisher { @@ -19,12 +23,14 @@ public class DappPushClient { public let logger: ConsoleLogging private let pushProposer: PushProposer + private let notifyProposer: NotifyProposer private let pushMessageSender: PushMessageSender private let proposalResponseSubscriber: ProposalResponseSubscriber private let subscriptionsProvider: SubscriptionsProvider private let deletePushSubscriptionService: DeletePushSubscriptionService private let deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber private let resubscribeService: PushResubscribeService + private let notifyProposeResponseSubscriber: NotifyProposeResponseSubscriber init(logger: ConsoleLogging, kms: KeyManagementServiceProtocol, @@ -34,7 +40,9 @@ public class DappPushClient { subscriptionsProvider: SubscriptionsProvider, deletePushSubscriptionService: DeletePushSubscriptionService, deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber, - resubscribeService: PushResubscribeService) { + resubscribeService: PushResubscribeService, + notifyProposer: NotifyProposer, + notifyProposeResponseSubscriber: NotifyProposeResponseSubscriber) { self.logger = logger self.pushProposer = pushProposer self.proposalResponseSubscriber = proposalResponseSubscriber @@ -43,6 +51,8 @@ public class DappPushClient { self.deletePushSubscriptionService = deletePushSubscriptionService self.deletePushSubscriptionSubscriber = deletePushSubscriptionSubscriber self.resubscribeService = resubscribeService + self.notifyProposer = notifyProposer + self.notifyProposeResponseSubscriber = notifyProposeResponseSubscriber setupSubscriptions() } @@ -50,6 +60,10 @@ public class DappPushClient { try await pushProposer.request(topic: topic, account: account) } + public func propose(account: Account, topic: String) async throws { + try await notifyProposer.propose(topic: topic, account: account) + } + public func notify(topic: String, message: PushMessage) async throws { try await pushMessageSender.request(topic: topic, message: message) } diff --git a/Sources/WalletConnectPush/Client/Dapp/DappPushClientFactory.swift b/Sources/WalletConnectPush/Client/Dapp/DappPushClientFactory.swift index e43460388..6d4a506bc 100644 --- a/Sources/WalletConnectPush/Client/Dapp/DappPushClientFactory.swift +++ b/Sources/WalletConnectPush/Client/Dapp/DappPushClientFactory.swift @@ -26,6 +26,8 @@ public struct DappPushClientFactory { let deletePushSubscriptionService = DeletePushSubscriptionService(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushSubscriptionStore: subscriptionStore, pushMessagesDatabase: nil) let deletePushSubscriptionSubscriber = DeletePushSubscriptionSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushSubscriptionStore: subscriptionStore) let resubscribeService = PushResubscribeService(networkInteractor: networkInteractor, subscriptionsStorage: subscriptionStore) + let notifyProposer = NotifyProposer(networkingInteractor: networkInteractor, kms: kms, appMetadata: metadata, logger: logger) + let notifyProposeResponseSubscriber = NotifyProposeResponseSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, metadata: metadata) return DappPushClient( logger: logger, kms: kms, @@ -35,7 +37,9 @@ public struct DappPushClientFactory { subscriptionsProvider: subscriptionProvider, deletePushSubscriptionService: deletePushSubscriptionService, deletePushSubscriptionSubscriber: deletePushSubscriptionSubscriber, - resubscribeService: resubscribeService + resubscribeService: resubscribeService, + notifyProposer: notifyProposer, + notifyProposeResponseSubscriber: notifyProposeResponseSubscriber ) } } diff --git a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift index dbf87fb0d..4879ffabc 100644 --- a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift +++ b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift @@ -104,6 +104,11 @@ public class WalletPushClient { try await proposeResponder.respond(requestId: id, onSign: onSign) } + // rename method after approve deprication + public func approvePropose(id: RPCID, onSign: @escaping SigningCallback) async throws { + try await proposeResponder.respond(requestId: id, onSign: onSign) + } + public func reject(id: RPCID) async throws { try await proposeResponder.respondError(requestId: id) } From 338ba42da13b6e0f4280cc09cd5140ca976b5380 Mon Sep 17 00:00:00 2001 From: Radek Novak Date: Tue, 16 May 2023 18:58:48 +0200 Subject: [PATCH 05/33] UI improvements + Sign integration --- .../Chat/Import/ImportView.swift | 6 +- Sources/Web3Modal/Extensions/Collection.swift | 8 +++ Sources/Web3Modal/Extensions/Color.swift | 58 +++++++++++++++++++ 3 files changed, 69 insertions(+), 3 deletions(-) create mode 100644 Sources/Web3Modal/Extensions/Collection.swift create mode 100644 Sources/Web3Modal/Extensions/Color.swift diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/Import/ImportView.swift b/Example/Showcase/Classes/PresentationLayer/Chat/Import/ImportView.swift index 9e0584723..a7ab737cf 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/Import/ImportView.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/Import/ImportView.swift @@ -17,9 +17,9 @@ struct ImportView: View { VStack { -// BrandButton(title: "Web3Modal") { -// try await presenter.didPressWeb3Modal() -// } + BrandButton(title: "Web3Modal") { + try await presenter.didPressWeb3Modal() + } BrandButton(title: "Ok, done" ) { try await presenter.didPressImport() diff --git a/Sources/Web3Modal/Extensions/Collection.swift b/Sources/Web3Modal/Extensions/Collection.swift new file mode 100644 index 000000000..0287d3bb4 --- /dev/null +++ b/Sources/Web3Modal/Extensions/Collection.swift @@ -0,0 +1,8 @@ +import Foundation + +extension Collection { + /// Returns the element at the specified index if it is within bounds, otherwise nil. + subscript(safe index: Index) -> Element? { + return indices.contains(index) ? self[index] : nil + } +} diff --git a/Sources/Web3Modal/Extensions/Color.swift b/Sources/Web3Modal/Extensions/Color.swift new file mode 100644 index 000000000..84e687266 --- /dev/null +++ b/Sources/Web3Modal/Extensions/Color.swift @@ -0,0 +1,58 @@ +import SwiftUI + +extension Color { + static let foreground1 = Color("foreground1", bundle: .module) + static let foreground2 = Color("foreground2", bundle: .module) + static let foreground3 = Color("foreground3", bundle: .module) + static let foregroundInverse = Color("foregroundInverse", bundle: .module) + static let background1 = Color("background1", bundle: .module) + static let background2 = Color("background2", bundle: .module) + static let background3 = Color("background3", bundle: .module) + static let negative = Color("negative", bundle: .module) + static let thickOverlay = Color("thickOverlay", bundle: .module) + static let thinOverlay = Color("thinOverlay", bundle: .module) + static let accent = Color("accent", bundle: .module) +} + +@available(iOS 15.0, *) +struct Color_Previews: PreviewProvider { + static var allColors: [(String, Color)] { + [ + ("foreground1", Color("foreground1", bundle: .module)), + ("foreground2", Color("foreground2", bundle: .module)), + ("foreground3", Color("foreground3", bundle: .module)), + ("foregroundInverse", Color("foregroundInverse", bundle: .module)), + ("background1", Color("background1", bundle: .module)), + ("background2", Color("background2", bundle: .module)), + ("background3", Color("background3", bundle: .module)), + ("negative", Color("negative", bundle: .module)), + ("thickOverlay", Color("thickOverlay", bundle: .module)), + ("thinOverlay", Color("thinOverlay", bundle: .module)), + ("accent", Color("accent", bundle: .module)), + ] + } + + static var previews: some View { + VStack { + let columns = [ + GridItem(.adaptive(minimum: 150)), + ] + + LazyVGrid(columns: columns, alignment: .leading) { + ForEach(allColors, id: \.1) { name, color in + + VStack(alignment: .leading) { + RoundedRectangle(cornerRadius: 12) + .fill(color) + .frame(width: 62, height: 62) + + Text(name).bold() + } + .font(.footnote) + } + } + } + .padding() + .previewLayout(.sizeThatFits) + } +} From 24b4e9dc0318381e492d84e1ff97d968f114a544 Mon Sep 17 00:00:00 2001 From: Radek Novak Date: Tue, 16 May 2023 19:05:39 +0200 Subject: [PATCH 06/33] Fetching and displaying Wallets --- .../xcshareddata/xcschemes/Web3Modal.xcscheme | 66 ++++++++++ Example/ExampleApp.xcodeproj/project.pbxproj | 8 +- .../Configurator/AppearanceConfigurator.swift | 2 +- Sources/Web3Modal/Environment+ProjectId.swift | 12 ++ Sources/Web3Modal/Modal/ModalInteractor.swift | 8 +- Sources/Web3Modal/Modal/ModalSheet.swift | 106 ++++++++-------- Sources/Web3Modal/Modal/ModalViewModel.swift | 24 ++-- .../Networking/Common/Endpoint.swift | 113 ++++++++++++++++++ .../Networking/Common/HttpService.swift | 58 +++++++++ .../Networking/Explorer/ExplorerAPI.swift | 38 ++++++ .../Explorer/ListingsResponse.swift | 34 ++++++ 11 files changed, 392 insertions(+), 77 deletions(-) create mode 100644 .swiftpm/xcode/xcshareddata/xcschemes/Web3Modal.xcscheme create mode 100644 Sources/Web3Modal/Environment+ProjectId.swift create mode 100644 Sources/Web3Modal/Networking/Common/Endpoint.swift create mode 100644 Sources/Web3Modal/Networking/Common/HttpService.swift create mode 100644 Sources/Web3Modal/Networking/Explorer/ExplorerAPI.swift create mode 100644 Sources/Web3Modal/Networking/Explorer/ListingsResponse.swift diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/Web3Modal.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/Web3Modal.xcscheme new file mode 100644 index 000000000..8096d2466 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/Web3Modal.xcscheme @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index 58481035b..c008366dc 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -2638,11 +2638,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; - CODE_SIGN_STYLE = Manual; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 7; - DEVELOPMENT_TEAM = ""; - "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; + DEVELOPMENT_TEAM = W5R8AG9K22; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Showcase/Other/Info.plist; INFOPLIST_KEY_NSCameraUsageDescription = "Allow the app to scan for QR codes"; @@ -2660,7 +2659,6 @@ PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.chat; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.walletconnect.chat"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/Example/Showcase/Classes/ApplicationLayer/Configurator/AppearanceConfigurator.swift b/Example/Showcase/Classes/ApplicationLayer/Configurator/AppearanceConfigurator.swift index 5ad69dfb5..d6bb58b30 100644 --- a/Example/Showcase/Classes/ApplicationLayer/Configurator/AppearanceConfigurator.swift +++ b/Example/Showcase/Classes/ApplicationLayer/Configurator/AppearanceConfigurator.swift @@ -14,6 +14,6 @@ struct AppearanceConfigurator: Configurator { UINavigationBar.appearance().scrollEdgeAppearance = appearance UINavigationBar.appearance().compactAppearance = appearance - UIApplication.currentWindow.overrideUserInterfaceStyle = .dark +// UIApplication.currentWindow.overrideUserInterfaceStyle = .dark } } diff --git a/Sources/Web3Modal/Environment+ProjectId.swift b/Sources/Web3Modal/Environment+ProjectId.swift new file mode 100644 index 000000000..30e272aab --- /dev/null +++ b/Sources/Web3Modal/Environment+ProjectId.swift @@ -0,0 +1,12 @@ +import SwiftUI + +private struct ProjectIdKey: EnvironmentKey { + static let defaultValue: String = "" +} + +extension EnvironmentValues { + var projectId: String { + get { self[ProjectIdKey.self] } + set { self[ProjectIdKey.self] = newValue } + } +} diff --git a/Sources/Web3Modal/Modal/ModalInteractor.swift b/Sources/Web3Modal/Modal/ModalInteractor.swift index a89a1b0db..3435860b8 100644 --- a/Sources/Web3Modal/Modal/ModalInteractor.swift +++ b/Sources/Web3Modal/Modal/ModalInteractor.swift @@ -20,10 +20,10 @@ extension ModalSheet { Networking.configure(projectId: projectId, socketFactory: socketFactory) } -// func getListings() async throws -> [Listing] { -// let listingResponse = try await ExplorerApi.live().getMobileListings(projectId) -// return listingResponse.listings.values.compactMap { $0 } -// } + func getListings() async throws -> [Listing] { + let listingResponse = try await ExplorerApi.live().getMobileListings(projectId) + return listingResponse.listings.values.compactMap { $0 } + } func connect() async throws -> WalletConnectURI { diff --git a/Sources/Web3Modal/Modal/ModalSheet.swift b/Sources/Web3Modal/Modal/ModalSheet.swift index 3df7d7cf3..6f5ff3092 100644 --- a/Sources/Web3Modal/Modal/ModalSheet.swift +++ b/Sources/Web3Modal/Modal/ModalSheet.swift @@ -84,26 +84,22 @@ public struct ModalSheet: View { private func content() -> some View { switch viewModel.destination { case .wallets: - - Text("TBD in subsequent PR") - -// ZStack { -// VStack { -// HStack { -// ForEach(0..<4) { wallet in -// gridItem(for: wallet) -// } -// } -// HStack { -// ForEach(4..<7) { wallet in -// gridItem(for: wallet) -// } -// } -// } -// -// Spacer().frame(height: 200) -// } - + ZStack { + VStack { + HStack { + ForEach(0..<4) { wallet in + gridItem(for: wallet) + } + } + HStack { + ForEach(4..<7) { wallet in + gridItem(for: wallet) + } + } + } + + Spacer().frame(height: 200) + } case .help: WhatIsWalletView() case .qr: @@ -117,41 +113,41 @@ public struct ModalSheet: View { } } -// @ViewBuilder -// private func gridItem(for index: Int) -> some View { -// let wallet: Listing = viewModel.wallets.indices.contains(index) ? viewModel.wallets[index] : nil -// -// if #available(iOS 14.0, *) { -// VStack { -// AsyncImage(url: wallet != nil ? viewModel.imageUrl(for: wallet!) : nil) { image in -// image -// .resizable() -// .scaledToFit() -// } placeholder: { -// Color.foreground3 -// } -// .cornerRadius(8) -// .overlay( -// RoundedRectangle(cornerRadius: 8) -// .stroke(.gray.opacity(0.4), lineWidth: 1) -// ) -// -// Text(wallet?.name ?? "WalletName") -// .font(.system(size: 12)) -// .foregroundColor(.foreground1) -// .padding(.horizontal, 12) -// .fixedSize(horizontal: true, vertical: true) -// -// Text("RECENT") -// .opacity(0) -// .font(.system(size: 10)) -// .foregroundColor(.foreground3) -// .padding(.horizontal, 12) -// } -// .redacted(reason: wallet == nil ? .placeholder : []) -// .frame(maxWidth: 80, maxHeight: 96) -// } -// } + @ViewBuilder + private func gridItem(for index: Int) -> some View { + let wallet: Listing? = viewModel.wallets.indices.contains(index) ? viewModel.wallets[index] : nil + + if #available(iOS 14.0, *) { + VStack { + AsyncImage(url: wallet != nil ? viewModel.imageUrl(for: wallet!) : nil) { image in + image + .resizable() + .scaledToFit() + } placeholder: { + Color.foreground3 + } + .cornerRadius(8) + .overlay( + RoundedRectangle(cornerRadius: 8) + .stroke(.gray.opacity(0.4), lineWidth: 1) + ) + + Text(wallet?.name ?? "WalletName") + .font(.system(size: 12)) + .foregroundColor(.foreground1) + .padding(.horizontal, 12) + .fixedSize(horizontal: true, vertical: true) + + Text("RECENT") + .opacity(0) + .font(.system(size: 10)) + .foregroundColor(.foreground3) + .padding(.horizontal, 12) + } + .redacted(reason: wallet == nil ? .placeholder : []) + .frame(maxWidth: 80, maxHeight: 96) + } + } private func helpButton() -> some View { Button(action: { diff --git a/Sources/Web3Modal/Modal/ModalViewModel.swift b/Sources/Web3Modal/Modal/ModalViewModel.swift index 05633ca9c..65b1c2752 100644 --- a/Sources/Web3Modal/Modal/ModalViewModel.swift +++ b/Sources/Web3Modal/Modal/ModalViewModel.swift @@ -31,7 +31,7 @@ extension ModalSheet { @Published var uri: String? @Published var destination: Destination = .wallets @Published var errorMessage: String? -// @Published var wallets: [Listing] = [] + @Published var wallets: [Listing] = [] init(isShown: Binding, projectId: String, interactor: Interactor) { self.isShown = isShown @@ -47,17 +47,17 @@ extension ModalSheet { .store(in: &disposeBag) } -// @MainActor -// func fetchWallets() async { -// do { -// -// try await Task.sleep(nanoseconds: 1_000_000_000) -// wallets = try await interactor.getListings() -// } catch { -// print(error) -// errorMessage = error.localizedDescription -// } -// } + @MainActor + func fetchWallets() async { + do { + + try await Task.sleep(nanoseconds: 1_000_000_000) + wallets = try await interactor.getListings() + } catch { + print(error) + errorMessage = error.localizedDescription + } + } @MainActor func createURI() async { diff --git a/Sources/Web3Modal/Networking/Common/Endpoint.swift b/Sources/Web3Modal/Networking/Common/Endpoint.swift new file mode 100644 index 000000000..1962d080b --- /dev/null +++ b/Sources/Web3Modal/Networking/Common/Endpoint.swift @@ -0,0 +1,113 @@ +import Foundation + +struct Endpoint { + let path: String + let queryItems: [URLQueryItem] + let headers: [Headers] + let method: Method + let host: String + let body: Data? + let validResponseCodes: Set + + public enum Method: String { + case GET + case POST + case PUT + case PATCH + case DELETE + } + + enum Headers { + /// Standard headers used for every network call + case standard + + public var makeHeader: [String: String] { + switch self { + case .standard: + return [ + "Content-Type": "application/json", + ] + } + } + } + + var urlRequest: URLRequest { + var urlRequest = URLRequest(url: urlForRequest) + urlRequest.httpMethod = method.rawValue + urlRequest.httpBody = body + urlRequest.allHTTPHeaderFields = makeHTTPHeaders(headers) + return urlRequest + } + + private var urlForRequest: URL { + var components = URLComponents() + components.scheme = "https" + components.host = host + components.path = path + + if !queryItems.isEmpty { + components.queryItems = queryItems + } + + guard + let url = components.url + else { + preconditionFailure( + """ + Failed to construct valid url, if setting up new endpoint + make sure you have prefix / in path such as /v1/users + """ + ) + } + + return url + } + + private func makeHTTPHeaders(_ headers: [Headers]) -> [String: String] { + headers.reduce(into: [String: String]()) { result, nextElement in + result = result.merging(nextElement.makeHeader) { _, new in new } + } + } +} + +extension Endpoint.Headers: Equatable { + static func == (lhs: Endpoint.Headers, rhs: Endpoint.Headers) -> Bool { + lhs.makeHeader == rhs.makeHeader + } +} + +extension Endpoint { + + /// Un-authenticated endpoint. + /// - Parameters: + /// - path: Path for your endpoint` + /// - headers: Specific headers + /// - method: .GET, .POST etc + /// - host: Host url + /// - shouldEncodePath: This setting affects how your url is constructed. + /// - body: If you need to pass parameters, provide them here. + /// - validResponseCodes: This is set to default value `Set(200 ..< 300)` + /// and can be overridden if needed + /// - Returns: Endpoint with URLRequest that gets passed directly to HttpService. + static func bare( + path: String, + queryItems: [URLQueryItem] = [], + headers: [Endpoint.Headers] = [], + method: Endpoint.Method, + host: String, + body: Data? = nil, + validResponseCodes: Set = Set(200 ..< 300) + ) -> Self { + var headers = headers + headers.append(.standard) + return Self( + path: path, + queryItems: queryItems, + headers: headers, + method: method, + host: host, + body: body, + validResponseCodes: validResponseCodes + ) + } +} diff --git a/Sources/Web3Modal/Networking/Common/HttpService.swift b/Sources/Web3Modal/Networking/Common/HttpService.swift new file mode 100644 index 000000000..3b4fe6971 --- /dev/null +++ b/Sources/Web3Modal/Networking/Common/HttpService.swift @@ -0,0 +1,58 @@ +import Foundation + +struct HttpService { + var performRequest: (_ endpoint: Endpoint) async throws -> Result +} + +extension HttpService { + + static var live: Self = .init(performRequest: { endpoint in + + let (data, response) = try await URLSession.shared.data(for: endpoint.urlRequest) + + let error = errorForResponse(response, data, validResponseCodes: endpoint.validResponseCodes) + if let error = error { + return .failure(error) + } else { + return .success(data) + } + }) + + private static func errorForResponse( + _ response: URLResponse?, + _ data: Data?, validResponseCodes: Set + ) -> Error? { + guard let httpResponse = response as? HTTPURLResponse else { + return nil + } + + if !validResponseCodes.contains(httpResponse.statusCode) { + return Errors.badResponseCode( + code: httpResponse.statusCode, + payload: data + ) + } + + return nil + } + + enum Errors: Error, Equatable { + case emptyResponse + case badResponseCode(code: Int, payload: Data?) + + public var properties: [String: String] { + switch self { + case let .badResponseCode(code, _): + return [ + "category": "http_error", + "http_code": "\(String(code))" + ] + case .emptyResponse: + return [ + "category": "payload", + "message": "Failed for empty response" + ] + } + } + } +} diff --git a/Sources/Web3Modal/Networking/Explorer/ExplorerAPI.swift b/Sources/Web3Modal/Networking/Explorer/ExplorerAPI.swift new file mode 100644 index 000000000..c9a0c8d70 --- /dev/null +++ b/Sources/Web3Modal/Networking/Explorer/ExplorerAPI.swift @@ -0,0 +1,38 @@ +import Foundation + +struct ExplorerApi { + let getMobileListings: @Sendable (_ projectID: String) async throws -> ListingsResponse +} + +extension ExplorerApi { + static func live(httpService: HttpService = .live) -> Self { + .init( + getMobileListings: { projectId in + + let endpoint = Endpoint.bare( + path: "/w3m/v1/getMobileListings", + queryItems: [ + .init(name: "projectId", value: projectId), + .init(name: "page", value: "1"), + .init(name: "entries", value: "9"), + .init(name: "platforms", value: "ios,mac"), + ], + method: .GET, + host: "explorer-api.walletconnect.com" + ) + + let response = try await httpService.performRequest(endpoint) + + switch response { + case let .success(data): + + let listings = try JSONDecoder().decode(ListingsResponse.self, from: data) + + return listings + case let .failure(error): + throw error + } + } + ) + } +} diff --git a/Sources/Web3Modal/Networking/Explorer/ListingsResponse.swift b/Sources/Web3Modal/Networking/Explorer/ListingsResponse.swift new file mode 100644 index 000000000..91160ee85 --- /dev/null +++ b/Sources/Web3Modal/Networking/Explorer/ListingsResponse.swift @@ -0,0 +1,34 @@ +import Foundation + +struct ListingsResponse: Codable { + let listings: [String: Listing] +} + +struct Listing: Codable, Hashable, Identifiable { + let id: String + let name: String + let homepage: String + let imageId: String + let app: App + let mobile: Mobile + + private enum CodingKeys: String, CodingKey { + case id + case name + case homepage + case imageId = "image_id" + case app + case mobile + } + + struct App: Codable, Hashable { + let ios: String? + let mac: String? + let safari: String? + } + + struct Mobile: Codable, Hashable { + let native: String? + let universal: String? + } +} From 6edd14733b93141d103171325d336ad8b2213a86 Mon Sep 17 00:00:00 2001 From: Radek Novak Date: Thu, 18 May 2023 09:10:56 +0200 Subject: [PATCH 07/33] Use new endpoint + order field --- .../Chat/Import/ImportView.swift | 2 +- Sources/Web3Modal/Extensions/Collection.swift | 8 --- Sources/Web3Modal/Extensions/Color.swift | 58 ------------------- Sources/Web3Modal/Modal/ModalInteractor.swift | 2 +- Sources/Web3Modal/Modal/ModalSheet.swift | 4 +- Sources/Web3Modal/Modal/ModalViewModel.swift | 17 +++--- .../Networking/Explorer/ExplorerAPI.swift | 7 +-- .../Explorer/ListingsResponse.swift | 2 + Sources/Web3Modal/Resources/Asset.swift | 8 +-- Sources/Web3Modal/UI/AsyncImage.swift | 1 + 10 files changed, 24 insertions(+), 85 deletions(-) delete mode 100644 Sources/Web3Modal/Extensions/Collection.swift delete mode 100644 Sources/Web3Modal/Extensions/Color.swift diff --git a/Example/Showcase/Classes/PresentationLayer/Chat/Import/ImportView.swift b/Example/Showcase/Classes/PresentationLayer/Chat/Import/ImportView.swift index a7ab737cf..9de69d9f3 100644 --- a/Example/Showcase/Classes/PresentationLayer/Chat/Import/ImportView.swift +++ b/Example/Showcase/Classes/PresentationLayer/Chat/Import/ImportView.swift @@ -17,7 +17,7 @@ struct ImportView: View { VStack { - BrandButton(title: "Web3Modal") { + BrandButton(title: "Web3Modal WIP") { try await presenter.didPressWeb3Modal() } diff --git a/Sources/Web3Modal/Extensions/Collection.swift b/Sources/Web3Modal/Extensions/Collection.swift deleted file mode 100644 index 0287d3bb4..000000000 --- a/Sources/Web3Modal/Extensions/Collection.swift +++ /dev/null @@ -1,8 +0,0 @@ -import Foundation - -extension Collection { - /// Returns the element at the specified index if it is within bounds, otherwise nil. - subscript(safe index: Index) -> Element? { - return indices.contains(index) ? self[index] : nil - } -} diff --git a/Sources/Web3Modal/Extensions/Color.swift b/Sources/Web3Modal/Extensions/Color.swift deleted file mode 100644 index 84e687266..000000000 --- a/Sources/Web3Modal/Extensions/Color.swift +++ /dev/null @@ -1,58 +0,0 @@ -import SwiftUI - -extension Color { - static let foreground1 = Color("foreground1", bundle: .module) - static let foreground2 = Color("foreground2", bundle: .module) - static let foreground3 = Color("foreground3", bundle: .module) - static let foregroundInverse = Color("foregroundInverse", bundle: .module) - static let background1 = Color("background1", bundle: .module) - static let background2 = Color("background2", bundle: .module) - static let background3 = Color("background3", bundle: .module) - static let negative = Color("negative", bundle: .module) - static let thickOverlay = Color("thickOverlay", bundle: .module) - static let thinOverlay = Color("thinOverlay", bundle: .module) - static let accent = Color("accent", bundle: .module) -} - -@available(iOS 15.0, *) -struct Color_Previews: PreviewProvider { - static var allColors: [(String, Color)] { - [ - ("foreground1", Color("foreground1", bundle: .module)), - ("foreground2", Color("foreground2", bundle: .module)), - ("foreground3", Color("foreground3", bundle: .module)), - ("foregroundInverse", Color("foregroundInverse", bundle: .module)), - ("background1", Color("background1", bundle: .module)), - ("background2", Color("background2", bundle: .module)), - ("background3", Color("background3", bundle: .module)), - ("negative", Color("negative", bundle: .module)), - ("thickOverlay", Color("thickOverlay", bundle: .module)), - ("thinOverlay", Color("thinOverlay", bundle: .module)), - ("accent", Color("accent", bundle: .module)), - ] - } - - static var previews: some View { - VStack { - let columns = [ - GridItem(.adaptive(minimum: 150)), - ] - - LazyVGrid(columns: columns, alignment: .leading) { - ForEach(allColors, id: \.1) { name, color in - - VStack(alignment: .leading) { - RoundedRectangle(cornerRadius: 12) - .fill(color) - .frame(width: 62, height: 62) - - Text(name).bold() - } - .font(.footnote) - } - } - } - .padding() - .previewLayout(.sizeThatFits) - } -} diff --git a/Sources/Web3Modal/Modal/ModalInteractor.swift b/Sources/Web3Modal/Modal/ModalInteractor.swift index 3435860b8..e305b6968 100644 --- a/Sources/Web3Modal/Modal/ModalInteractor.swift +++ b/Sources/Web3Modal/Modal/ModalInteractor.swift @@ -21,7 +21,7 @@ extension ModalSheet { } func getListings() async throws -> [Listing] { - let listingResponse = try await ExplorerApi.live().getMobileListings(projectId) + let listingResponse = try await ExplorerApi.live().getListings(projectId) return listingResponse.listings.values.compactMap { $0 } } diff --git a/Sources/Web3Modal/Modal/ModalSheet.swift b/Sources/Web3Modal/Modal/ModalSheet.swift index 6f5ff3092..6a517ce55 100644 --- a/Sources/Web3Modal/Modal/ModalSheet.swift +++ b/Sources/Web3Modal/Modal/ModalSheet.swift @@ -19,7 +19,7 @@ public struct ModalSheet: View { .onAppear { Task { await viewModel.createURI() -// await viewModel.fetchWallets() + await viewModel.fetchWallets() } } .background( @@ -125,7 +125,9 @@ public struct ModalSheet: View { .scaledToFit() } placeholder: { Color.foreground3 + .frame(width: 60, height: 60) } + .animation(.default) .cornerRadius(8) .overlay( RoundedRectangle(cornerRadius: 8) diff --git a/Sources/Web3Modal/Modal/ModalViewModel.swift b/Sources/Web3Modal/Modal/ModalViewModel.swift index 65b1c2752..5cfd93f76 100644 --- a/Sources/Web3Modal/Modal/ModalViewModel.swift +++ b/Sources/Web3Modal/Modal/ModalViewModel.swift @@ -50,9 +50,10 @@ extension ModalSheet { @MainActor func fetchWallets() async { do { - - try await Task.sleep(nanoseconds: 1_000_000_000) - wallets = try await interactor.getListings() + let wallets = try await interactor.getListings() + // Small deliberate delay to ensure animations execute properly + try await Task.sleep(nanoseconds: 500_000_000) + self.wallets = wallets.sorted { $0.order < $1.order } } catch { print(error) errorMessage = error.localizedDescription @@ -81,10 +82,10 @@ extension ModalSheet { UIPasteboard.general.string = uri } -// func imageUrl(for listing: Listing) -> URL? { -// let urlString = "https://explorer-api.walletconnect.com/v3/logo/md/\(listing.imageId)?projectId=\(projectId)" -// -// return URL(string: urlString) -// } + func imageUrl(for listing: Listing) -> URL? { + let urlString = "https://explorer-api.walletconnect.com/v3/logo/md/\(listing.imageId)?projectId=\(projectId)" + + return URL(string: urlString) + } } } diff --git a/Sources/Web3Modal/Networking/Explorer/ExplorerAPI.swift b/Sources/Web3Modal/Networking/Explorer/ExplorerAPI.swift index c9a0c8d70..49d977972 100644 --- a/Sources/Web3Modal/Networking/Explorer/ExplorerAPI.swift +++ b/Sources/Web3Modal/Networking/Explorer/ExplorerAPI.swift @@ -1,21 +1,20 @@ import Foundation struct ExplorerApi { - let getMobileListings: @Sendable (_ projectID: String) async throws -> ListingsResponse + let getListings: @Sendable (_ projectID: String) async throws -> ListingsResponse } extension ExplorerApi { static func live(httpService: HttpService = .live) -> Self { .init( - getMobileListings: { projectId in + getListings: { projectId in let endpoint = Endpoint.bare( - path: "/w3m/v1/getMobileListings", + path: "/w3m/v1/getiOSListings", queryItems: [ .init(name: "projectId", value: projectId), .init(name: "page", value: "1"), .init(name: "entries", value: "9"), - .init(name: "platforms", value: "ios,mac"), ], method: .GET, host: "explorer-api.walletconnect.com" diff --git a/Sources/Web3Modal/Networking/Explorer/ListingsResponse.swift b/Sources/Web3Modal/Networking/Explorer/ListingsResponse.swift index 91160ee85..884943e44 100644 --- a/Sources/Web3Modal/Networking/Explorer/ListingsResponse.swift +++ b/Sources/Web3Modal/Networking/Explorer/ListingsResponse.swift @@ -8,6 +8,7 @@ struct Listing: Codable, Hashable, Identifiable { let id: String let name: String let homepage: String + let order: Int let imageId: String let app: App let mobile: Mobile @@ -16,6 +17,7 @@ struct Listing: Codable, Hashable, Identifiable { case id case name case homepage + case order case imageId = "image_id" case app case mobile diff --git a/Sources/Web3Modal/Resources/Asset.swift b/Sources/Web3Modal/Resources/Asset.swift index c5d987646..2cbc6a7cb 100644 --- a/Sources/Web3Modal/Resources/Asset.swift +++ b/Sources/Web3Modal/Resources/Asset.swift @@ -4,21 +4,21 @@ import UIKit enum Asset: String { - // Icons + /// Icons case close case external_link case help case wallet - // large + /// large case copy_large case qr_large - // Images + /// Images case walletconnect_logo case wc_logo - // Help + /// Help case Browser case DAO case DeFi diff --git a/Sources/Web3Modal/UI/AsyncImage.swift b/Sources/Web3Modal/UI/AsyncImage.swift index 4eca1aba1..c5a2972ab 100644 --- a/Sources/Web3Modal/UI/AsyncImage.swift +++ b/Sources/Web3Modal/UI/AsyncImage.swift @@ -9,6 +9,7 @@ struct AsyncImage: View where Content: View { init(_ url: URL?) { guard let url = url else { return } + URLSession.shared.dataTaskPublisher(for: url) .map(\.data) .map { $0 as Data? } From 0463193866c5ee22127cd285dc7d8cdf4f2cf87b Mon Sep 17 00:00:00 2001 From: Radek Novak Date: Thu, 18 May 2023 10:03:18 +0200 Subject: [PATCH 08/33] Fix AsyncImage animations --- Example/ExampleApp.xcodeproj/project.pbxproj | 7 ++++-- .../Configurator/AppearanceConfigurator.swift | 2 +- Sources/Web3Modal/Modal/ModalSheet.swift | 22 +++++++++++-------- Sources/Web3Modal/Modal/ModalViewModel.swift | 5 ++++- Sources/Web3Modal/UI/AsyncImage.swift | 12 ++++++++-- 5 files changed, 33 insertions(+), 15 deletions(-) diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index c008366dc..e0b5af040 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -2639,9 +2639,11 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_IDENTITY = "Apple Development"; - CODE_SIGN_STYLE = Automatic; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; CURRENT_PROJECT_VERSION = 7; - DEVELOPMENT_TEAM = W5R8AG9K22; + DEVELOPMENT_TEAM = ""; + "DEVELOPMENT_TEAM[sdk=iphoneos*]" = W5R8AG9K22; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Showcase/Other/Info.plist; INFOPLIST_KEY_NSCameraUsageDescription = "Allow the app to scan for QR codes"; @@ -2659,6 +2661,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.chat; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + "PROVISIONING_PROFILE_SPECIFIER[sdk=iphoneos*]" = "match Development com.walletconnect.chat"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/Example/Showcase/Classes/ApplicationLayer/Configurator/AppearanceConfigurator.swift b/Example/Showcase/Classes/ApplicationLayer/Configurator/AppearanceConfigurator.swift index d6bb58b30..5ad69dfb5 100644 --- a/Example/Showcase/Classes/ApplicationLayer/Configurator/AppearanceConfigurator.swift +++ b/Example/Showcase/Classes/ApplicationLayer/Configurator/AppearanceConfigurator.swift @@ -14,6 +14,6 @@ struct AppearanceConfigurator: Configurator { UINavigationBar.appearance().scrollEdgeAppearance = appearance UINavigationBar.appearance().compactAppearance = appearance -// UIApplication.currentWindow.overrideUserInterfaceStyle = .dark + UIApplication.currentWindow.overrideUserInterfaceStyle = .dark } } diff --git a/Sources/Web3Modal/Modal/ModalSheet.swift b/Sources/Web3Modal/Modal/ModalSheet.swift index 6a517ce55..06ec57a84 100644 --- a/Sources/Web3Modal/Modal/ModalSheet.swift +++ b/Sources/Web3Modal/Modal/ModalSheet.swift @@ -1,6 +1,7 @@ import SwiftUI public struct ModalSheet: View { + @ObservedObject var viewModel: ModalViewModel public var body: some View { @@ -73,10 +74,12 @@ public struct ModalSheet: View { .foregroundColor(.accent) .frame(height: 60) .overlay( - Text(viewModel.destination.contentTitle) - .font(.system(size: 20).weight(.semibold)) - .foregroundColor(.foreground1) - .padding(.horizontal, 50) + VStack { + Text(viewModel.destination.contentTitle) + .font(.system(size: 20).weight(.semibold)) + .foregroundColor(.foreground1) + .padding(.horizontal, 50) + } ) } @@ -116,18 +119,19 @@ public struct ModalSheet: View { @ViewBuilder private func gridItem(for index: Int) -> some View { let wallet: Listing? = viewModel.wallets.indices.contains(index) ? viewModel.wallets[index] : nil - - if #available(iOS 14.0, *) { + let walletUrl: URL? = wallet != nil ? viewModel.imageUrl(for: wallet!) : nil + + if #available(iOS 15.0, *) { VStack { - AsyncImage(url: wallet != nil ? viewModel.imageUrl(for: wallet!) : nil) { image in + AsyncImage(url: walletUrl) { image in image .resizable() .scaledToFit() } placeholder: { - Color.foreground3 + Color + .foreground3 .frame(width: 60, height: 60) } - .animation(.default) .cornerRadius(8) .overlay( RoundedRectangle(cornerRadius: 8) diff --git a/Sources/Web3Modal/Modal/ModalViewModel.swift b/Sources/Web3Modal/Modal/ModalViewModel.swift index 5cfd93f76..3f6e32b25 100644 --- a/Sources/Web3Modal/Modal/ModalViewModel.swift +++ b/Sources/Web3Modal/Modal/ModalViewModel.swift @@ -53,7 +53,10 @@ extension ModalSheet { let wallets = try await interactor.getListings() // Small deliberate delay to ensure animations execute properly try await Task.sleep(nanoseconds: 500_000_000) - self.wallets = wallets.sorted { $0.order < $1.order } + + withAnimation { + self.wallets = wallets.sorted { $0.order < $1.order } + } } catch { print(error) errorMessage = error.localizedDescription diff --git a/Sources/Web3Modal/UI/AsyncImage.swift b/Sources/Web3Modal/UI/AsyncImage.swift index c5a2972ab..3ce9283c4 100644 --- a/Sources/Web3Modal/UI/AsyncImage.swift +++ b/Sources/Web3Modal/UI/AsyncImage.swift @@ -15,7 +15,11 @@ struct AsyncImage: View where Content: View { .map { $0 as Data? } .replaceError(with: nil) .receive(on: RunLoop.main) - .assign(to: \.data, on: self) + .sink(receiveValue: { data in + withAnimation { + self.data = data + } + }) .store(in: &cancellables) } } @@ -28,7 +32,11 @@ struct AsyncImage: View where Content: View { self.conditionalContent = nil } - init(url: URL?, @ViewBuilder content: @escaping (Image) -> I, @ViewBuilder placeholder: @escaping () -> P) where Content == _ConditionalContent, I: View, P: View { + init( + url: URL?, + @ViewBuilder content: @escaping (Image) -> I, + @ViewBuilder placeholder: @escaping () -> P + ) where Content == _ConditionalContent, I: View, P: View { self.imageLoader = Loader(url) self.conditionalContent = { image in if let image = image { From bf5b18258d53109ff5e0632eec18ac4a6982cef6 Mon Sep 17 00:00:00 2001 From: Radek Novak Date: Fri, 19 May 2023 17:00:24 +0200 Subject: [PATCH 09/33] Extract and reuse existing HTTPClient --- Package.swift | 5 +- .../HTTPClient/HTTPClient.swift | 0 .../HTTPClient/HTTPError.swift | 0 .../HTTPClient/HTTPNetworkClient.swift | 0 .../HTTPClient/HTTPService.swift | 0 .../WalletConnectEcho/EchoClientFactory.swift | 1 + .../Register/EchoRegisterService.swift | 1 + .../Register/EchoService.swift | 1 + .../IdentityClientFactory.swift | 1 + .../IdentityKeyAPI.swift | 1 + .../IdentityNetworkService.swift | 1 + .../Ethereum/ContractCall/RPCService.swift | 1 + .../Ethereum/EIP1271/EIP1271Verifier.swift | 1 + .../Ethereum/ENS/ENSRegistryContract.swift | 1 + .../Ethereum/ENS/ENSResolver.swift | 1 + .../Ethereum/ENS/ENSResolverContract.swift | 1 + .../Ethereum/ENS/ENSResolverFactory.swift | 1 + .../Verifier/MessageVerifierFactory.swift | 1 + .../WalletConnectVerify/OriginVerifier.swift | 1 + .../Register/VerifyService.swift | 1 + Sources/Web3Modal/Modal/ModalInteractor.swift | 12 +- .../Networking/Common/Endpoint.swift | 113 ------------------ .../Networking/Common/HttpService.swift | 58 --------- .../Networking/Explorer/ExplorerAPI.swift | 66 +++++----- Tests/AuthTests/Stubs/MessageSignerMock.swift | 1 + .../Helpers/Error+Extension.swift | 1 + Tests/TestingUtils/Mocks/HTTPClientMock.swift | 2 +- 27 files changed, 66 insertions(+), 207 deletions(-) rename Sources/{WalletConnectNetworking => }/HTTPClient/HTTPClient.swift (100%) rename Sources/{WalletConnectNetworking => }/HTTPClient/HTTPError.swift (100%) rename Sources/{WalletConnectNetworking => }/HTTPClient/HTTPNetworkClient.swift (100%) rename Sources/{WalletConnectNetworking => }/HTTPClient/HTTPService.swift (100%) delete mode 100644 Sources/Web3Modal/Networking/Common/Endpoint.swift delete mode 100644 Sources/Web3Modal/Networking/Common/HttpService.swift diff --git a/Package.swift b/Package.swift index c8a77cfb3..6960b3734 100644 --- a/Package.swift +++ b/Package.swift @@ -109,9 +109,12 @@ let package = Package( .target( name: "Commons", dependencies: []), + .target( + name: "HTTPClient", + dependencies: []), .target( name: "WalletConnectNetworking", - dependencies: ["WalletConnectRelay"]), + dependencies: ["HTTPClient", "WalletConnectRelay"]), .target( name: "WalletConnectRouter", dependencies: []), diff --git a/Sources/WalletConnectNetworking/HTTPClient/HTTPClient.swift b/Sources/HTTPClient/HTTPClient.swift similarity index 100% rename from Sources/WalletConnectNetworking/HTTPClient/HTTPClient.swift rename to Sources/HTTPClient/HTTPClient.swift diff --git a/Sources/WalletConnectNetworking/HTTPClient/HTTPError.swift b/Sources/HTTPClient/HTTPError.swift similarity index 100% rename from Sources/WalletConnectNetworking/HTTPClient/HTTPError.swift rename to Sources/HTTPClient/HTTPError.swift diff --git a/Sources/WalletConnectNetworking/HTTPClient/HTTPNetworkClient.swift b/Sources/HTTPClient/HTTPNetworkClient.swift similarity index 100% rename from Sources/WalletConnectNetworking/HTTPClient/HTTPNetworkClient.swift rename to Sources/HTTPClient/HTTPNetworkClient.swift diff --git a/Sources/WalletConnectNetworking/HTTPClient/HTTPService.swift b/Sources/HTTPClient/HTTPService.swift similarity index 100% rename from Sources/WalletConnectNetworking/HTTPClient/HTTPService.swift rename to Sources/HTTPClient/HTTPService.swift diff --git a/Sources/WalletConnectEcho/EchoClientFactory.swift b/Sources/WalletConnectEcho/EchoClientFactory.swift index 43bdc82b7..9b431d045 100644 --- a/Sources/WalletConnectEcho/EchoClientFactory.swift +++ b/Sources/WalletConnectEcho/EchoClientFactory.swift @@ -1,4 +1,5 @@ import Foundation +import HTTPClient public struct EchoClientFactory { public static func create(projectId: String, diff --git a/Sources/WalletConnectEcho/Register/EchoRegisterService.swift b/Sources/WalletConnectEcho/Register/EchoRegisterService.swift index e7e27e68f..50bb41c0b 100644 --- a/Sources/WalletConnectEcho/Register/EchoRegisterService.swift +++ b/Sources/WalletConnectEcho/Register/EchoRegisterService.swift @@ -1,4 +1,5 @@ import Foundation +import HTTPClient actor EchoRegisterService { private let httpClient: HTTPClient diff --git a/Sources/WalletConnectEcho/Register/EchoService.swift b/Sources/WalletConnectEcho/Register/EchoService.swift index 44c328101..381b9c9ef 100644 --- a/Sources/WalletConnectEcho/Register/EchoService.swift +++ b/Sources/WalletConnectEcho/Register/EchoService.swift @@ -1,4 +1,5 @@ import Foundation +import HTTPClient enum EchoAPI: HTTPService { case register(clientId: String, token: String, projectId: String, environment: APNSEnvironment, auth: String) diff --git a/Sources/WalletConnectIdentity/IdentityClientFactory.swift b/Sources/WalletConnectIdentity/IdentityClientFactory.swift index 236a939f7..6a6d9a44b 100644 --- a/Sources/WalletConnectIdentity/IdentityClientFactory.swift +++ b/Sources/WalletConnectIdentity/IdentityClientFactory.swift @@ -1,4 +1,5 @@ import Foundation +import HTTPClient public final class IdentityClientFactory { diff --git a/Sources/WalletConnectIdentity/IdentityKeyAPI.swift b/Sources/WalletConnectIdentity/IdentityKeyAPI.swift index b99d7165c..dbc3d8780 100644 --- a/Sources/WalletConnectIdentity/IdentityKeyAPI.swift +++ b/Sources/WalletConnectIdentity/IdentityKeyAPI.swift @@ -1,4 +1,5 @@ import Foundation +import HTTPClient enum IdentityKeyAPI: HTTPService { diff --git a/Sources/WalletConnectIdentity/IdentityNetworkService.swift b/Sources/WalletConnectIdentity/IdentityNetworkService.swift index 9fc27b58b..89f3f2808 100644 --- a/Sources/WalletConnectIdentity/IdentityNetworkService.swift +++ b/Sources/WalletConnectIdentity/IdentityNetworkService.swift @@ -1,4 +1,5 @@ import Foundation +import HTTPClient actor IdentityNetworkService: IdentityNetworking { diff --git a/Sources/WalletConnectSigner/Ethereum/ContractCall/RPCService.swift b/Sources/WalletConnectSigner/Ethereum/ContractCall/RPCService.swift index eb6db47e7..ed1a10cd9 100644 --- a/Sources/WalletConnectSigner/Ethereum/ContractCall/RPCService.swift +++ b/Sources/WalletConnectSigner/Ethereum/ContractCall/RPCService.swift @@ -1,4 +1,5 @@ import Foundation +import HTTPClient struct RPCService: HTTPService { let data: Data diff --git a/Sources/WalletConnectSigner/Ethereum/EIP1271/EIP1271Verifier.swift b/Sources/WalletConnectSigner/Ethereum/EIP1271/EIP1271Verifier.swift index bd3c318da..29127d7ac 100644 --- a/Sources/WalletConnectSigner/Ethereum/EIP1271/EIP1271Verifier.swift +++ b/Sources/WalletConnectSigner/Ethereum/EIP1271/EIP1271Verifier.swift @@ -1,4 +1,5 @@ import Foundation +import HTTPClient actor EIP1271Verifier { private let projectId: String diff --git a/Sources/WalletConnectSigner/Ethereum/ENS/ENSRegistryContract.swift b/Sources/WalletConnectSigner/Ethereum/ENS/ENSRegistryContract.swift index 1e6ba1855..f576fdab2 100644 --- a/Sources/WalletConnectSigner/Ethereum/ENS/ENSRegistryContract.swift +++ b/Sources/WalletConnectSigner/Ethereum/ENS/ENSRegistryContract.swift @@ -1,4 +1,5 @@ import Foundation +import HTTPClient actor ENSRegistryContract { diff --git a/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolver.swift b/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolver.swift index 0355e5845..634d688e8 100644 --- a/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolver.swift +++ b/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolver.swift @@ -1,4 +1,5 @@ import Foundation +import HTTPClient public actor ENSResolver { diff --git a/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolverContract.swift b/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolverContract.swift index e91f17972..0cd77fb6e 100644 --- a/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolverContract.swift +++ b/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolverContract.swift @@ -1,4 +1,5 @@ import Foundation +import HTTPClient actor ENSResolverContract { diff --git a/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolverFactory.swift b/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolverFactory.swift index 0f85ada83..00d60a0d5 100644 --- a/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolverFactory.swift +++ b/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolverFactory.swift @@ -1,4 +1,5 @@ import Foundation +import HTTPClient public final class ENSResolverFactory { diff --git a/Sources/WalletConnectSigner/Verifier/MessageVerifierFactory.swift b/Sources/WalletConnectSigner/Verifier/MessageVerifierFactory.swift index 2dcc4255c..a59254fae 100644 --- a/Sources/WalletConnectSigner/Verifier/MessageVerifierFactory.swift +++ b/Sources/WalletConnectSigner/Verifier/MessageVerifierFactory.swift @@ -1,4 +1,5 @@ import Foundation +import HTTPClient public struct MessageVerifierFactory { diff --git a/Sources/WalletConnectVerify/OriginVerifier.swift b/Sources/WalletConnectVerify/OriginVerifier.swift index 039cccecf..f7aa4960f 100644 --- a/Sources/WalletConnectVerify/OriginVerifier.swift +++ b/Sources/WalletConnectVerify/OriginVerifier.swift @@ -1,4 +1,5 @@ import Foundation +import HTTPClient public final class OriginVerifier { enum Errors: Error { diff --git a/Sources/WalletConnectVerify/Register/VerifyService.swift b/Sources/WalletConnectVerify/Register/VerifyService.swift index 5cf720118..9580f1727 100644 --- a/Sources/WalletConnectVerify/Register/VerifyService.swift +++ b/Sources/WalletConnectVerify/Register/VerifyService.swift @@ -1,4 +1,5 @@ import Foundation +import HTTPClient enum VerifyAPI: HTTPService { case resolve(assertionId: String) diff --git a/Sources/Web3Modal/Modal/ModalInteractor.swift b/Sources/Web3Modal/Modal/ModalInteractor.swift index e305b6968..4ad128740 100644 --- a/Sources/Web3Modal/Modal/ModalInteractor.swift +++ b/Sources/Web3Modal/Modal/ModalInteractor.swift @@ -1,7 +1,7 @@ import WalletConnectPairing import WalletConnectSign import Combine -import WalletConnectNetworking +import HTTPClient extension ModalSheet { final class Interactor { @@ -21,8 +21,14 @@ extension ModalSheet { } func getListings() async throws -> [Listing] { - let listingResponse = try await ExplorerApi.live().getListings(projectId) - return listingResponse.listings.values.compactMap { $0 } + + let httpClient = HTTPNetworkClient(host: "explorer-api.walletconnect.com") + let response = try await httpClient.request( + ListingsResponse.self, + at: ExplorerAPI.getListings(projectId: projectId) + ) + + return response.listings.values.compactMap { $0 } } func connect() async throws -> WalletConnectURI { diff --git a/Sources/Web3Modal/Networking/Common/Endpoint.swift b/Sources/Web3Modal/Networking/Common/Endpoint.swift deleted file mode 100644 index 1962d080b..000000000 --- a/Sources/Web3Modal/Networking/Common/Endpoint.swift +++ /dev/null @@ -1,113 +0,0 @@ -import Foundation - -struct Endpoint { - let path: String - let queryItems: [URLQueryItem] - let headers: [Headers] - let method: Method - let host: String - let body: Data? - let validResponseCodes: Set - - public enum Method: String { - case GET - case POST - case PUT - case PATCH - case DELETE - } - - enum Headers { - /// Standard headers used for every network call - case standard - - public var makeHeader: [String: String] { - switch self { - case .standard: - return [ - "Content-Type": "application/json", - ] - } - } - } - - var urlRequest: URLRequest { - var urlRequest = URLRequest(url: urlForRequest) - urlRequest.httpMethod = method.rawValue - urlRequest.httpBody = body - urlRequest.allHTTPHeaderFields = makeHTTPHeaders(headers) - return urlRequest - } - - private var urlForRequest: URL { - var components = URLComponents() - components.scheme = "https" - components.host = host - components.path = path - - if !queryItems.isEmpty { - components.queryItems = queryItems - } - - guard - let url = components.url - else { - preconditionFailure( - """ - Failed to construct valid url, if setting up new endpoint - make sure you have prefix / in path such as /v1/users - """ - ) - } - - return url - } - - private func makeHTTPHeaders(_ headers: [Headers]) -> [String: String] { - headers.reduce(into: [String: String]()) { result, nextElement in - result = result.merging(nextElement.makeHeader) { _, new in new } - } - } -} - -extension Endpoint.Headers: Equatable { - static func == (lhs: Endpoint.Headers, rhs: Endpoint.Headers) -> Bool { - lhs.makeHeader == rhs.makeHeader - } -} - -extension Endpoint { - - /// Un-authenticated endpoint. - /// - Parameters: - /// - path: Path for your endpoint` - /// - headers: Specific headers - /// - method: .GET, .POST etc - /// - host: Host url - /// - shouldEncodePath: This setting affects how your url is constructed. - /// - body: If you need to pass parameters, provide them here. - /// - validResponseCodes: This is set to default value `Set(200 ..< 300)` - /// and can be overridden if needed - /// - Returns: Endpoint with URLRequest that gets passed directly to HttpService. - static func bare( - path: String, - queryItems: [URLQueryItem] = [], - headers: [Endpoint.Headers] = [], - method: Endpoint.Method, - host: String, - body: Data? = nil, - validResponseCodes: Set = Set(200 ..< 300) - ) -> Self { - var headers = headers - headers.append(.standard) - return Self( - path: path, - queryItems: queryItems, - headers: headers, - method: method, - host: host, - body: body, - validResponseCodes: validResponseCodes - ) - } -} diff --git a/Sources/Web3Modal/Networking/Common/HttpService.swift b/Sources/Web3Modal/Networking/Common/HttpService.swift deleted file mode 100644 index 3b4fe6971..000000000 --- a/Sources/Web3Modal/Networking/Common/HttpService.swift +++ /dev/null @@ -1,58 +0,0 @@ -import Foundation - -struct HttpService { - var performRequest: (_ endpoint: Endpoint) async throws -> Result -} - -extension HttpService { - - static var live: Self = .init(performRequest: { endpoint in - - let (data, response) = try await URLSession.shared.data(for: endpoint.urlRequest) - - let error = errorForResponse(response, data, validResponseCodes: endpoint.validResponseCodes) - if let error = error { - return .failure(error) - } else { - return .success(data) - } - }) - - private static func errorForResponse( - _ response: URLResponse?, - _ data: Data?, validResponseCodes: Set - ) -> Error? { - guard let httpResponse = response as? HTTPURLResponse else { - return nil - } - - if !validResponseCodes.contains(httpResponse.statusCode) { - return Errors.badResponseCode( - code: httpResponse.statusCode, - payload: data - ) - } - - return nil - } - - enum Errors: Error, Equatable { - case emptyResponse - case badResponseCode(code: Int, payload: Data?) - - public var properties: [String: String] { - switch self { - case let .badResponseCode(code, _): - return [ - "category": "http_error", - "http_code": "\(String(code))" - ] - case .emptyResponse: - return [ - "category": "payload", - "message": "Failed for empty response" - ] - } - } - } -} diff --git a/Sources/Web3Modal/Networking/Explorer/ExplorerAPI.swift b/Sources/Web3Modal/Networking/Explorer/ExplorerAPI.swift index 49d977972..b26d84662 100644 --- a/Sources/Web3Modal/Networking/Explorer/ExplorerAPI.swift +++ b/Sources/Web3Modal/Networking/Explorer/ExplorerAPI.swift @@ -1,37 +1,41 @@ import Foundation +import HTTPClient -struct ExplorerApi { - let getListings: @Sendable (_ projectID: String) async throws -> ListingsResponse -} - -extension ExplorerApi { - static func live(httpService: HttpService = .live) -> Self { - .init( - getListings: { projectId in - - let endpoint = Endpoint.bare( - path: "/w3m/v1/getiOSListings", - queryItems: [ - .init(name: "projectId", value: projectId), - .init(name: "page", value: "1"), - .init(name: "entries", value: "9"), - ], - method: .GET, - host: "explorer-api.walletconnect.com" - ) +enum ExplorerAPI: HTTPService { + case getListings(projectId: String) + + var path: String { + switch self { + case .getListings: return "/w3m/v1/getiOSListings" + } + } + + var method: HTTPMethod { + switch self { + case .getListings: return .get + } + } + + var body: Data? { + nil + } - let response = try await httpService.performRequest(endpoint) + var queryParameters: [String: String]? { + switch self { + case let .getListings(projectId): + return [ + "projectId": projectId, + "page": "1", + "entries": "9", + ] + } + } + + var scheme: String { + return "https" + } - switch response { - case let .success(data): - - let listings = try JSONDecoder().decode(ListingsResponse.self, from: data) - - return listings - case let .failure(error): - throw error - } - } - ) + var additionalHeaderFields: [String: String]? { + nil } } diff --git a/Tests/AuthTests/Stubs/MessageSignerMock.swift b/Tests/AuthTests/Stubs/MessageSignerMock.swift index 3809c2dbb..7b1b116de 100644 --- a/Tests/AuthTests/Stubs/MessageSignerMock.swift +++ b/Tests/AuthTests/Stubs/MessageSignerMock.swift @@ -1,4 +1,5 @@ import Foundation +import HTTPClient @testable import WalletConnectSigner extension MessageVerifier { diff --git a/Tests/RelayerTests/Helpers/Error+Extension.swift b/Tests/RelayerTests/Helpers/Error+Extension.swift index cf5525e24..901d2d829 100644 --- a/Tests/RelayerTests/Helpers/Error+Extension.swift +++ b/Tests/RelayerTests/Helpers/Error+Extension.swift @@ -1,6 +1,7 @@ import Foundation @testable import WalletConnectRelay @testable import WalletConnectNetworking +@testable import HTTPClient extension NSError { diff --git a/Tests/TestingUtils/Mocks/HTTPClientMock.swift b/Tests/TestingUtils/Mocks/HTTPClientMock.swift index c769c2b68..5cc5d8096 100644 --- a/Tests/TestingUtils/Mocks/HTTPClientMock.swift +++ b/Tests/TestingUtils/Mocks/HTTPClientMock.swift @@ -1,5 +1,5 @@ import Foundation -import WalletConnectNetworking +import HTTPClient public final class HTTPClientMock: HTTPClient { From abbac6eb4120af8ec07686e372d4c02cb68b6b47 Mon Sep 17 00:00:00 2001 From: Radek Novak Date: Mon, 22 May 2023 18:08:30 +0200 Subject: [PATCH 10/33] Remove unnecessary imports --- Sources/WalletConnectEcho/EchoClientFactory.swift | 1 - .../WalletConnectEcho/Register/EchoRegisterService.swift | 1 - Sources/WalletConnectEcho/Register/EchoService.swift | 1 - Sources/WalletConnectIdentity/IdentityClientFactory.swift | 1 - Sources/WalletConnectIdentity/IdentityKeyAPI.swift | 1 - .../WalletConnectIdentity/IdentityNetworkService.swift | 1 - Sources/WalletConnectNetworking/NetworkingImports.swift | 1 + .../Ethereum/ContractCall/RPCService.swift | 1 - .../Ethereum/EIP1271/EIP1271Verifier.swift | 1 - .../Ethereum/ENS/ENSRegistryContract.swift | 1 - .../WalletConnectSigner/Ethereum/ENS/ENSResolver.swift | 1 - .../Ethereum/ENS/ENSResolverContract.swift | 1 - .../Ethereum/ENS/ENSResolverFactory.swift | 1 - .../Verifier/MessageVerifierFactory.swift | 1 - Sources/WalletConnectVerify/OriginVerifier.swift | 1 - Sources/WalletConnectVerify/Register/VerifyService.swift | 1 - Sources/Web3Modal/Extensions/Collection.swift | 8 ++++++++ Sources/Web3Modal/Modal/ModalInteractor.swift | 1 - Sources/Web3Modal/Modal/ModalSheet.swift | 5 ++--- Sources/Web3Modal/Modal/ModalViewModel.swift | 4 +++- Tests/AuthTests/Stubs/MessageSignerMock.swift | 1 - Tests/TestingUtils/Mocks/HTTPClientMock.swift | 2 +- 22 files changed, 15 insertions(+), 22 deletions(-) create mode 100644 Sources/Web3Modal/Extensions/Collection.swift diff --git a/Sources/WalletConnectEcho/EchoClientFactory.swift b/Sources/WalletConnectEcho/EchoClientFactory.swift index 9b431d045..43bdc82b7 100644 --- a/Sources/WalletConnectEcho/EchoClientFactory.swift +++ b/Sources/WalletConnectEcho/EchoClientFactory.swift @@ -1,5 +1,4 @@ import Foundation -import HTTPClient public struct EchoClientFactory { public static func create(projectId: String, diff --git a/Sources/WalletConnectEcho/Register/EchoRegisterService.swift b/Sources/WalletConnectEcho/Register/EchoRegisterService.swift index 50bb41c0b..e7e27e68f 100644 --- a/Sources/WalletConnectEcho/Register/EchoRegisterService.swift +++ b/Sources/WalletConnectEcho/Register/EchoRegisterService.swift @@ -1,5 +1,4 @@ import Foundation -import HTTPClient actor EchoRegisterService { private let httpClient: HTTPClient diff --git a/Sources/WalletConnectEcho/Register/EchoService.swift b/Sources/WalletConnectEcho/Register/EchoService.swift index 381b9c9ef..44c328101 100644 --- a/Sources/WalletConnectEcho/Register/EchoService.swift +++ b/Sources/WalletConnectEcho/Register/EchoService.swift @@ -1,5 +1,4 @@ import Foundation -import HTTPClient enum EchoAPI: HTTPService { case register(clientId: String, token: String, projectId: String, environment: APNSEnvironment, auth: String) diff --git a/Sources/WalletConnectIdentity/IdentityClientFactory.swift b/Sources/WalletConnectIdentity/IdentityClientFactory.swift index 6a6d9a44b..236a939f7 100644 --- a/Sources/WalletConnectIdentity/IdentityClientFactory.swift +++ b/Sources/WalletConnectIdentity/IdentityClientFactory.swift @@ -1,5 +1,4 @@ import Foundation -import HTTPClient public final class IdentityClientFactory { diff --git a/Sources/WalletConnectIdentity/IdentityKeyAPI.swift b/Sources/WalletConnectIdentity/IdentityKeyAPI.swift index dbc3d8780..b99d7165c 100644 --- a/Sources/WalletConnectIdentity/IdentityKeyAPI.swift +++ b/Sources/WalletConnectIdentity/IdentityKeyAPI.swift @@ -1,5 +1,4 @@ import Foundation -import HTTPClient enum IdentityKeyAPI: HTTPService { diff --git a/Sources/WalletConnectIdentity/IdentityNetworkService.swift b/Sources/WalletConnectIdentity/IdentityNetworkService.swift index 89f3f2808..9fc27b58b 100644 --- a/Sources/WalletConnectIdentity/IdentityNetworkService.swift +++ b/Sources/WalletConnectIdentity/IdentityNetworkService.swift @@ -1,5 +1,4 @@ import Foundation -import HTTPClient actor IdentityNetworkService: IdentityNetworking { diff --git a/Sources/WalletConnectNetworking/NetworkingImports.swift b/Sources/WalletConnectNetworking/NetworkingImports.swift index 16d3e3213..73fdb3411 100644 --- a/Sources/WalletConnectNetworking/NetworkingImports.swift +++ b/Sources/WalletConnectNetworking/NetworkingImports.swift @@ -1,3 +1,4 @@ #if !CocoaPods @_exported import WalletConnectRelay +@_exported import HTTPClient #endif diff --git a/Sources/WalletConnectSigner/Ethereum/ContractCall/RPCService.swift b/Sources/WalletConnectSigner/Ethereum/ContractCall/RPCService.swift index ed1a10cd9..eb6db47e7 100644 --- a/Sources/WalletConnectSigner/Ethereum/ContractCall/RPCService.swift +++ b/Sources/WalletConnectSigner/Ethereum/ContractCall/RPCService.swift @@ -1,5 +1,4 @@ import Foundation -import HTTPClient struct RPCService: HTTPService { let data: Data diff --git a/Sources/WalletConnectSigner/Ethereum/EIP1271/EIP1271Verifier.swift b/Sources/WalletConnectSigner/Ethereum/EIP1271/EIP1271Verifier.swift index 29127d7ac..bd3c318da 100644 --- a/Sources/WalletConnectSigner/Ethereum/EIP1271/EIP1271Verifier.swift +++ b/Sources/WalletConnectSigner/Ethereum/EIP1271/EIP1271Verifier.swift @@ -1,5 +1,4 @@ import Foundation -import HTTPClient actor EIP1271Verifier { private let projectId: String diff --git a/Sources/WalletConnectSigner/Ethereum/ENS/ENSRegistryContract.swift b/Sources/WalletConnectSigner/Ethereum/ENS/ENSRegistryContract.swift index f576fdab2..1e6ba1855 100644 --- a/Sources/WalletConnectSigner/Ethereum/ENS/ENSRegistryContract.swift +++ b/Sources/WalletConnectSigner/Ethereum/ENS/ENSRegistryContract.swift @@ -1,5 +1,4 @@ import Foundation -import HTTPClient actor ENSRegistryContract { diff --git a/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolver.swift b/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolver.swift index 634d688e8..0355e5845 100644 --- a/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolver.swift +++ b/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolver.swift @@ -1,5 +1,4 @@ import Foundation -import HTTPClient public actor ENSResolver { diff --git a/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolverContract.swift b/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolverContract.swift index 0cd77fb6e..e91f17972 100644 --- a/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolverContract.swift +++ b/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolverContract.swift @@ -1,5 +1,4 @@ import Foundation -import HTTPClient actor ENSResolverContract { diff --git a/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolverFactory.swift b/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolverFactory.swift index 00d60a0d5..0f85ada83 100644 --- a/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolverFactory.swift +++ b/Sources/WalletConnectSigner/Ethereum/ENS/ENSResolverFactory.swift @@ -1,5 +1,4 @@ import Foundation -import HTTPClient public final class ENSResolverFactory { diff --git a/Sources/WalletConnectSigner/Verifier/MessageVerifierFactory.swift b/Sources/WalletConnectSigner/Verifier/MessageVerifierFactory.swift index a59254fae..2dcc4255c 100644 --- a/Sources/WalletConnectSigner/Verifier/MessageVerifierFactory.swift +++ b/Sources/WalletConnectSigner/Verifier/MessageVerifierFactory.swift @@ -1,5 +1,4 @@ import Foundation -import HTTPClient public struct MessageVerifierFactory { diff --git a/Sources/WalletConnectVerify/OriginVerifier.swift b/Sources/WalletConnectVerify/OriginVerifier.swift index f7aa4960f..039cccecf 100644 --- a/Sources/WalletConnectVerify/OriginVerifier.swift +++ b/Sources/WalletConnectVerify/OriginVerifier.swift @@ -1,5 +1,4 @@ import Foundation -import HTTPClient public final class OriginVerifier { enum Errors: Error { diff --git a/Sources/WalletConnectVerify/Register/VerifyService.swift b/Sources/WalletConnectVerify/Register/VerifyService.swift index 9580f1727..5cf720118 100644 --- a/Sources/WalletConnectVerify/Register/VerifyService.swift +++ b/Sources/WalletConnectVerify/Register/VerifyService.swift @@ -1,5 +1,4 @@ import Foundation -import HTTPClient enum VerifyAPI: HTTPService { case resolve(assertionId: String) diff --git a/Sources/Web3Modal/Extensions/Collection.swift b/Sources/Web3Modal/Extensions/Collection.swift new file mode 100644 index 000000000..4fbe17dcd --- /dev/null +++ b/Sources/Web3Modal/Extensions/Collection.swift @@ -0,0 +1,8 @@ +import Foundation + +extension Collection { + /// Returns the element at the specified index if it is within bounds, otherwise nil. + subscript (safe index: Index) -> Element? { + return indices.contains(index) ? self[index] : nil + } +} diff --git a/Sources/Web3Modal/Modal/ModalInteractor.swift b/Sources/Web3Modal/Modal/ModalInteractor.swift index 4ad128740..69ac7ea41 100644 --- a/Sources/Web3Modal/Modal/ModalInteractor.swift +++ b/Sources/Web3Modal/Modal/ModalInteractor.swift @@ -1,7 +1,6 @@ import WalletConnectPairing import WalletConnectSign import Combine -import HTTPClient extension ModalSheet { final class Interactor { diff --git a/Sources/Web3Modal/Modal/ModalSheet.swift b/Sources/Web3Modal/Modal/ModalSheet.swift index 06ec57a84..19928cb65 100644 --- a/Sources/Web3Modal/Modal/ModalSheet.swift +++ b/Sources/Web3Modal/Modal/ModalSheet.swift @@ -118,12 +118,11 @@ public struct ModalSheet: View { @ViewBuilder private func gridItem(for index: Int) -> some View { - let wallet: Listing? = viewModel.wallets.indices.contains(index) ? viewModel.wallets[index] : nil - let walletUrl: URL? = wallet != nil ? viewModel.imageUrl(for: wallet!) : nil + let wallet: Listing? = viewModel.wallets[safe: index] if #available(iOS 15.0, *) { VStack { - AsyncImage(url: walletUrl) { image in + AsyncImage(url: viewModel.imageUrl(for: wallet)) { image in image .resizable() .scaledToFit() diff --git a/Sources/Web3Modal/Modal/ModalViewModel.swift b/Sources/Web3Modal/Modal/ModalViewModel.swift index 3f6e32b25..39682c72b 100644 --- a/Sources/Web3Modal/Modal/ModalViewModel.swift +++ b/Sources/Web3Modal/Modal/ModalViewModel.swift @@ -85,7 +85,9 @@ extension ModalSheet { UIPasteboard.general.string = uri } - func imageUrl(for listing: Listing) -> URL? { + func imageUrl(for listing: Listing?) -> URL? { + guard let listing = listing else { return nil } + let urlString = "https://explorer-api.walletconnect.com/v3/logo/md/\(listing.imageId)?projectId=\(projectId)" return URL(string: urlString) diff --git a/Tests/AuthTests/Stubs/MessageSignerMock.swift b/Tests/AuthTests/Stubs/MessageSignerMock.swift index 7b1b116de..3809c2dbb 100644 --- a/Tests/AuthTests/Stubs/MessageSignerMock.swift +++ b/Tests/AuthTests/Stubs/MessageSignerMock.swift @@ -1,5 +1,4 @@ import Foundation -import HTTPClient @testable import WalletConnectSigner extension MessageVerifier { diff --git a/Tests/TestingUtils/Mocks/HTTPClientMock.swift b/Tests/TestingUtils/Mocks/HTTPClientMock.swift index 5cc5d8096..4746bed92 100644 --- a/Tests/TestingUtils/Mocks/HTTPClientMock.swift +++ b/Tests/TestingUtils/Mocks/HTTPClientMock.swift @@ -1,5 +1,5 @@ import Foundation -import HTTPClient +@testable import HTTPClient public final class HTTPClientMock: HTTPClient { From afdc8cdbaf99ad741a7716f8475339c80a92441d Mon Sep 17 00:00:00 2001 From: Radek Novak Date: Mon, 22 May 2023 21:03:17 +0200 Subject: [PATCH 11/33] Add HttpClient to podspec --- WalletConnectSwiftV2.podspec | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/WalletConnectSwiftV2.podspec b/WalletConnectSwiftV2.podspec index 534b4cabd..dcad13934 100644 --- a/WalletConnectSwiftV2.podspec +++ b/WalletConnectSwiftV2.podspec @@ -60,7 +60,7 @@ Pod::Spec.new do |spec| osx_deployment_target = '10.15' tvos_deployment_target = '13.0' - spec.swift_versions = '5.3' + spec.swift_versions = '5.5' spec.pod_target_xcconfig = { 'OTHER_SWIFT_FLAGS' => '-DCocoaPods' @@ -132,6 +132,7 @@ Pod::Spec.new do |spec| spec.subspec 'WalletConnectNetworking' do |ss| ss.source_files = 'Sources/WalletConnectNetworking/**/*.{h,m,swift}' ss.dependency 'WalletConnectSwiftV2/WalletConnectRelay' + ss.dependency 'WalletConnectSwiftV2/HTTPClient' end spec.subspec 'WalletConnectPairing' do |ss| @@ -177,5 +178,8 @@ Pod::Spec.new do |spec| ss.source_files = 'Sources/JSONRPC/**/*' ss.dependency 'WalletConnectSwiftV2/Commons' end - + + spec.subspec 'HTTPClient' do |ss| + ss.source_files = 'Sources/HTTPClient/**/*' + end end From 94a5ae1bdbbee11f73c6d62eb77089286884efdc Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 23 May 2023 08:30:53 +0200 Subject: [PATCH 12/33] add propose test --- Example/IntegrationTests/Push/PushTests.swift | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/Example/IntegrationTests/Push/PushTests.swift b/Example/IntegrationTests/Push/PushTests.swift index efb2a5f1b..725f4ae89 100644 --- a/Example/IntegrationTests/Push/PushTests.swift +++ b/Example/IntegrationTests/Push/PushTests.swift @@ -127,9 +127,27 @@ final class PushTests: XCTestCase { wait(for: [expectation], timeout: InputConfig.defaultTimeout) } - func testPushPropose() async { - - } +// func testPushPropose() async { +// let expectation = expectation(description: "expects dapp to receive error response") +// +// let uri = try! await dappPairingClient.create() +// try! await walletPairingClient.pair(uri: uri) +// try! await dappPushClient.propose(account: Account.stub(), topic: uri.topic) +// +// walletPushClient.requestPublisher.sink { [unowned self] (id, _, _) in +// Task(priority: .high) { try! await walletPushClient.approvePropose(id: id, onSign: sign) } +// }.store(in: &publishers) +// +// dappPushClient.proposalResponsePublisher.sink { (result) in +// guard case .success = result else { +// XCTFail() +// return +// } +// expectation.fulfill() +// }.store(in: &publishers) +// wait(for: [expectation], timeout: InputConfig.defaultTimeout) +// +// } func testWalletRejectsPushRequest() async { let expectation = expectation(description: "expects dapp to receive error response") @@ -139,7 +157,6 @@ final class PushTests: XCTestCase { try! await dappPushClient.request(account: Account.stub(), topic: uri.topic) walletPushClient.requestPublisher.sink { [unowned self] (id, _, _) in - Task(priority: .high) { try! await walletPushClient.reject(id: id) } }.store(in: &publishers) From a6f8f734939ead8ca3e20dc2dbf134312fc497b8 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 23 May 2023 10:15:21 +0200 Subject: [PATCH 13/33] savepoint --- Example/IntegrationTests/Push/PushTests.swift | 48 +++++++++---------- .../wc_notifyPropose/NotifyProposer.swift | 1 - .../NotifyProposeResponder.swift | 25 +++++++++- .../PushSubscribeResponseSubscriber.swift | 2 +- .../Client/Wallet/WalletPushClient.swift | 7 ++- .../Wallet/WalletPushClientFactory.swift | 4 +- 6 files changed, 56 insertions(+), 31 deletions(-) diff --git a/Example/IntegrationTests/Push/PushTests.swift b/Example/IntegrationTests/Push/PushTests.swift index 5d5a7662b..34a3da8df 100644 --- a/Example/IntegrationTests/Push/PushTests.swift +++ b/Example/IntegrationTests/Push/PushTests.swift @@ -27,8 +27,8 @@ final class PushTests: XCTestCase { let keychain = KeychainStorageMock() let keyValueStorage = RuntimeKeyValueStorage() - let relayLogger = ConsoleLogger(suffix: prefix + " [Relay]", loggingLevel: .debug) - let pairingLogger = ConsoleLogger(suffix: prefix + " [Pairing]", loggingLevel: .debug) + let relayLogger = ConsoleLogger(suffix: prefix + " [Relay]", loggingLevel: .off) + let pairingLogger = ConsoleLogger(suffix: prefix + " [Pairing]", loggingLevel: .off) let networkingLogger = ConsoleLogger(suffix: prefix + " [Networking]", loggingLevel: .debug) let relayClient = RelayClient( @@ -62,7 +62,7 @@ final class PushTests: XCTestCase { let (pairingClient, networkingInteractor, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix) let pushLogger = ConsoleLogger(suffix: prefix + " [Push]", loggingLevel: .debug) dappPairingClient = pairingClient - dappPushClient = DappPushClientFactory.create(metadata: AppMetadata(name: name, description: "", url: "", icons: [""]), + dappPushClient = DappPushClientFactory.create(metadata: AppMetadata(name: "GM Dapp", description: "", url: "https://gm-dapp-xi.vercel.app/", icons: []), logger: pushLogger, keyValueStorage: keyValueStorage, keychainStorage: keychain, @@ -127,27 +127,27 @@ final class PushTests: XCTestCase { wait(for: [expectation], timeout: InputConfig.defaultTimeout) } -// func testPushPropose() async { -// let expectation = expectation(description: "expects dapp to receive error response") -// -// let uri = try! await dappPairingClient.create() -// try! await walletPairingClient.pair(uri: uri) -// try! await dappPushClient.propose(account: Account.stub(), topic: uri.topic) -// -// walletPushClient.requestPublisher.sink { [unowned self] (id, _, _) in -// Task(priority: .high) { try! await walletPushClient.approvePropose(id: id, onSign: sign) } -// }.store(in: &publishers) -// -// dappPushClient.proposalResponsePublisher.sink { (result) in -// guard case .success = result else { -// XCTFail() -// return -// } -// expectation.fulfill() -// }.store(in: &publishers) -// wait(for: [expectation], timeout: InputConfig.defaultTimeout) -// -// } + func testPushPropose() async { + let expectation = expectation(description: "expects dapp to receive error response") + + let uri = try! await dappPairingClient.create() + try! await walletPairingClient.pair(uri: uri) + try! await dappPushClient.propose(account: Account.stub(), topic: uri.topic) + + walletPushClient.requestPublisher.sink { [unowned self] (id, _, _) in + Task(priority: .high) { try! await walletPushClient.approvePropose(id: id, onSign: sign) } + }.store(in: &publishers) + + dappPushClient.proposalResponsePublisher.sink { (result) in + guard case .success = result else { + XCTFail() + return + } + expectation.fulfill() + }.store(in: &publishers) + wait(for: [expectation], timeout: InputConfig.defaultTimeout) + + } func testWalletRejectsPushRequest() async { let expectation = expectation(description: "expects dapp to receive error response") diff --git a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposer.swift b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposer.swift index 69d6d9958..3a5231993 100644 --- a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposer.swift +++ b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposer.swift @@ -24,7 +24,6 @@ class NotifyProposer { let responseTopic = publicKey.rawRepresentation.sha256().toHexString() try kms.setPublicKey(publicKey: publicKey, for: responseTopic) - let params = NotifyProposeParams(publicKey: publicKey.hexRepresentation, metadata: metadata, account: account, scope: "") let request = RPCRequest(method: protocolMethod.method, params: params) try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift index 7a8a4124f..3ab3866d6 100644 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift @@ -12,6 +12,7 @@ class NotifyProposeResponder { private let logger: ConsoleLogging private let pushSubscribeRequester: PushSubscribeRequester private let rpcHistory: RPCHistory + private let subscriptionResponsePublisher: AnyPublisher, Never> private var publishers = [AnyCancellable]() @@ -19,21 +20,38 @@ class NotifyProposeResponder { kms: KeyManagementServiceProtocol, logger: ConsoleLogging, pushSubscribeRequester: PushSubscribeRequester, - rpcHistory: RPCHistory) { + rpcHistory: RPCHistory, + pushSubscribeResponseSubscriber: PushSubscribeResponseSubscriber) { self.networkingInteractor = networkingInteractor self.kms = kms self.logger = logger self.pushSubscribeRequester = pushSubscribeRequester + self.subscriptionResponsePublisher = pushSubscribeResponseSubscriber.subscriptionPublisher self.rpcHistory = rpcHistory } - func respond(requestId: RPCID, onSign: @escaping SigningCallback) async throws { + func approve(requestId: RPCID, onSign: @escaping SigningCallback) async throws { + + logger.debug("NotifyProposeResponder: approving proposal") guard let requestRecord = rpcHistory.get(recordId: requestId) else { throw Errors.recordForIdNotFound } let proposal = try requestRecord.request.params!.get(NotifyProposeParams.self) let subscriptionAuthWrapper = try await pushSubscribeRequester.subscribe(metadata: proposal.metadata, account: proposal.account, onSign: onSign) + + await subscriptionResponsePublisher + .first() // Wait for the first value to be published + .sink { _ in + // Continue code execution here + do { + try await networkingInteractor.respond(topic: responseTopic, response: response, protocolMethod: protocolMethod, envelopeType: .type1(pubKey: keys.publicKey.rawRepresentation)) + kms.deleteSymmetricKey(for: responseTopic) + } catch { + // Handle any errors that occur during response or key deletion + } + }.store(in: publishers) + guard let peerPublicKey = try? AgreementPublicKey(hex: proposal.publicKey) else { throw Errors.malformedRequestParams } @@ -42,11 +60,14 @@ class NotifyProposeResponder { let keys = try generateAgreementKeys(peerPublicKey: peerPublicKey) + try kms.setSymmetricKey(keys.sharedKey, for: responseTopic) + let response = RPCResponse(id: requestId, result: subscriptionAuthWrapper) let protocolMethod = NotifyProposeProtocolMethod() try await networkingInteractor.respond(topic: responseTopic, response: response, protocolMethod: protocolMethod, envelopeType: .type1(pubKey: keys.publicKey.rawRepresentation)) + kms.deleteSymmetricKey(for: responseTopic) } private func generateAgreementKeys(peerPublicKey: AgreementPublicKey) throws -> AgreementKeys { diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeResponseSubscriber.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeResponseSubscriber.swift index 0fb0e036e..9b3858c95 100644 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeResponseSubscriber.swift +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeResponseSubscriber.swift @@ -43,7 +43,7 @@ class PushSubscribeResponseSubscriber { networkingInteractor.responseSubscription(on: protocolMethod) .sink {[unowned self] (payload: ResponseSubscriptionPayload) in Task(priority: .high) { - logger.debug("Received Push Subscribe response") + logger.debug("PushSubscribeResponseSubscriber: Received Push Subscribe response") guard let responseKeys = kms.getAgreementSecret(for: payload.topic) else { logger.debug("PushSubscribeResponseSubscriber: no symmetric key for topic \(payload.topic)") diff --git a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift index 436a93711..8ad5daad8 100644 --- a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift +++ b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift @@ -60,6 +60,7 @@ public class WalletPushClient { private let pushSubscribeResponseSubscriber: PushSubscribeResponseSubscriber private let notifyUpdateRequester: NotifyUpdateRequester private let notifyUpdateResponseSubscriber: NotifyUpdateResponseSubscriber + private let notifyProposeResponder: NotifyProposeResponder init(logger: ConsoleLogging, kms: KeyManagementServiceProtocol, @@ -76,7 +77,8 @@ public class WalletPushClient { pushSubscribeRequester: PushSubscribeRequester, pushSubscribeResponseSubscriber: PushSubscribeResponseSubscriber, notifyUpdateRequester: NotifyUpdateRequester, - notifyUpdateResponseSubscriber: NotifyUpdateResponseSubscriber + notifyUpdateResponseSubscriber: NotifyUpdateResponseSubscriber, + notifyProposeResponder: NotifyProposeResponder ) { self.logger = logger self.pairingRegisterer = pairingRegisterer @@ -93,6 +95,7 @@ public class WalletPushClient { self.pushSubscribeResponseSubscriber = pushSubscribeResponseSubscriber self.notifyUpdateRequester = notifyUpdateRequester self.notifyUpdateResponseSubscriber = notifyUpdateResponseSubscriber + self.notifyProposeResponder = notifyProposeResponder setupSubscriptions() } @@ -106,7 +109,7 @@ public class WalletPushClient { // rename method after approve deprication public func approvePropose(id: RPCID, onSign: @escaping SigningCallback) async throws { - try await proposeResponder.respond(requestId: id, onSign: onSign) + try await notifyProposeResponder.approve(requestId: id, onSign: onSign) } public func reject(id: RPCID) async throws { diff --git a/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift b/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift index fd8d9739c..bc50c752a 100644 --- a/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift +++ b/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift @@ -64,6 +64,7 @@ public struct WalletPushClientFactory { let notifyUpdateRequester = NotifyUpdateRequester(keyserverURL: keyserverURL, identityClient: identityClient, networkingInteractor: networkInteractor, logger: logger, subscriptionsStore: subscriptionStore) let notifyUpdateResponseSubscriber = NotifyUpdateResponseSubscriber(networkingInteractor: networkInteractor, logger: logger, subscriptionScopeProvider: subscriptionScopeProvider, subscriptionsStore: subscriptionStore) + let notifyProposeResponder = NotifyProposeResponder(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushSubscribeRequester: pushSubscribeRequester, rpcHistory: history, pushSubscribeResponseSubscriber: pushSubscribeResponseSubscriber) return WalletPushClient( logger: logger, @@ -81,7 +82,8 @@ public struct WalletPushClientFactory { pushSubscribeRequester: pushSubscribeRequester, pushSubscribeResponseSubscriber: pushSubscribeResponseSubscriber, notifyUpdateRequester: notifyUpdateRequester, - notifyUpdateResponseSubscriber: notifyUpdateResponseSubscriber + notifyUpdateResponseSubscriber: notifyUpdateResponseSubscriber, + notifyProposeResponder: notifyProposeResponder ) } } From c33dc009017554ed3a85cbedd15d35aeb40e19d6 Mon Sep 17 00:00:00 2001 From: Radek Novak Date: Tue, 23 May 2023 15:11:35 +0200 Subject: [PATCH 14/33] Update podspec --- WalletConnectSwiftV2.podspec | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/WalletConnectSwiftV2.podspec b/WalletConnectSwiftV2.podspec index dcad13934..c224bae47 100644 --- a/WalletConnectSwiftV2.podspec +++ b/WalletConnectSwiftV2.podspec @@ -60,7 +60,7 @@ Pod::Spec.new do |spec| osx_deployment_target = '10.15' tvos_deployment_target = '13.0' - spec.swift_versions = '5.5' + spec.swift_versions = '5.3' spec.pod_target_xcconfig = { 'OTHER_SWIFT_FLAGS' => '-DCocoaPods' @@ -145,11 +145,6 @@ Pod::Spec.new do |spec| ss.platform = :ios end - spec.subspec 'WalletConnectNetworking' do |ss| - ss.source_files = 'Sources/WalletConnectNetworking/**/*.{h,m,swift}' - ss.dependency 'WalletConnectSwiftV2/WalletConnectRelay' - end - spec.subspec 'WalletConnectRelay' do |ss| ss.source_files = 'Sources/WalletConnectRelay/**/*.{h,m,swift}' ss.dependency 'WalletConnectSwiftV2/WalletConnectJWT' @@ -161,25 +156,25 @@ Pod::Spec.new do |spec| end spec.subspec 'WalletConnectUtils' do |ss| - ss.source_files = 'Sources/WalletConnectUtils/**/*' + ss.source_files = 'Sources/WalletConnectUtils/**/*.{h,m,swift}' ss.dependency 'WalletConnectSwiftV2/JSONRPC' end spec.subspec 'WalletConnectKMS' do |ss| - ss.source_files = 'Sources/WalletConnectKMS/**/*' + ss.source_files = 'Sources/WalletConnectKMS/**/*.{h,m,swift}' ss.dependency 'WalletConnectSwiftV2/WalletConnectUtils' end spec.subspec 'Commons' do |ss| - ss.source_files = 'Sources/Commons/**/*' + ss.source_files = 'Sources/Commons/**/*.{h,m,swift}' end spec.subspec 'JSONRPC' do |ss| - ss.source_files = 'Sources/JSONRPC/**/*' + ss.source_files = 'Sources/JSONRPC/**/*.{h,m,swift}' ss.dependency 'WalletConnectSwiftV2/Commons' end spec.subspec 'HTTPClient' do |ss| - ss.source_files = 'Sources/HTTPClient/**/*' + ss.source_files = 'Sources/HTTPClient/**/*.{h,m,swift}' end end From 60b0c8b0eafc6de2736dfa69a9d606e0adbf6e9e Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 23 May 2023 15:32:06 +0200 Subject: [PATCH 15/33] update approve method --- .../NotifyProposeResponder.swift | 31 ++++++++++--------- .../PushSubscribeRequester.swift | 4 +-- .../PushSubscribeResponseSubscriber.swift | 2 ++ 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift index 3ab3866d6..e2f624e53 100644 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift @@ -12,7 +12,7 @@ class NotifyProposeResponder { private let logger: ConsoleLogging private let pushSubscribeRequester: PushSubscribeRequester private let rpcHistory: RPCHistory - private let subscriptionResponsePublisher: AnyPublisher, Never> + private var subscriptionResponsePublisher: AnyPublisher, Never> private var publishers = [AnyCancellable]() @@ -21,7 +21,8 @@ class NotifyProposeResponder { logger: ConsoleLogging, pushSubscribeRequester: PushSubscribeRequester, rpcHistory: RPCHistory, - pushSubscribeResponseSubscriber: PushSubscribeResponseSubscriber) { + pushSubscribeResponseSubscriber: PushSubscribeResponseSubscriber + ) { self.networkingInteractor = networkingInteractor self.kms = kms self.logger = logger @@ -39,18 +40,18 @@ class NotifyProposeResponder { let subscriptionAuthWrapper = try await pushSubscribeRequester.subscribe(metadata: proposal.metadata, account: proposal.account, onSign: onSign) - - await subscriptionResponsePublisher - .first() // Wait for the first value to be published - .sink { _ in - // Continue code execution here - do { - try await networkingInteractor.respond(topic: responseTopic, response: response, protocolMethod: protocolMethod, envelopeType: .type1(pubKey: keys.publicKey.rawRepresentation)) - kms.deleteSymmetricKey(for: responseTopic) - } catch { - // Handle any errors that occur during response or key deletion - } - }.store(in: publishers) + try await withCheckedThrowingContinuation { continuation in + subscriptionResponsePublisher + .first() + .sink(receiveValue: { value in + switch value { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + }).store(in: &publishers) + } guard let peerPublicKey = try? AgreementPublicKey(hex: proposal.publicKey) else { throw Errors.malformedRequestParams @@ -66,6 +67,8 @@ class NotifyProposeResponder { let protocolMethod = NotifyProposeProtocolMethod() + logger.debug("NotifyProposeResponder: sending response") + try await networkingInteractor.respond(topic: responseTopic, response: response, protocolMethod: protocolMethod, envelopeType: .type1(pubKey: keys.publicKey.rawRepresentation)) kms.deleteSymmetricKey(for: responseTopic) } diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift index a7ca4911f..842f57707 100644 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift @@ -90,9 +90,9 @@ class PushSubscribeRequester { let keys = try kms.performKeyAgreement(selfPublicKey: selfPubKey, peerPublicKey: peerPublicKey.hexRepresentation) return keys } - +// todo private func createJWTWrapper(subscriptionAccount: Account, dappUrl: String) throws -> SubscriptionJWTPayload.Wrapper { - let jwtPayload = SubscriptionJWTPayload(keyserver: keyserverURL, subscriptionAccount: subscriptionAccount, dappUrl: dappUrl, scope: "") + let jwtPayload = SubscriptionJWTPayload(keyserver: keyserverURL, subscriptionAccount: subscriptionAccount, dappUrl: dappUrl, scope: "asdsadsadas") return try identityClient.signAndCreateWrapper( payload: jwtPayload, account: subscriptionAccount diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeResponseSubscriber.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeResponseSubscriber.swift index 9b3858c95..b5aa6af71 100644 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeResponseSubscriber.swift +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeResponseSubscriber.swift @@ -82,6 +82,7 @@ class PushSubscribeResponseSubscriber { guard let metadata = metadata else { logger.debug("PushSubscribeResponseSubscriber: no metadata for topic: \(pushSubscriptionTopic!)") + subscriptionPublisherSubject.send(.failure(Errors.couldNotCreateSubscription)) return } dappsMetadataStore.delete(forKey: payload.topic) @@ -93,6 +94,7 @@ class PushSubscribeResponseSubscriber { logger.debug("PushSubscribeResponseSubscriber: unsubscribing response topic: \(payload.topic)") networkingInteractor.unsubscribe(topic: payload.topic) + subscriptionPublisherSubject.send(.success(pushSubscription)) } }.store(in: &publishers) From cfe36b7bdad4b3bd0e277fe9839a2937d05daf39 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Tue, 23 May 2023 15:39:49 +0200 Subject: [PATCH 16/33] fix bug - subscribe scope --- .../wc_pushSubscribe/PushSubscribeRequester.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift index 842f57707..2a4f12782 100644 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift @@ -62,7 +62,7 @@ class PushSubscribeRequester { let protocolMethod = PushSubscribeProtocolMethod() - let subscriptionAuthWrapper = try createJWTWrapper(subscriptionAccount: account, dappUrl: dappUrl) + let subscriptionAuthWrapper = try await createJWTWrapper(subscriptionAccount: account, dappUrl: dappUrl) let request = RPCRequest(method: protocolMethod.method, params: subscriptionAuthWrapper) logger.debug("PushSubscribeRequester: subscribing to response topic: \(responseTopic)") @@ -90,9 +90,11 @@ class PushSubscribeRequester { let keys = try kms.performKeyAgreement(selfPublicKey: selfPubKey, peerPublicKey: peerPublicKey.hexRepresentation) return keys } -// todo - private func createJWTWrapper(subscriptionAccount: Account, dappUrl: String) throws -> SubscriptionJWTPayload.Wrapper { - let jwtPayload = SubscriptionJWTPayload(keyserver: keyserverURL, subscriptionAccount: subscriptionAccount, dappUrl: dappUrl, scope: "asdsadsadas") + + private func createJWTWrapper(subscriptionAccount: Account, dappUrl: String) async throws -> SubscriptionJWTPayload.Wrapper { + let types = try await subscriptionScopeProvider.getSubscriptionScope(dappUrl: dappUrl) + let scope = types.map{$0.name}.joined(separator: " ") + let jwtPayload = SubscriptionJWTPayload(keyserver: keyserverURL, subscriptionAccount: subscriptionAccount, dappUrl: dappUrl, scope: scope) return try identityClient.signAndCreateWrapper( payload: jwtPayload, account: subscriptionAccount From 4d7f70e3974ab8296a35c3c261929028fb82b2ac Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 24 May 2023 08:09:51 +0200 Subject: [PATCH 17/33] update rpc method according to spec change --- .../Client/Dapp/wc_notifyPropose/NotifyProposer.swift | 2 +- Sources/WalletConnectPush/RPCRequests/NotifyProposeParams.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposer.swift b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposer.swift index 3a5231993..02c3720f2 100644 --- a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposer.swift +++ b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposer.swift @@ -24,7 +24,7 @@ class NotifyProposer { let responseTopic = publicKey.rawRepresentation.sha256().toHexString() try kms.setPublicKey(publicKey: publicKey, for: responseTopic) - let params = NotifyProposeParams(publicKey: publicKey.hexRepresentation, metadata: metadata, account: account, scope: "") + let params = NotifyProposeParams(publicKey: publicKey.hexRepresentation, metadata: metadata, account: account, scope: []) let request = RPCRequest(method: protocolMethod.method, params: params) try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) try await networkingInteractor.subscribe(topic: responseTopic) diff --git a/Sources/WalletConnectPush/RPCRequests/NotifyProposeParams.swift b/Sources/WalletConnectPush/RPCRequests/NotifyProposeParams.swift index 9ca025876..2d33ca7d7 100644 --- a/Sources/WalletConnectPush/RPCRequests/NotifyProposeParams.swift +++ b/Sources/WalletConnectPush/RPCRequests/NotifyProposeParams.swift @@ -5,5 +5,5 @@ struct NotifyProposeParams: Codable { let publicKey: String let metadata: AppMetadata let account: Account - let scope: String + let scope: [String] } From cfd488aca2eba1ed64c72d5cba16aee9296daff3 Mon Sep 17 00:00:00 2001 From: Radek Novak Date: Wed, 24 May 2023 15:45:14 +0200 Subject: [PATCH 18/33] Remove unnecessary EnvironmentKey --- Sources/Web3Modal/Environment+ProjectId.swift | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 Sources/Web3Modal/Environment+ProjectId.swift diff --git a/Sources/Web3Modal/Environment+ProjectId.swift b/Sources/Web3Modal/Environment+ProjectId.swift deleted file mode 100644 index 30e272aab..000000000 --- a/Sources/Web3Modal/Environment+ProjectId.swift +++ /dev/null @@ -1,12 +0,0 @@ -import SwiftUI - -private struct ProjectIdKey: EnvironmentKey { - static let defaultValue: String = "" -} - -extension EnvironmentValues { - var projectId: String { - get { self[ProjectIdKey.self] } - set { self[ProjectIdKey.self] = newValue } - } -} From 49137f7b10de0c832d56dc33d90505b9ed86cb36 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 24 May 2023 19:03:00 +0200 Subject: [PATCH 19/33] savepoint --- Example/IntegrationTests/Push/PushTests.swift | 4 ++-- .../Wallet/Main/MainPresenter.swift | 2 +- .../Wallet/Main/Model/TabPage.swift | 12 ++++++------ .../Wallet/PushRequest/PushRequestInteractor.swift | 2 +- .../NotifyProposeResponseSubscriber.swift | 10 ++++++++++ .../wc_notifyPropose/NotifyProposeResponder.swift | 8 ++++++++ 6 files changed, 28 insertions(+), 10 deletions(-) diff --git a/Example/IntegrationTests/Push/PushTests.swift b/Example/IntegrationTests/Push/PushTests.swift index 34a3da8df..e2940fa3b 100644 --- a/Example/IntegrationTests/Push/PushTests.swift +++ b/Example/IntegrationTests/Push/PushTests.swift @@ -27,8 +27,8 @@ final class PushTests: XCTestCase { let keychain = KeychainStorageMock() let keyValueStorage = RuntimeKeyValueStorage() - let relayLogger = ConsoleLogger(suffix: prefix + " [Relay]", loggingLevel: .off) - let pairingLogger = ConsoleLogger(suffix: prefix + " [Pairing]", loggingLevel: .off) + let relayLogger = ConsoleLogger(suffix: prefix + " [Relay]", loggingLevel: .debug) + let pairingLogger = ConsoleLogger(suffix: prefix + " [Pairing]", loggingLevel: .debug) let networkingLogger = ConsoleLogger(suffix: prefix + " [Networking]", loggingLevel: .debug) let relayClient = RelayClient( diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift index ee968adb0..2f0548b02 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift @@ -35,7 +35,7 @@ extension MainPresenter { .receive(on: DispatchQueue.main) .sink { [weak self] request in -// self?.router.present(pushRequest: request) + self?.router.present(pushRequest: request) }.store(in: &disposeBag) interactor.sessionProposalPublisher diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift index d195c90ae..698349ebd 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift @@ -2,15 +2,15 @@ import UIKit enum TabPage: CaseIterable { case wallet -// case notifications + case notifications case web3Inbox var title: String { switch self { case .wallet: return "Apps" -// case .notifications: -// return "Notifications" + case .notifications: + return "Notifications" case .web3Inbox: return "w3i" } @@ -20,8 +20,8 @@ enum TabPage: CaseIterable { switch self { case .wallet: return UIImage(systemName: "house.fill")! -// case .notifications: -// return UIImage(systemName: "bell.fill")! + case .notifications: + return UIImage(systemName: "bell.fill")! case .web3Inbox: return UIImage(systemName: "bell.fill")! } @@ -32,6 +32,6 @@ enum TabPage: CaseIterable { } static var enabledTabs: [TabPage] { - return [.wallet, .web3Inbox] + return [.wallet, .notifications, .web3Inbox] } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift index 2afda2ff4..d3c6e239d 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift @@ -3,7 +3,7 @@ import WalletConnectPush final class PushRequestInteractor { func approve(pushRequest: PushRequest) async throws { - try await Push.wallet.approve(id: pushRequest.id, onSign: Web3InboxSigner.onSing) + try await Push.wallet.approvePropose(id: pushRequest.id, onSign: Web3InboxSigner.onSing) } func reject(pushRequest: PushRequest) async throws { diff --git a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift index d20942288..e8942eee8 100644 --- a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift +++ b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift @@ -23,6 +23,7 @@ class NotifyProposeResponseSubscriber { self.logger = logger self.metadata = metadata subscribeForProposalResponse() + subscribeForProposalErrors() } @@ -56,5 +57,14 @@ class NotifyProposeResponseSubscriber { return PushSubscription(topic: updateTopic, account: payload.request.account, relay: relay, metadata: metadata, scope: [:], expiry: expiry) } + private func subscribeForProposalErrors() { + let protocolMethod = PushRequestProtocolMethod() + networkingInteractor.responseErrorSubscription(on: protocolMethod) + .sink { [unowned self] (payload: ResponseSubscriptionErrorPayload) in + kms.deletePrivateKey(for: payload.request.publicKey) + guard let error = PushError(code: payload.error.code) else { return } + proposalResponsePublisherSubject.send(.failure(error)) + }.store(in: &publishers) + } } diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift index e2f624e53..acd9acaef 100644 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift @@ -73,6 +73,14 @@ class NotifyProposeResponder { kms.deleteSymmetricKey(for: responseTopic) } + func reject(requestId: RPCID) async throws { + logger.debug("NotifyProposeResponder - rejecting notify request") + guard let requestRecord = rpcHistory.get(recordId: requestId) else { throw Errors.recordForIdNotFound } + let pairingTopic = requestRecord.topic + + try await networkingInteractor.respondError(topic: pairingTopic, requestId: requestId, protocolMethod: PushRequestProtocolMethod(), reason: PushError.rejected) + } + private func generateAgreementKeys(peerPublicKey: AgreementPublicKey) throws -> AgreementKeys { let selfPubKey = try kms.createX25519KeyPair() let keys = try kms.performKeyAgreement(selfPublicKey: selfPubKey, peerPublicKey: peerPublicKey.hexRepresentation) From 4081ca192d321360e05841dd4955ae7400f590ed Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 25 May 2023 08:36:41 +0200 Subject: [PATCH 20/33] Pass propose rejection test --- Example/IntegrationTests/Push/PushTests.swift | 42 ++----------------- .../NotifyProposeResponseSubscriber.swift | 4 +- .../Client/Wallet/WalletPushClient.swift | 2 +- 3 files changed, 6 insertions(+), 42 deletions(-) diff --git a/Example/IntegrationTests/Push/PushTests.swift b/Example/IntegrationTests/Push/PushTests.swift index e2940fa3b..b1a2d2038 100644 --- a/Example/IntegrationTests/Push/PushTests.swift +++ b/Example/IntegrationTests/Push/PushTests.swift @@ -91,42 +91,6 @@ final class PushTests: XCTestCase { makeWalletClients() } - func testRequestPush() async { - let expectation = expectation(description: "expects to receive push request") - - let uri = try! await dappPairingClient.create() - try! await walletPairingClient.pair(uri: uri) - try! await dappPushClient.request(account: Account.stub(), topic: uri.topic) - - walletPushClient.requestPublisher.sink { (_, _, _) in - expectation.fulfill() - } - .store(in: &publishers) - wait(for: [expectation], timeout: InputConfig.defaultTimeout) - } - - func testWalletApprovesPushRequest() async { - let expectation = expectation(description: "expects dapp to receive successful response") - - let uri = try! await dappPairingClient.create() - try! await walletPairingClient.pair(uri: uri) - try! await dappPushClient.request(account: Account.stub(), topic: uri.topic) - - walletPushClient.requestPublisher.sink { [unowned self] (id, _, _) in - Task(priority: .high) { try! await walletPushClient.approve(id: id, onSign: sign) } - }.store(in: &publishers) - - dappPushClient.responsePublisher.sink { (_, result) in - guard case .success = result else { - XCTFail() - return - } - expectation.fulfill() - }.store(in: &publishers) - - wait(for: [expectation], timeout: InputConfig.defaultTimeout) - } - func testPushPropose() async { let expectation = expectation(description: "expects dapp to receive error response") @@ -149,18 +113,18 @@ final class PushTests: XCTestCase { } - func testWalletRejectsPushRequest() async { + func testWalletRejectsPushPropose() async { let expectation = expectation(description: "expects dapp to receive error response") let uri = try! await dappPairingClient.create() try! await walletPairingClient.pair(uri: uri) - try! await dappPushClient.request(account: Account.stub(), topic: uri.topic) + try! await dappPushClient.propose(account: Account.stub(), topic: uri.topic) walletPushClient.requestPublisher.sink { [unowned self] (id, _, _) in Task(priority: .high) { try! await walletPushClient.reject(id: id) } }.store(in: &publishers) - dappPushClient.responsePublisher.sink { (_, result) in + dappPushClient.proposalResponsePublisher.sink { (result) in guard case .failure = result else { XCTFail() return diff --git a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift index e8942eee8..ff2e530e5 100644 --- a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift +++ b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift @@ -58,9 +58,9 @@ class NotifyProposeResponseSubscriber { } private func subscribeForProposalErrors() { - let protocolMethod = PushRequestProtocolMethod() + let protocolMethod = NotifyProposeProtocolMethod() networkingInteractor.responseErrorSubscription(on: protocolMethod) - .sink { [unowned self] (payload: ResponseSubscriptionErrorPayload) in + .sink { [unowned self] (payload: ResponseSubscriptionErrorPayload) in kms.deletePrivateKey(for: payload.request.publicKey) guard let error = PushError(code: payload.error.code) else { return } proposalResponsePublisherSubject.send(.failure(error)) diff --git a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift index 8ad5daad8..2818398cb 100644 --- a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift +++ b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift @@ -113,7 +113,7 @@ public class WalletPushClient { } public func reject(id: RPCID) async throws { - try await proposeResponder.respondError(requestId: id) + try await notifyProposeResponder.reject(requestId: id) } public func update(topic: String, scope: Set) async throws { From 67c1cb09561b255e3cbc82920b362d799b624d7d Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 25 May 2023 09:53:15 +0200 Subject: [PATCH 21/33] remove unused dapp services --- .../Pairing/PairingTests.swift | 4 +- Example/IntegrationTests/Push/PushTests.swift | 98 +------------- .../Client/Dapp/DappPushClient.swift | 27 ---- .../Client/Dapp/DappPushClientFactory.swift | 6 - .../Dapp/ProposalResponseSubscriber.swift | 85 ------------ .../Client/Dapp/PushMessageSender.swift | 22 ---- .../Client/Dapp/PushProposer.swift | 32 ----- .../NotifyProposeResponder.swift | 2 +- .../wc_pushRequest/PushRequestResponder.swift | 121 ------------------ .../Client/Wallet/WalletPushClient.swift | 25 +--- .../Wallet/WalletPushClientFactory.swift | 3 - .../PushRequestProtocolMethod.swift | 10 -- .../RPCRequests/PushRequestParams.swift | 7 - 13 files changed, 9 insertions(+), 433 deletions(-) delete mode 100644 Sources/WalletConnectPush/Client/Dapp/ProposalResponseSubscriber.swift delete mode 100644 Sources/WalletConnectPush/Client/Dapp/PushMessageSender.swift delete mode 100644 Sources/WalletConnectPush/Client/Dapp/PushProposer.swift delete mode 100644 Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushRequest/PushRequestResponder.swift delete mode 100644 Sources/WalletConnectPush/ProtocolMethods/PushRequestProtocolMethod.swift delete mode 100644 Sources/WalletConnectPush/RPCRequests/PushRequestParams.swift diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift index d8435d1cd..8ec199b8d 100644 --- a/Example/IntegrationTests/Pairing/PairingTests.swift +++ b/Example/IntegrationTests/Pairing/PairingTests.swift @@ -106,7 +106,7 @@ final class PairingTests: XCTestCase { try! await walletPairingClient.pair(uri: uri) - try! await appPushClient.request(account: Account.stub(), topic: uri.topic) + try! await appPushClient.propose(account: Account.stub(), topic: uri.topic) wait(for: [expectation], timeout: InputConfig.defaultTimeout) } @@ -138,7 +138,7 @@ final class PairingTests: XCTestCase { try! await walletPairingClient.pair(uri: uri) - try! await appPushClient.request(account: Account.stub(), topic: uri.topic) + try! await appPushClient.propose(account: Account.stub(), topic: uri.topic) wait(for: [expectation], timeout: InputConfig.defaultTimeout) } diff --git a/Example/IntegrationTests/Push/PushTests.swift b/Example/IntegrationTests/Push/PushTests.swift index b1a2d2038..34826e30c 100644 --- a/Example/IntegrationTests/Push/PushTests.swift +++ b/Example/IntegrationTests/Push/PushTests.swift @@ -99,7 +99,7 @@ final class PushTests: XCTestCase { try! await dappPushClient.propose(account: Account.stub(), topic: uri.topic) walletPushClient.requestPublisher.sink { [unowned self] (id, _, _) in - Task(priority: .high) { try! await walletPushClient.approvePropose(id: id, onSign: sign) } + Task(priority: .high) { try! await walletPushClient.approve(id: id, onSign: sign) } }.store(in: &publishers) dappPushClient.proposalResponsePublisher.sink { (result) in @@ -135,94 +135,6 @@ final class PushTests: XCTestCase { wait(for: [expectation], timeout: InputConfig.defaultTimeout) } - func testDappSendsPushMessage() async { - let expectation = expectation(description: "expects wallet to receive push message") - let pushMessage = PushMessage.stub() - var pushSubscription: PushSubscription! - let uri = try! await dappPairingClient.create() - try! await walletPairingClient.pair(uri: uri) - try! await dappPushClient.request(account: Account.stub(), topic: uri.topic) - - walletPushClient.requestPublisher.sink { [unowned self] (id, _, _) in - Task(priority: .high) { try! await walletPushClient.approve(id: id, onSign: sign) } - }.store(in: &publishers) - - dappPushClient.responsePublisher.sink { [unowned self] (_, result) in - guard case .success(let result) = result else { - XCTFail() - return - } - pushSubscription = result.pushSubscription - Task(priority: .userInitiated) { try! await dappPushClient.notify(topic: result.pushSubscription.topic, message: pushMessage) } - }.store(in: &publishers) - - walletPushClient.pushMessagePublisher.sink { [unowned self] receivedPushMessageRecord in - let messageHistory = walletPushClient.getMessageHistory(topic: pushSubscription.topic) - XCTAssertEqual(pushMessage, receivedPushMessageRecord.message) - XCTAssertTrue(messageHistory.contains(receivedPushMessageRecord)) - expectation.fulfill() - }.store(in: &publishers) - - - wait(for: [expectation], timeout: InputConfig.defaultTimeout) - - } - - func testWalletDeletePushSubscription() async { - let expectation = expectation(description: "expects to delete push subscription") - let uri = try! await dappPairingClient.create() - try! await walletPairingClient.pair(uri: uri) - try! await dappPushClient.request(account: Account.stub(), topic: uri.topic) - var subscriptionTopic: String! - - walletPushClient.requestPublisher.sink { [unowned self] (id, _, _) in - Task(priority: .high) { try! await walletPushClient.approve(id: id, onSign: sign) } - }.store(in: &publishers) - - dappPushClient.responsePublisher.sink { [unowned self] (_, result) in - guard case .success(let result) = result else { - XCTFail() - return - } - subscriptionTopic = result.pushSubscription.topic - Task(priority: .userInitiated) { try! await walletPushClient.deleteSubscription(topic: result.pushSubscription.topic)} - }.store(in: &publishers) - - dappPushClient.deleteSubscriptionPublisher.sink { topic in - XCTAssertEqual(subscriptionTopic, topic) - expectation.fulfill() - }.store(in: &publishers) - wait(for: [expectation], timeout: InputConfig.defaultTimeout) - } - - func testDappDeletePushSubscription() async { - let expectation = expectation(description: "expects to delete push subscription") - let uri = try! await dappPairingClient.create() - try! await walletPairingClient.pair(uri: uri) - try! await dappPushClient.request(account: Account.stub(), topic: uri.topic) - var subscriptionTopic: String! - - walletPushClient.requestPublisher.sink { [unowned self] (id, _, _) in - Task(priority: .high) { try! await walletPushClient.approve(id: id, onSign: sign) } - }.store(in: &publishers) - - dappPushClient.responsePublisher.sink { [unowned self] (_, result) in - guard case .success(let result) = result else { - XCTFail() - return - } - subscriptionTopic = result.pushSubscription.topic - Task(priority: .userInitiated) { try! await dappPushClient.delete(topic: result.pushSubscription.topic)} - }.store(in: &publishers) - - walletPushClient.deleteSubscriptionPublisher.sink { topic in - XCTAssertEqual(subscriptionTopic, topic) - expectation.fulfill() - }.store(in: &publishers) - wait(for: [expectation], timeout: InputConfig.defaultTimeout) - } - - // Push Subscribe func testWalletCreatesSubscription() async { let expectation = expectation(description: "expects to create push subscription") let metadata = AppMetadata(name: "GM Dapp", description: "", url: "https://gm-dapp-xi.vercel.app/", icons: []) @@ -230,10 +142,10 @@ final class PushTests: XCTestCase { walletPushClient.subscriptionsPublisher .first() .sink { [unowned self] subscriptions in - XCTAssertNotNil(subscriptions.first) - Task { try! await walletPushClient.deleteSubscription(topic: subscriptions.first!.topic) } - expectation.fulfill() - }.store(in: &publishers) + XCTAssertNotNil(subscriptions.first) + Task { try! await walletPushClient.deleteSubscription(topic: subscriptions.first!.topic) } + expectation.fulfill() + }.store(in: &publishers) wait(for: [expectation], timeout: InputConfig.defaultTimeout) } diff --git a/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift b/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift index 0acdbe338..342beebab 100644 --- a/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift +++ b/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift @@ -3,13 +3,6 @@ import Combine import WalletConnectUtils public class DappPushClient { - - private let responsePublisherSubject = PassthroughSubject<(id: RPCID, result: Result), Never>() - - public var responsePublisher: AnyPublisher<(id: RPCID, result: Result), Never> { - responsePublisherSubject.eraseToAnyPublisher() - } - var proposalResponsePublisher: AnyPublisher, Never> { return notifyProposeResponseSubscriber.proposalResponsePublisher } @@ -22,10 +15,7 @@ public class DappPushClient { public let logger: ConsoleLogging - private let pushProposer: PushProposer private let notifyProposer: NotifyProposer - private let pushMessageSender: PushMessageSender - private let proposalResponseSubscriber: ProposalResponseSubscriber private let subscriptionsProvider: SubscriptionsProvider private let deletePushSubscriptionService: DeletePushSubscriptionService private let deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber @@ -34,9 +24,6 @@ public class DappPushClient { init(logger: ConsoleLogging, kms: KeyManagementServiceProtocol, - pushProposer: PushProposer, - proposalResponseSubscriber: ProposalResponseSubscriber, - pushMessageSender: PushMessageSender, subscriptionsProvider: SubscriptionsProvider, deletePushSubscriptionService: DeletePushSubscriptionService, deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber, @@ -44,9 +31,6 @@ public class DappPushClient { notifyProposer: NotifyProposer, notifyProposeResponseSubscriber: NotifyProposeResponseSubscriber) { self.logger = logger - self.pushProposer = pushProposer - self.proposalResponseSubscriber = proposalResponseSubscriber - self.pushMessageSender = pushMessageSender self.subscriptionsProvider = subscriptionsProvider self.deletePushSubscriptionService = deletePushSubscriptionService self.deletePushSubscriptionSubscriber = deletePushSubscriptionSubscriber @@ -56,18 +40,10 @@ public class DappPushClient { setupSubscriptions() } - public func request(account: Account, topic: String) async throws { - try await pushProposer.request(topic: topic, account: account) - } - public func propose(account: Account, topic: String) async throws { try await notifyProposer.propose(topic: topic, account: account) } - public func notify(topic: String, message: PushMessage) async throws { - try await pushMessageSender.request(topic: topic, message: message) - } - public func getActiveSubscriptions() -> [PushSubscription] { subscriptionsProvider.getActiveSubscriptions() } @@ -81,9 +57,6 @@ public class DappPushClient { private extension DappPushClient { func setupSubscriptions() { - proposalResponseSubscriber.onResponse = {[unowned self] (id, result) in - responsePublisherSubject.send((id, result)) - } deletePushSubscriptionSubscriber.onDelete = {[unowned self] topic in deleteSubscriptionPublisherSubject.send(topic) } diff --git a/Sources/WalletConnectPush/Client/Dapp/DappPushClientFactory.swift b/Sources/WalletConnectPush/Client/Dapp/DappPushClientFactory.swift index 6d4a506bc..a74a5dbbb 100644 --- a/Sources/WalletConnectPush/Client/Dapp/DappPushClientFactory.swift +++ b/Sources/WalletConnectPush/Client/Dapp/DappPushClientFactory.swift @@ -18,10 +18,7 @@ public struct DappPushClientFactory { static func create(metadata: AppMetadata, logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, networkInteractor: NetworkInteracting) -> DappPushClient { let kms = KeyManagementService(keychain: keychainStorage) - let pushProposer = PushProposer(networkingInteractor: networkInteractor, kms: kms, appMetadata: metadata, logger: logger) let subscriptionStore = CodableStore(defaults: keyValueStorage, identifier: PushStorageIdntifiers.pushSubscription) - let proposalResponseSubscriber = ProposalResponseSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, metadata: metadata, relay: RelayProtocolOptions(protocol: "irn", data: nil), subscriptionsStore: subscriptionStore) - let pushMessageSender = PushMessageSender(networkingInteractor: networkInteractor, kms: kms, logger: logger) let subscriptionProvider = SubscriptionsProvider(store: subscriptionStore) let deletePushSubscriptionService = DeletePushSubscriptionService(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushSubscriptionStore: subscriptionStore, pushMessagesDatabase: nil) let deletePushSubscriptionSubscriber = DeletePushSubscriptionSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushSubscriptionStore: subscriptionStore) @@ -31,9 +28,6 @@ public struct DappPushClientFactory { return DappPushClient( logger: logger, kms: kms, - pushProposer: pushProposer, - proposalResponseSubscriber: proposalResponseSubscriber, - pushMessageSender: pushMessageSender, subscriptionsProvider: subscriptionProvider, deletePushSubscriptionService: deletePushSubscriptionService, deletePushSubscriptionSubscriber: deletePushSubscriptionSubscriber, diff --git a/Sources/WalletConnectPush/Client/Dapp/ProposalResponseSubscriber.swift b/Sources/WalletConnectPush/Client/Dapp/ProposalResponseSubscriber.swift deleted file mode 100644 index e131f075b..000000000 --- a/Sources/WalletConnectPush/Client/Dapp/ProposalResponseSubscriber.swift +++ /dev/null @@ -1,85 +0,0 @@ -import Foundation -import Combine -import WalletConnectKMS -import WalletConnectNetworking - -class ProposalResponseSubscriber { - enum Errors: Error { - case subscriptionTopicNotDerived - } - private let networkingInteractor: NetworkInteracting - private let kms: KeyManagementServiceProtocol - private let logger: ConsoleLogging - private var publishers = [AnyCancellable]() - private let metadata: AppMetadata - private let relay: RelayProtocolOptions - var onResponse: ((_ id: RPCID, _ result: Result) -> Void)? - private let subscriptionsStore: CodableStore - - init(networkingInteractor: NetworkInteracting, - kms: KeyManagementServiceProtocol, - logger: ConsoleLogging, - metadata: AppMetadata, - relay: RelayProtocolOptions, - subscriptionsStore: CodableStore) { - self.networkingInteractor = networkingInteractor - self.kms = kms - self.logger = logger - self.metadata = metadata - self.relay = relay - self.subscriptionsStore = subscriptionsStore - subscribeForProposalErrors() - subscribeForProposalResponse() - } - - private func subscribeForProposalResponse() { - let protocolMethod = PushRequestProtocolMethod() - networkingInteractor.responseSubscription(on: protocolMethod) - .sink { [unowned self] (payload: ResponseSubscriptionPayload) in - logger.debug("Received Push Proposal response") - Task(priority: .userInitiated) { - do { - let (pushSubscription, jwt) = try await handleResponse(payload: payload) - let result = PushSubscriptionResult(pushSubscription: pushSubscription, subscriptionAuth: jwt) - onResponse?(payload.id, .success(result)) - } catch { - logger.error(error) - } - } - }.store(in: &publishers) - } - - private func handleResponse(payload: ResponseSubscriptionPayload) async throws -> (PushSubscription, String) { - - let jwt = payload.response.jwtString - let (_, claims) = try SubscriptionJWTPayload.decodeAndVerify(from: payload.response) - logger.debug("subscriptionAuth JWT validated") - - guard let subscriptionTopic = payload.derivedTopic else { throw Errors.subscriptionTopicNotDerived } - let expiry = Date(timeIntervalSince1970: TimeInterval(claims.exp)) - - let pushSubscription = PushSubscription(topic: subscriptionTopic, account: payload.request.account, relay: relay, metadata: metadata, scope: [:], expiry: expiry) - logger.debug("Subscribing to Push Subscription topic: \(subscriptionTopic)") - subscriptionsStore.set(pushSubscription, forKey: subscriptionTopic) - try await networkingInteractor.subscribe(topic: subscriptionTopic) - return (pushSubscription, jwt) - } - - private func generateAgreementKeys(peerPublicKeyHex: String, selfpublicKeyHex: String) throws -> String { - let selfPublicKey = try AgreementPublicKey(hex: selfpublicKeyHex) - let keys = try kms.performKeyAgreement(selfPublicKey: selfPublicKey, peerPublicKey: peerPublicKeyHex) - let topic = keys.derivedTopic() - try kms.setAgreementSecret(keys, topic: topic) - return topic - } - - private func subscribeForProposalErrors() { - let protocolMethod = PushRequestProtocolMethod() - networkingInteractor.responseErrorSubscription(on: protocolMethod) - .sink { [unowned self] (payload: ResponseSubscriptionErrorPayload) in - kms.deletePrivateKey(for: payload.request.publicKey) - guard let error = PushError(code: payload.error.code) else { return } - onResponse?(payload.id, .failure(error)) - }.store(in: &publishers) - } -} diff --git a/Sources/WalletConnectPush/Client/Dapp/PushMessageSender.swift b/Sources/WalletConnectPush/Client/Dapp/PushMessageSender.swift deleted file mode 100644 index a0c134cae..000000000 --- a/Sources/WalletConnectPush/Client/Dapp/PushMessageSender.swift +++ /dev/null @@ -1,22 +0,0 @@ -import Foundation - -class PushMessageSender { - 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, message: PushMessage) async throws { - logger.debug("PushMessageSender: Sending Push Message") - let protocolMethod = PushMessageProtocolMethod() - let request = RPCRequest(method: protocolMethod.method, params: message) - try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) - } -} diff --git a/Sources/WalletConnectPush/Client/Dapp/PushProposer.swift b/Sources/WalletConnectPush/Client/Dapp/PushProposer.swift deleted file mode 100644 index f76fdb075..000000000 --- a/Sources/WalletConnectPush/Client/Dapp/PushProposer.swift +++ /dev/null @@ -1,32 +0,0 @@ -import Foundation -import Combine -import WalletConnectPairing - -class PushProposer { - private let networkingInteractor: NetworkInteracting - private let kms: KeyManagementServiceProtocol - private let appMetadata: AppMetadata - private let logger: ConsoleLogging - - init(networkingInteractor: NetworkInteracting, - kms: KeyManagementServiceProtocol, - appMetadata: AppMetadata, - logger: ConsoleLogging) { - self.networkingInteractor = networkingInteractor - self.kms = kms - self.logger = logger - self.appMetadata = appMetadata - } - - func request(topic: String, account: Account) async throws { - logger.debug("PushProposer: Sending Push Proposal") - let protocolMethod = PushRequestProtocolMethod() - let pubKey = try kms.createX25519KeyPair() - let responseTopic = pubKey.rawRepresentation.sha256().toHexString() - try kms.setPublicKey(publicKey: pubKey, for: responseTopic) - let params = PushRequestParams(publicKey: pubKey.hexRepresentation, metadata: appMetadata, account: account) - let request = RPCRequest(method: protocolMethod.method, params: params) - try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) - try await networkingInteractor.subscribe(topic: responseTopic) - } -} diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift index acd9acaef..a84e908dc 100644 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift @@ -78,7 +78,7 @@ class NotifyProposeResponder { guard let requestRecord = rpcHistory.get(recordId: requestId) else { throw Errors.recordForIdNotFound } let pairingTopic = requestRecord.topic - try await networkingInteractor.respondError(topic: pairingTopic, requestId: requestId, protocolMethod: PushRequestProtocolMethod(), reason: PushError.rejected) + try await networkingInteractor.respondError(topic: pairingTopic, requestId: requestId, protocolMethod: NotifyProposeProtocolMethod(), reason: PushError.rejected) } private func generateAgreementKeys(peerPublicKey: AgreementPublicKey) throws -> AgreementKeys { diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushRequest/PushRequestResponder.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushRequest/PushRequestResponder.swift deleted file mode 100644 index cfebe88a3..000000000 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushRequest/PushRequestResponder.swift +++ /dev/null @@ -1,121 +0,0 @@ -import WalletConnectNetworking -import WalletConnectIdentity -import Combine -import Foundation - -class PushRequestResponder { - enum Errors: Error { - case recordForIdNotFound - case malformedRequestParams - } - private let keyserverURL: URL - private let identityClient: IdentityClient - private let networkingInteractor: NetworkInteracting - private let kms: KeyManagementService - private let rpcHistory: RPCHistory - private let logger: ConsoleLogging - private let subscriptionsStore: CodableStore - // Keychain shared with UNNotificationServiceExtension in order to decrypt PNs - private let groupKeychainStorage: KeychainStorageProtocol - - private var subscriptionPublisherSubject = PassthroughSubject, Never>() - var subscriptionPublisher: AnyPublisher, Never> { - return subscriptionPublisherSubject.eraseToAnyPublisher() - } - - init(keyserverURL: URL, - networkingInteractor: NetworkInteracting, - identityClient: IdentityClient, - logger: ConsoleLogging, - kms: KeyManagementService, - groupKeychainStorage: KeychainStorageProtocol, - rpcHistory: RPCHistory, - subscriptionsStore: CodableStore - ) { - self.keyserverURL = keyserverURL - self.identityClient = identityClient - self.networkingInteractor = networkingInteractor - self.logger = logger - self.kms = kms - self.groupKeychainStorage = groupKeychainStorage - self.rpcHistory = rpcHistory - self.subscriptionsStore = subscriptionsStore - } - - func respond(requestId: RPCID, onSign: @escaping SigningCallback) async throws { - - logger.debug("Approving Push Proposal") - - let requestRecord = try getRecord(requestId: requestId) - let peerPublicKey = try getPeerPublicKey(for: requestRecord) - let responseTopic = peerPublicKey.rawRepresentation.sha256().toHexString() - - let keys = try generateAgreementKeys(peerPublicKey: peerPublicKey) - let pushTopic = keys.derivedTopic() - let requestParams = try requestRecord.request.params!.get(PushRequestParams.self) - - _ = try await identityClient.register(account: requestParams.account, onSign: onSign) - - try kms.setAgreementSecret(keys, topic: responseTopic) - - logger.debug("PushRequestResponder: responding on response topic \(responseTopic) \(pushTopic)") - - try kms.setAgreementSecret(keys, topic: pushTopic) - - try groupKeychainStorage.add(keys, forKey: pushTopic) - - logger.debug("Subscribing to push topic: \(pushTopic)") - - try await networkingInteractor.subscribe(topic: pushTopic) - - let response = try createJWTResponse(requestId: requestId, subscriptionAccount: requestParams.account, dappUrl: requestParams.metadata.url) - - // will be changed in stage 2 refactor, this method will depricate - let expiry = Date() - let pushSubscription = PushSubscription(topic: pushTopic, account: requestParams.account, relay: RelayProtocolOptions(protocol: "irn", data: nil), metadata: requestParams.metadata, scope: [:], expiry: expiry) - - subscriptionsStore.set(pushSubscription, forKey: pushTopic) - - try await networkingInteractor.respond(topic: responseTopic, response: response, protocolMethod: PushRequestProtocolMethod(), envelopeType: .type1(pubKey: keys.publicKey.rawRepresentation)) - - kms.deletePrivateKey(for: keys.publicKey.hexRepresentation) - subscriptionPublisherSubject.send(.success(pushSubscription)) - } - - func respondError(requestId: RPCID) async throws { - logger.debug("PushRequestResponder - rejecting rush request") - let requestRecord = try getRecord(requestId: requestId) - let pairingTopic = requestRecord.topic - - try await networkingInteractor.respondError(topic: pairingTopic, requestId: requestId, protocolMethod: PushRequestProtocolMethod(), reason: PushError.rejected) - } - - private func createJWTResponse(requestId: RPCID, subscriptionAccount: Account, dappUrl: String) throws -> RPCResponse { - let jwtPayload = SubscriptionJWTPayload(keyserver: keyserverURL, subscriptionAccount: subscriptionAccount, dappUrl: dappUrl, scope: "v1") - let wrapper = try identityClient.signAndCreateWrapper( - payload: jwtPayload, - account: subscriptionAccount - ) - return RPCResponse(id: requestId, result: wrapper) - } - - private func getRecord(requestId: RPCID) throws -> RPCHistory.Record { - guard let record = rpcHistory.get(recordId: requestId) - else { throw Errors.recordForIdNotFound } - return record - } - - private func getPeerPublicKey(for record: RPCHistory.Record) throws -> AgreementPublicKey { - guard let params = try record.request.params?.get(PushRequestParams.self) - else { throw Errors.malformedRequestParams } - - let peerPublicKey = try AgreementPublicKey(hex: params.publicKey) - return peerPublicKey - } - - private func generateAgreementKeys(peerPublicKey: AgreementPublicKey) throws -> AgreementKeys { - let selfPubKey = try kms.createX25519KeyPair() - let keys = try kms.performKeyAgreement(selfPublicKey: selfPubKey, peerPublicKey: peerPublicKey.hexRepresentation) - return keys - } -} diff --git a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift index 2818398cb..bd1a9ca88 100644 --- a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift +++ b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift @@ -11,7 +11,7 @@ public class WalletPushClient { /// publishes new subscriptions public var subscriptionPublisher: AnyPublisher, Never> { - return subscriptionPublisherSubject.eraseToAnyPublisher() + return pushSubscribeResponseSubscriber.subscriptionPublisher } public var subscriptionPublisherSubject = PassthroughSubject, Never>() @@ -52,7 +52,6 @@ public class WalletPushClient { private let pairingRegisterer: PairingRegisterer private let echoClient: EchoClient - private let proposeResponder: PushRequestResponder private let pushMessageSubscriber: PushMessageSubscriber private let subscriptionsProvider: SubscriptionsProvider private let pushMessagesDatabase: PushMessagesDatabase @@ -66,7 +65,6 @@ public class WalletPushClient { kms: KeyManagementServiceProtocol, echoClient: EchoClient, pairingRegisterer: PairingRegisterer, - proposeResponder: PushRequestResponder, pushMessageSubscriber: PushMessageSubscriber, subscriptionsProvider: SubscriptionsProvider, pushMessagesDatabase: PushMessagesDatabase, @@ -82,7 +80,6 @@ public class WalletPushClient { ) { self.logger = logger self.pairingRegisterer = pairingRegisterer - self.proposeResponder = proposeResponder self.echoClient = echoClient self.pushMessageSubscriber = pushMessageSubscriber self.subscriptionsProvider = subscriptionsProvider @@ -104,11 +101,6 @@ public class WalletPushClient { } public func approve(id: RPCID, onSign: @escaping SigningCallback) async throws { - try await proposeResponder.respond(requestId: id, onSign: onSign) - } - - // rename method after approve deprication - public func approvePropose(id: RPCID, onSign: @escaping SigningCallback) async throws { try await notifyProposeResponder.approve(requestId: id, onSign: onSign) } @@ -144,13 +136,6 @@ public class WalletPushClient { private extension WalletPushClient { func setupSubscriptions() { - let protocolMethod = PushRequestProtocolMethod() - - pairingRegisterer.register(method: protocolMethod) - .sink { [unowned self] (payload: RequestSubscriptionPayload) in - requestPublisherSubject.send((id: payload.id, account: payload.request.account, metadata: payload.request.metadata)) - }.store(in: &publishers) - pairingRegisterer.register(method: NotifyProposeProtocolMethod()) .sink { [unowned self] (payload: RequestSubscriptionPayload) in requestPublisherSubject.send((id: payload.id, account: payload.request.account, metadata: payload.request.metadata)) @@ -163,14 +148,6 @@ private extension WalletPushClient { deletePushSubscriptionSubscriber.onDelete = {[unowned self] topic in deleteSubscriptionPublisherSubject.send(topic) } - - pushSubscribeResponseSubscriber.subscriptionPublisher.sink { [unowned self] result in - subscriptionPublisherSubject.send(result) - }.store(in: &publishers) - - proposeResponder.subscriptionPublisher.sink { [unowned self] result in - subscriptionPublisherSubject.send(result) - }.store(in: &publishers) } } diff --git a/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift b/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift index bc50c752a..11a0539f0 100644 --- a/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift +++ b/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift @@ -42,8 +42,6 @@ public struct WalletPushClientFactory { let identityClient = IdentityClientFactory.create(keyserver: keyserverURL, keychain: keychainStorage, logger: logger) - let proposeResponder = PushRequestResponder(keyserverURL: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, logger: logger, kms: kms, groupKeychainStorage: groupKeychainStorage, rpcHistory: history, subscriptionsStore: subscriptionStore) - let pushMessagesRecordsStore = CodableStore(defaults: keyValueStorage, identifier: PushStorageIdntifiers.pushMessagesRecords) let pushMessagesDatabase = PushMessagesDatabase(store: pushMessagesRecordsStore) let pushMessageSubscriber = PushMessageSubscriber(networkingInteractor: networkInteractor, pushMessagesDatabase: pushMessagesDatabase, logger: logger) @@ -71,7 +69,6 @@ public struct WalletPushClientFactory { kms: kms, echoClient: echoClient, pairingRegisterer: pairingRegisterer, - proposeResponder: proposeResponder, pushMessageSubscriber: pushMessageSubscriber, subscriptionsProvider: subscriptionProvider, pushMessagesDatabase: pushMessagesDatabase, diff --git a/Sources/WalletConnectPush/ProtocolMethods/PushRequestProtocolMethod.swift b/Sources/WalletConnectPush/ProtocolMethods/PushRequestProtocolMethod.swift deleted file mode 100644 index 6c04631e1..000000000 --- a/Sources/WalletConnectPush/ProtocolMethods/PushRequestProtocolMethod.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation -import WalletConnectPairing - -struct PushRequestProtocolMethod: ProtocolMethod { - let method: String = "wc_pushRequest" - - let requestConfig: RelayConfig = RelayConfig(tag: 4000, prompt: true, ttl: 86400) - - let responseConfig: RelayConfig = RelayConfig(tag: 4001, prompt: true, ttl: 86400) -} diff --git a/Sources/WalletConnectPush/RPCRequests/PushRequestParams.swift b/Sources/WalletConnectPush/RPCRequests/PushRequestParams.swift deleted file mode 100644 index d3b4abe9f..000000000 --- a/Sources/WalletConnectPush/RPCRequests/PushRequestParams.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -public struct PushRequestParams: Codable { - let publicKey: String - let metadata: AppMetadata - let account: Account -} From 15e5cee65a9f90ffb88d62f40b3f5bac618ea886 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 25 May 2023 10:08:15 +0200 Subject: [PATCH 22/33] update proposal response publisher --- Example/IntegrationTests/Pairing/PairingTests.swift | 2 +- .../WalletConnectPush/Client/Dapp/DappPushClient.swift | 10 +--------- .../Client/Dapp/DappPushClientFactory.swift | 2 -- .../NotifyProposeResponseSubscriber.swift | 4 ++-- 4 files changed, 4 insertions(+), 14 deletions(-) diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift index 8ec199b8d..4fb84f283 100644 --- a/Example/IntegrationTests/Pairing/PairingTests.swift +++ b/Example/IntegrationTests/Pairing/PairingTests.swift @@ -129,7 +129,7 @@ final class PairingTests: XCTestCase { makeWalletPairingClient() let expectation = expectation(description: "wallet responds unsupported method for unregistered method") - appPushClient.responsePublisher.sink { (_, response) in + appPushClient.proposalResponsePublisher.sink { (response) in XCTAssertEqual(response, .failure(PushError(code: 10001)!)) expectation.fulfill() }.store(in: &publishers) diff --git a/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift b/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift index 342beebab..cd40fe717 100644 --- a/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift +++ b/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift @@ -3,7 +3,7 @@ import Combine import WalletConnectUtils public class DappPushClient { - var proposalResponsePublisher: AnyPublisher, Never> { + var proposalResponsePublisher: AnyPublisher, Never> { return notifyProposeResponseSubscriber.proposalResponsePublisher } @@ -17,7 +17,6 @@ public class DappPushClient { private let notifyProposer: NotifyProposer private let subscriptionsProvider: SubscriptionsProvider - private let deletePushSubscriptionService: DeletePushSubscriptionService private let deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber private let resubscribeService: PushResubscribeService private let notifyProposeResponseSubscriber: NotifyProposeResponseSubscriber @@ -25,14 +24,12 @@ public class DappPushClient { init(logger: ConsoleLogging, kms: KeyManagementServiceProtocol, subscriptionsProvider: SubscriptionsProvider, - deletePushSubscriptionService: DeletePushSubscriptionService, deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber, resubscribeService: PushResubscribeService, notifyProposer: NotifyProposer, notifyProposeResponseSubscriber: NotifyProposeResponseSubscriber) { self.logger = logger self.subscriptionsProvider = subscriptionsProvider - self.deletePushSubscriptionService = deletePushSubscriptionService self.deletePushSubscriptionSubscriber = deletePushSubscriptionSubscriber self.resubscribeService = resubscribeService self.notifyProposer = notifyProposer @@ -47,11 +44,6 @@ public class DappPushClient { public func getActiveSubscriptions() -> [PushSubscription] { subscriptionsProvider.getActiveSubscriptions() } - - public func delete(topic: String) async throws { - try await deletePushSubscriptionService.delete(topic: topic) - } - } private extension DappPushClient { diff --git a/Sources/WalletConnectPush/Client/Dapp/DappPushClientFactory.swift b/Sources/WalletConnectPush/Client/Dapp/DappPushClientFactory.swift index a74a5dbbb..8764901a3 100644 --- a/Sources/WalletConnectPush/Client/Dapp/DappPushClientFactory.swift +++ b/Sources/WalletConnectPush/Client/Dapp/DappPushClientFactory.swift @@ -20,7 +20,6 @@ public struct DappPushClientFactory { let kms = KeyManagementService(keychain: keychainStorage) let subscriptionStore = CodableStore(defaults: keyValueStorage, identifier: PushStorageIdntifiers.pushSubscription) let subscriptionProvider = SubscriptionsProvider(store: subscriptionStore) - let deletePushSubscriptionService = DeletePushSubscriptionService(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushSubscriptionStore: subscriptionStore, pushMessagesDatabase: nil) let deletePushSubscriptionSubscriber = DeletePushSubscriptionSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushSubscriptionStore: subscriptionStore) let resubscribeService = PushResubscribeService(networkInteractor: networkInteractor, subscriptionsStorage: subscriptionStore) let notifyProposer = NotifyProposer(networkingInteractor: networkInteractor, kms: kms, appMetadata: metadata, logger: logger) @@ -29,7 +28,6 @@ public struct DappPushClientFactory { logger: logger, kms: kms, subscriptionsProvider: subscriptionProvider, - deletePushSubscriptionService: deletePushSubscriptionService, deletePushSubscriptionSubscriber: deletePushSubscriptionSubscriber, resubscribeService: resubscribeService, notifyProposer: notifyProposer, diff --git a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift index ff2e530e5..f83045f17 100644 --- a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift +++ b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift @@ -7,10 +7,10 @@ class NotifyProposeResponseSubscriber { private let metadata: AppMetadata private let kms: KeyManagementServiceProtocol private let logger: ConsoleLogging - var proposalResponsePublisher: AnyPublisher, Never> { + var proposalResponsePublisher: AnyPublisher, Never> { proposalResponsePublisherSubject.eraseToAnyPublisher() } - private let proposalResponsePublisherSubject = PassthroughSubject, Never>() + private let proposalResponsePublisherSubject = PassthroughSubject, Never>() private var publishers = [AnyCancellable]() From 6a42ae3870a0e50b85be5220a71a833fcf90fb0f Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Thu, 25 May 2023 10:16:32 +0200 Subject: [PATCH 23/33] fix sample apps --- .../Accounts/AccountsViewController.swift | 22 ++++--------------- .../PushRequest/PushRequestInteractor.swift | 2 +- .../Client/Dapp/DappPushClient.swift | 2 +- 3 files changed, 6 insertions(+), 20 deletions(-) diff --git a/Example/DApp/Sign/Accounts/AccountsViewController.swift b/Example/DApp/Sign/Accounts/AccountsViewController.swift index f51b87b0c..ad29010ae 100644 --- a/Example/DApp/Sign/Accounts/AccountsViewController.swift +++ b/Example/DApp/Sign/Accounts/AccountsViewController.swift @@ -46,13 +46,6 @@ final class AccountsViewController: UIViewController, UITableViewDataSource, UIT action: #selector(disconnect) ) - navigationItem.leftBarButtonItem = UIBarButtonItem( - title: "Push Test", - style: .plain, - target: self, - action: #selector(pushTest) - ) - accountsView.tableView.dataSource = self accountsView.tableView.delegate = self session.namespaces.values.forEach { namespace in @@ -65,24 +58,17 @@ final class AccountsViewController: UIViewController, UITableViewDataSource, UIT func proposePushSubscription() { let account = session.namespaces.values.first!.accounts.first! - Task(priority: .high){ try! await Push.dapp.request(account: account, topic: session.pairingTopic)} - Push.dapp.responsePublisher.sink { (id: RPCID, result: Result) in + Task(priority: .high){ try! await Push.dapp.propose(account: account, topic: session.pairingTopic)} + Push.dapp.proposalResponsePublisher.sink { result in switch result { - case .success(let subscriptionResult): - self.pushSubscription = subscriptionResult.pushSubscription + case .success(let subscription): + self.pushSubscription = subscription case .failure(let error): print(error) } }.store(in: &publishers) } - @objc - private func pushTest() { - guard let pushTopic = pushSubscription?.topic else {return} - let message = PushMessage(title: "Push Message", body: "He,y this is a message from the swift client", icon: "", url: "") - Task(priority: .userInitiated) { try! await Push.dapp.notify(topic: pushTopic, message: message) } - } - @objc private func disconnect() { Task { diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift index d3c6e239d..2afda2ff4 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift @@ -3,7 +3,7 @@ import WalletConnectPush final class PushRequestInteractor { func approve(pushRequest: PushRequest) async throws { - try await Push.wallet.approvePropose(id: pushRequest.id, onSign: Web3InboxSigner.onSing) + try await Push.wallet.approve(id: pushRequest.id, onSign: Web3InboxSigner.onSing) } func reject(pushRequest: PushRequest) async throws { diff --git a/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift b/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift index cd40fe717..f38cfd494 100644 --- a/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift +++ b/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift @@ -3,7 +3,7 @@ import Combine import WalletConnectUtils public class DappPushClient { - var proposalResponsePublisher: AnyPublisher, Never> { + public var proposalResponsePublisher: AnyPublisher, Never> { return notifyProposeResponseSubscriber.proposalResponsePublisher } From 2d32f546a3d865c21daf11cf14d380ab5ee7e42f Mon Sep 17 00:00:00 2001 From: Radek Novak Date: Thu, 25 May 2023 14:13:51 +0200 Subject: [PATCH 24/33] Deeplink to wallets --- Package.swift | 5 +- .../Helpers/UIApplicationWrapper.swift | 19 +++ .../Web3Modal/Modal/ModalContainerView.swift | 2 +- Sources/Web3Modal/Modal/ModalInteractor.swift | 85 +++++++------- .../Web3Modal/Modal/ModalSheet+Previews.swift | 2 +- Sources/Web3Modal/Modal/ModalSheet.swift | 7 +- Sources/Web3Modal/Modal/ModalViewModel.swift | 111 ++++++++++++++++-- Tests/Web3ModalTests/Helpers/FuncTests.swift | 10 ++ .../Mocks/ModalSheetInteractorMock.swift | 34 ++++++ .../Web3ModalTests/ModalViewModelTests.swift | 80 +++++++++++++ 10 files changed, 300 insertions(+), 55 deletions(-) create mode 100644 Sources/Web3Modal/Helpers/UIApplicationWrapper.swift create mode 100644 Tests/Web3ModalTests/Helpers/FuncTests.swift create mode 100644 Tests/Web3ModalTests/Mocks/ModalSheetInteractorMock.swift create mode 100644 Tests/Web3ModalTests/ModalViewModelTests.swift diff --git a/Package.swift b/Package.swift index 6960b3734..e1db6a6fd 100644 --- a/Package.swift +++ b/Package.swift @@ -160,7 +160,10 @@ let package = Package( dependencies: ["JSONRPC", "TestingUtils"]), .testTarget( name: "CommonsTests", - dependencies: ["Commons", "TestingUtils"]) + dependencies: ["Commons", "TestingUtils"]), + .testTarget( + name: "Web3ModalTests", + dependencies: ["Web3Modal", "TestingUtils"]) ], swiftLanguageVersions: [.v5] ) diff --git a/Sources/Web3Modal/Helpers/UIApplicationWrapper.swift b/Sources/Web3Modal/Helpers/UIApplicationWrapper.swift new file mode 100644 index 000000000..f27b12de0 --- /dev/null +++ b/Sources/Web3Modal/Helpers/UIApplicationWrapper.swift @@ -0,0 +1,19 @@ +import UIKit + +struct UIApplicationWrapper { + let openURL: (URL) -> Void + let canOpenURL: (URL) -> Bool +} + +extension UIApplicationWrapper { + static let live = Self( + openURL: { url in + Task { @MainActor in + await UIApplication.shared.open(url) + } + }, + canOpenURL: { url in + UIApplication.shared.canOpenURL(url) + } + ) +} diff --git a/Sources/Web3Modal/Modal/ModalContainerView.swift b/Sources/Web3Modal/Modal/ModalContainerView.swift index 312120dbf..5722cdd3d 100644 --- a/Sources/Web3Modal/Modal/ModalContainerView.swift +++ b/Sources/Web3Modal/Modal/ModalContainerView.swift @@ -34,7 +34,7 @@ public struct ModalContainerView: View { viewModel: .init( isShown: $showModal, projectId: projectId, - interactor: .init(projectId: projectId, metadata: metadata, webSocketFactory: webSocketFactory) + interactor: DefaultModalSheetInteractor(projectId: projectId, metadata: metadata, webSocketFactory: webSocketFactory) )) .transition(.move(edge: .bottom)) .animation(.spring(), value: showModal) diff --git a/Sources/Web3Modal/Modal/ModalInteractor.swift b/Sources/Web3Modal/Modal/ModalInteractor.swift index 69ac7ea41..4d2ba5af6 100644 --- a/Sources/Web3Modal/Modal/ModalInteractor.swift +++ b/Sources/Web3Modal/Modal/ModalInteractor.swift @@ -2,51 +2,56 @@ import WalletConnectPairing import WalletConnectSign import Combine -extension ModalSheet { - final class Interactor { - let projectId: String - let metadata: AppMetadata - let socketFactory: WebSocketFactory +protocol ModalSheetInteractor { + func getListings() async throws -> [Listing] + func connect() async throws -> WalletConnectURI + + var sessionSettlePublisher: AnyPublisher { get } +} + +final class DefaultModalSheetInteractor: ModalSheetInteractor { + let projectId: String + let metadata: AppMetadata + let socketFactory: WebSocketFactory + + lazy var sessionSettlePublisher: AnyPublisher = Sign.instance.sessionSettlePublisher + + init(projectId: String, metadata: AppMetadata, webSocketFactory: WebSocketFactory) { + self.projectId = projectId + self.metadata = metadata + self.socketFactory = webSocketFactory - lazy var sessionsPublisher: AnyPublisher<[Session], Never> = Sign.instance.sessionsPublisher + Pair.configure(metadata: metadata) + Networking.configure(projectId: projectId, socketFactory: socketFactory) + } + + func getListings() async throws -> [Listing] { + + let httpClient = HTTPNetworkClient(host: "explorer-api.walletconnect.com") + let response = try await httpClient.request( + ListingsResponse.self, + at: ExplorerAPI.getListings(projectId: projectId) + ) + + return response.listings.values.compactMap { $0 } + } + + func connect() async throws -> WalletConnectURI { - init(projectId: String, metadata: AppMetadata, webSocketFactory: WebSocketFactory) { - self.projectId = projectId - self.metadata = metadata - self.socketFactory = webSocketFactory - - Pair.configure(metadata: metadata) - Networking.configure(projectId: projectId, socketFactory: socketFactory) - } + let uri = try await Pair.instance.create() - func getListings() async throws -> [Listing] { - - let httpClient = HTTPNetworkClient(host: "explorer-api.walletconnect.com") - let response = try await httpClient.request( - ListingsResponse.self, - at: ExplorerAPI.getListings(projectId: projectId) + let methods: Set = ["eth_sendTransaction", "personal_sign", "eth_signTypedData"] + let blockchains: Set = [Blockchain("eip155:1")!] + let namespaces: [String: ProposalNamespace] = [ + "eip155": ProposalNamespace( + chains: blockchains, + methods: methods, + events: [] ) + ] - return response.listings.values.compactMap { $0 } - } + try await Sign.instance.connect(requiredNamespaces: namespaces, topic: uri.topic) - func connect() async throws -> WalletConnectURI { - - let uri = try await Pair.instance.create() - - let methods: Set = ["eth_sendTransaction", "personal_sign", "eth_signTypedData"] - let blockchains: Set = [Blockchain("eip155:1")!, Blockchain("eip155:137")!] - let namespaces: [String: ProposalNamespace] = [ - "eip155": ProposalNamespace( - chains: blockchains, - methods: methods, - events: [] - ) - ] - - try await Sign.instance.connect(requiredNamespaces: namespaces, topic: uri.topic) - - return uri - } + return uri } } diff --git a/Sources/Web3Modal/Modal/ModalSheet+Previews.swift b/Sources/Web3Modal/Modal/ModalSheet+Previews.swift index f4be2b253..161c15bb0 100644 --- a/Sources/Web3Modal/Modal/ModalSheet+Previews.swift +++ b/Sources/Web3Modal/Modal/ModalSheet+Previews.swift @@ -37,7 +37,7 @@ struct ModalSheet_Previews: PreviewProvider { viewModel: .init( isShown: .constant(true), projectId: projectId, - interactor: .init( + interactor: DefaultModalSheetInteractor( projectId: projectId, metadata: metadata, webSocketFactory: WebSocketFactoryMock() diff --git a/Sources/Web3Modal/Modal/ModalSheet.swift b/Sources/Web3Modal/Modal/ModalSheet.swift index 19928cb65..4d4f42bf8 100644 --- a/Sources/Web3Modal/Modal/ModalSheet.swift +++ b/Sources/Web3Modal/Modal/ModalSheet.swift @@ -120,7 +120,7 @@ public struct ModalSheet: View { private func gridItem(for index: Int) -> some View { let wallet: Listing? = viewModel.wallets[safe: index] - if #available(iOS 15.0, *) { + if #available(iOS 14.0, *) { VStack { AsyncImage(url: viewModel.imageUrl(for: wallet)) { image in image @@ -151,6 +151,11 @@ public struct ModalSheet: View { } .redacted(reason: wallet == nil ? .placeholder : []) .frame(maxWidth: 80, maxHeight: 96) + .onTapGesture { + Task { + await viewModel.onWalletTapped(index: index) + } + } } } diff --git a/Sources/Web3Modal/Modal/ModalViewModel.swift b/Sources/Web3Modal/Modal/ModalViewModel.swift index 39682c72b..921e9b260 100644 --- a/Sources/Web3Modal/Modal/ModalViewModel.swift +++ b/Sources/Web3Modal/Modal/ModalViewModel.swift @@ -22,23 +22,31 @@ extension ModalSheet { } final class ModalViewModel: ObservableObject { - private var disposeBag = Set() - private let interactor: Interactor + @Published private(set) var isShown: Binding private let projectId: String + private let interactor: ModalSheetInteractor + private let uiApplicationWrapper: UIApplicationWrapper + + private var disposeBag = Set() + private var deeplinkUri: String? - @Published var isShown: Binding - - @Published var uri: String? - @Published var destination: Destination = .wallets - @Published var errorMessage: String? - @Published var wallets: [Listing] = [] + @Published private(set) var uri: String? + @Published private(set) var destination: Destination = .wallets + @Published private(set) var errorMessage: String? + @Published private(set) var wallets: [Listing] = [] - init(isShown: Binding, projectId: String, interactor: Interactor) { + init( + isShown: Binding, + projectId: String, + interactor: ModalSheetInteractor, + uiApplicationWrapper: UIApplicationWrapper = .live + ) { self.isShown = isShown self.interactor = interactor self.projectId = projectId + self.uiApplicationWrapper = uiApplicationWrapper - interactor.sessionsPublisher + interactor.sessionSettlePublisher .receive(on: DispatchQueue.main) .sink { sessions in print(sessions) @@ -66,7 +74,9 @@ extension ModalSheet { @MainActor func createURI() async { do { - uri = try await interactor.connect().absoluteString + let wcUri = try await interactor.connect() + uri = wcUri.absoluteString + deeplinkUri = wcUri.deeplinkUri } catch { print(error) errorMessage = error.localizedDescription @@ -85,6 +95,15 @@ extension ModalSheet { UIPasteboard.general.string = uri } + func onWalletTapped(index: Int) { + guard let wallet = wallets[safe: index] else { return } + + navigateToDeepLink( + universalLink: wallet.mobile.universal ?? "", + nativeLink: wallet.mobile.native ?? "" + ) + } + func imageUrl(for listing: Listing?) -> URL? { guard let listing = listing else { return nil } @@ -94,3 +113,73 @@ extension ModalSheet { } } } + +private extension ModalSheet.ModalViewModel { + enum Errors: Error { + case noWalletLinkFound + } + + func navigateToDeepLink(universalLink: String, nativeLink: String) { + do { + let nativeUrlString = formatNativeUrlString(nativeLink) + let universalUrlString = formatUniversalUrlString(universalLink) + + if let nativeUrl = nativeUrlString?.toURL() { + uiApplicationWrapper.openURL(nativeUrl) + } else if let universalUrl = universalUrlString?.toURL() { + uiApplicationWrapper.openURL(universalUrl) + } else { + throw Errors.noWalletLinkFound + } + } catch { + let alertController = UIAlertController(title: "Unable to open the app", message: nil, preferredStyle: .alert) + alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) + UIApplication.shared.windows.first?.rootViewController?.present(alertController, animated: true, completion: nil) + } + } + + func isHttpUrl(url: String) -> Bool { + return url.hasPrefix("http://") || url.hasPrefix("https://") + } + + func formatNativeUrlString(_ string: String) -> String? { + if string.isEmpty { return nil } + + if isHttpUrl(url: string) { + return formatUniversalUrlString(string) + } + + var safeAppUrl = string + if !safeAppUrl.contains("://") { + safeAppUrl = safeAppUrl.replacingOccurrences(of: "/", with: "").replacingOccurrences(of: ":", with: "") + safeAppUrl = "\(safeAppUrl)://" + } + + guard let deeplinkUri else { return nil } + + return "\(safeAppUrl)wc?uri=\(deeplinkUri)" + } + + func formatUniversalUrlString(_ string: String) -> String? { + if string.isEmpty { return nil } + + if !isHttpUrl(url: string) { + return formatNativeUrlString(string) + } + + var plainAppUrl = string + if plainAppUrl.hasSuffix("/") { + plainAppUrl = String(plainAppUrl.dropLast()) + } + + guard let deeplinkUri else { return nil } + + return "\(plainAppUrl)/wc?uri=\(deeplinkUri)" + } +} + +private extension String { + func toURL() -> URL? { + URL(string: self) + } +} diff --git a/Tests/Web3ModalTests/Helpers/FuncTests.swift b/Tests/Web3ModalTests/Helpers/FuncTests.swift new file mode 100644 index 000000000..5e23eda44 --- /dev/null +++ b/Tests/Web3ModalTests/Helpers/FuncTests.swift @@ -0,0 +1,10 @@ +struct FuncTest { + private(set) var values: [T] = [] + var wasCalled: Bool { !values.isEmpty } + var wasNotCalled: Bool { !wasCalled } + var callsCount: Int { values.count } + var wasCalledOnce: Bool { values.count == 1 } + var currentValue: T? { values.last } + mutating func call(_ value: T) { values.append(value) } + init() {} +} diff --git a/Tests/Web3ModalTests/Mocks/ModalSheetInteractorMock.swift b/Tests/Web3ModalTests/Mocks/ModalSheetInteractorMock.swift new file mode 100644 index 000000000..c2f9e7e54 --- /dev/null +++ b/Tests/Web3ModalTests/Mocks/ModalSheetInteractorMock.swift @@ -0,0 +1,34 @@ +import Combine +import Foundation +import WalletConnectSign +import WalletConnectUtils +@testable import Web3Modal +@testable import WalletConnectSign + +final class ModalSheetInteractorMock: ModalSheetInteractor { + + static let listingsStub: [Listing] = [ + Listing(id: UUID().uuidString, name: "Sample App", homepage: "https://example.com", order: 1, imageId: UUID().uuidString, app: Listing.App(ios: "https://example.com/download-ios", mac: "https://example.com/download-mac", safari: "https://example.com/download-safari"), mobile: Listing.Mobile(native: "sampleapp://deeplink", universal: "https://example.com/universal")), + Listing(id: UUID().uuidString, name: "Awesome App", homepage: "https://example.com/awesome", order: 2, imageId: UUID().uuidString, app: Listing.App(ios: "https://example.com/download-ios", mac: "https://example.com/download-mac", safari: "https://example.com/download-safari"), mobile: Listing.Mobile(native: "awesomeapp://deeplink", universal: "https://example.com/awesome/universal")), + Listing(id: UUID().uuidString, name: "Cool App", homepage: "https://example.com/cool", order: 3, imageId: UUID().uuidString, app: Listing.App(ios: "https://example.com/download-ios", mac: "https://example.com/download-mac", safari: "https://example.com/download-safari"), mobile: Listing.Mobile(native: "coolapp://deeplink", universal: "https://example.com/cool/universal")) + ] + + var listings: [Listing] + + init(listings: [Listing] = ModalSheetInteractorMock.listingsStub) { + self.listings = listings + } + + func getListings() async throws -> [Web3Modal.Listing] { + listings + } + + func connect() async throws -> WalletConnectURI { + .init(topic: "foo", symKey: "bar", relay: .init(protocol: "irn", data: nil)) + } + + var sessionSettlePublisher: AnyPublisher { + Result.Publisher(Session(topic: "", pairingTopic: "", peer: .stub(), namespaces: [:], expiryDate: Date())) + .eraseToAnyPublisher() + } +} diff --git a/Tests/Web3ModalTests/ModalViewModelTests.swift b/Tests/Web3ModalTests/ModalViewModelTests.swift new file mode 100644 index 000000000..7965a201d --- /dev/null +++ b/Tests/Web3ModalTests/ModalViewModelTests.swift @@ -0,0 +1,80 @@ +import TestingUtils +@testable import Web3Modal +import XCTest + +final class ModalViewModelTests: XCTestCase { + private var sut: ModalSheet.ModalViewModel! + + private var openURLFuncTest: FuncTest! + private var canOpenURLFuncTest: FuncTest! + private var expectation: XCTestExpectation! + + override func setUpWithError() throws { + try super.setUpWithError() + + openURLFuncTest = .init() + canOpenURLFuncTest = .init() + + sut = .init( + isShown: .constant(true), + projectId: "", + interactor: ModalSheetInteractorMock(listings: [ + Listing(id: "1", name: "Sample App", homepage: "https://example.com", order: 1, imageId: "1", app: Listing.App(ios: "https://example.com/download-ios", mac: "https://example.com/download-mac", safari: "https://example.com/download-safari"), mobile: Listing.Mobile(native: nil, universal: "https://example.com/universal")), + Listing(id: "2", name: "Awesome App", homepage: "https://example.com/awesome", order: 2, imageId: "2", app: Listing.App(ios: "https://example.com/download-ios", mac: "https://example.com/download-mac", safari: "https://example.com/download-safari"), mobile: Listing.Mobile(native: "awesomeapp://deeplink", universal: "https://awesome.com/awesome/universal")), + ]), + uiApplicationWrapper: .init( + openURL: { url in + self.openURLFuncTest.call(url) + self.expectation.fulfill() + }, + canOpenURL: { url in + self.canOpenURLFuncTest.call(url) + self.expectation.fulfill() + return true + } + ) + ) + } + + override func tearDownWithError() throws { + sut = nil + openURLFuncTest = nil + canOpenURLFuncTest = nil + try super.tearDownWithError() + } + + func test_onWalletTapped() async throws { + await sut.fetchWallets() + await sut.createURI() + + XCTAssertEqual(sut.uri, "wc:foo@2?symKey=bar&relay-protocol=irn") + + XCTAssertEqual(sut.wallets.count, 2) + XCTAssertEqual(sut.wallets, [ + Listing(id: "1", name: "Sample App", homepage: "https://example.com", order: 1, imageId: "1", app: Listing.App(ios: "https://example.com/download-ios", mac: "https://example.com/download-mac", safari: "https://example.com/download-safari"), mobile: Listing.Mobile(native: nil, universal: "https://example.com/universal")), + Listing(id: "2", name: "Awesome App", homepage: "https://example.com/awesome", order: 2, imageId: "2", app: Listing.App(ios: "https://example.com/download-ios", mac: "https://example.com/download-mac", safari: "https://example.com/download-safari"), mobile: Listing.Mobile(native: "awesomeapp://deeplink", universal: "https://awesome.com/awesome/universal")), + ]) + + expectation = XCTestExpectation(description: "Wait for openUrl to be called") + + sut.onWalletTapped(index: 0) + + XCTWaiter.wait(for: [expectation], timeout: 3) + + XCTAssertEqual( + openURLFuncTest.currentValue, + URL(string: "https://example.com/universal/wc?uri=wc%3Afoo%402%3FsymKey%3Dbar%26relay-protocol%3Dirn")! + ) + + expectation = XCTestExpectation(description: "Wait for openUrl to be called 2nd time") + + sut.onWalletTapped(index: 1) + + XCTWaiter.wait(for: [expectation], timeout: 3) + + XCTAssertEqual( + openURLFuncTest.currentValue, + URL(string: "awesomeapp://deeplinkwc?uri=wc%3Afoo%402%3FsymKey%3Dbar%26relay-protocol%3Dirn")! + ) + } +} From 3e7aea38024d932b07bf39f0c36072e1d33fd6d2 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Fri, 26 May 2023 08:49:03 +0200 Subject: [PATCH 25/33] present w3i only --- .../PresentationLayer/Wallet/Main/MainRouter.swift | 4 ++-- .../Wallet/Main/Model/TabPage.swift | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift index 1bd145592..fc3a4c0bb 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift @@ -30,8 +30,8 @@ final class MainRouter { } func present(pushRequest: PushRequest) { - PushRequestModule.create(app: app, pushRequest: pushRequest) - .presentFullScreen(from: viewController, transparentBackground: true) +// PushRequestModule.create(app: app, pushRequest: pushRequest) +// .presentFullScreen(from: viewController, transparentBackground: true) } init(app: Application) { diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift index 698349ebd..d195c90ae 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/Model/TabPage.swift @@ -2,15 +2,15 @@ import UIKit enum TabPage: CaseIterable { case wallet - case notifications +// case notifications case web3Inbox var title: String { switch self { case .wallet: return "Apps" - case .notifications: - return "Notifications" +// case .notifications: +// return "Notifications" case .web3Inbox: return "w3i" } @@ -20,8 +20,8 @@ enum TabPage: CaseIterable { switch self { case .wallet: return UIImage(systemName: "house.fill")! - case .notifications: - return UIImage(systemName: "bell.fill")! +// case .notifications: +// return UIImage(systemName: "bell.fill")! case .web3Inbox: return UIImage(systemName: "bell.fill")! } @@ -32,6 +32,6 @@ enum TabPage: CaseIterable { } static var enabledTabs: [TabPage] { - return [.wallet, .notifications, .web3Inbox] + return [.wallet, .web3Inbox] } } From d23fcfce447caf137244a7a4780c75fed8c30aaf Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Fri, 26 May 2023 12:41:35 +0200 Subject: [PATCH 26/33] Update proposal response according to spec change --- Example/WalletApp/ApplicationLayer/SceneDelegate.swift | 2 +- .../wc_notifyPropose/NotifyProposeResponseSubscriber.swift | 6 +++--- .../wc_notifyPropose/NotifyProposeResponder.swift | 5 ++++- .../RPCRequests/NotifyProposeResponseParams.swift | 7 +++++++ 4 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 Sources/WalletConnectPush/RPCRequests/NotifyProposeResponseParams.swift diff --git a/Example/WalletApp/ApplicationLayer/SceneDelegate.swift b/Example/WalletApp/ApplicationLayer/SceneDelegate.swift index 999164f85..7bd4a81f3 100644 --- a/Example/WalletApp/ApplicationLayer/SceneDelegate.swift +++ b/Example/WalletApp/ApplicationLayer/SceneDelegate.swift @@ -32,7 +32,7 @@ final class SceneDelegate: UIResponder, UIWindowSceneDelegate { app.uri = connectionOptions.urlContexts.first?.url.absoluteString.replacingOccurrences(of: "walletapp://wc?uri=", with: "") - configurators.configure() + configurators.configure() app.pushRegisterer.registerForPushNotifications() } diff --git a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift index f83045f17..abf3f35a2 100644 --- a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift +++ b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift @@ -30,7 +30,7 @@ class NotifyProposeResponseSubscriber { private func subscribeForProposalResponse() { let protocolMethod = NotifyProposeProtocolMethod() networkingInteractor.responseSubscription(on: protocolMethod) - .sink { [unowned self] (payload: ResponseSubscriptionPayload) in + .sink { [unowned self] (payload: ResponseSubscriptionPayload) in logger.debug("Received Notify Proposal response") Task(priority: .userInitiated) { do { @@ -44,8 +44,8 @@ class NotifyProposeResponseSubscriber { } /// Implemented only for integration testing purpose, dapp client is not supported - func handleResponse(payload: ResponseSubscriptionPayload) async throws -> PushSubscription { - let (_, claims) = try SubscriptionJWTPayload.decodeAndVerify(from: payload.response) + func handleResponse(payload: ResponseSubscriptionPayload) async throws -> PushSubscription { + let (_, claims) = try SubscriptionJWTPayload.decodeAndVerify(from: payload.response.subscriptionAuth) logger.debug("subscriptionAuth JWT validated") let expiry = Date(timeIntervalSince1970: TimeInterval(claims.exp)) diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift index a84e908dc..475ab0a41 100644 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift @@ -63,7 +63,9 @@ class NotifyProposeResponder { try kms.setSymmetricKey(keys.sharedKey, for: responseTopic) - let response = RPCResponse(id: requestId, result: subscriptionAuthWrapper) + let responseParams = NotifyProposeResponseParams(subscriptionAuth: subscriptionAuthWrapper, subscriptionSymKey: keys.sharedKey.hexRepresentation) + + let response = RPCResponse(id: requestId, result: responseParams) let protocolMethod = NotifyProposeProtocolMethod() @@ -87,3 +89,4 @@ class NotifyProposeResponder { return keys } } + diff --git a/Sources/WalletConnectPush/RPCRequests/NotifyProposeResponseParams.swift b/Sources/WalletConnectPush/RPCRequests/NotifyProposeResponseParams.swift new file mode 100644 index 000000000..a74c795bb --- /dev/null +++ b/Sources/WalletConnectPush/RPCRequests/NotifyProposeResponseParams.swift @@ -0,0 +1,7 @@ + +import Foundation + +struct NotifyProposeResponseParams: Codable { + let subscriptionAuth: SubscriptionJWTPayload.Wrapper + let subscriptionSymKey: String +} From 9b0c31705dab55b59293edeb7f71b0a95251e87b Mon Sep 17 00:00:00 2001 From: Radek Novak Date: Fri, 26 May 2023 12:26:12 +0200 Subject: [PATCH 27/33] Try using pull_request_target to allow access to secrets when approved --- .github/workflows/ci_v2.yml | 100 ++++++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 .github/workflows/ci_v2.yml diff --git a/.github/workflows/ci_v2.yml b/.github/workflows/ci_v2.yml new file mode 100644 index 000000000..ae90e0202 --- /dev/null +++ b/.github/workflows/ci_v2.yml @@ -0,0 +1,100 @@ +name: ci_v2 + +on: + pull_request_target: + branches: + - develop + - main + +concurrency: + group: ${{ github.workflow }}-${{ github.event_name == 'pull_request_target' && github.event.pull_request.number || github.ref_name }} + cancel-in-progress: ${{ github.event_name == 'pull_request_target' }} + +jobs: + authorize: + environment: + ${{ (github.event_name == 'pull_request_target' && + github.event.pull_request.head.repo.full_name != github.repository) && + 'external' || 'internal' }} + runs-on: ubuntu-latest + steps: + - run: echo ✓ + + prepare: + needs: authorize + runs-on: macos-12 + steps: + - uses: actions/checkout@v3 + + - uses: ./.github/actions/build + with: + project-id: ${{ secrets.PROJECT_ID }} + + test: + needs: prepare + runs-on: macos-12 + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + type: [relay-tests] # Put this back when verified it is working [integration-tests, relay-tests, unit-tests] + + steps: + - uses: actions/checkout@v3 + + - uses: actions/cache/restore@v3 + with: + path: | + products.tar + key: ${{ runner.os }}-deriveddata-${{ github.ref }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-deriveddata-${{ github.ref }}- + + - name: Untar DerivedDataCache + shell: bash + run: test -f products.tar && tar xPpf products.tar || echo "No artifacts to untar" + + # Package Unit tests + - name: Run tests + if: matrix.type == 'unit-tests' + shell: bash + run: make unit_tests + + # Integration tests + - name: Run integration tests + if: matrix.type == 'integration-tests' + shell: bash + run: make integration_tests RELAY_HOST=relay.walletconnect.com PROJECT_ID=${{ secrets.PROJECT_ID }} + + # Relay Integration tests + - name: Run Relay integration tests + if: matrix.type == 'relay-tests' + shell: bash + run: make relay_tests RELAY_HOST=relay.walletconnect.com PROJECT_ID=${{ secrets.PROJECT_ID }} + + # Smoke tests + - name: Run smoke tests + if: matrix.type == 'smoke-tests' + shell: bash + run: make smoke_tests RELAY_HOST=relay.walletconnect.com PROJECT_ID=${{ secrets.PROJECT_ID }} + + - name: Publish Test Report + uses: mikepenz/action-junit-report@v3 + if: success() || failure() + with: + check_name: ${{ matrix.type }} junit report + report_paths: 'test_results/report.junit' + + - name: Zip test artifacts + if: always() + shell: bash + run: test -d "test_results" && zip artifacts.zip -r ./test_results || echo "Nothing to zip" + + - name: Upload test artifacts + if: always() + uses: actions/upload-artifact@v3 + with: + name: ${{ matrix.type }} test_results + path: ./artifacts.zip + if-no-files-found: warn + From b0f7a4fc7df45ad4645a0b3ca0bf3596d2d72c1f Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 29 May 2023 08:14:50 +0200 Subject: [PATCH 28/33] fix proposal response restore delete subscription test --- Example/IntegrationTests/Push/PushTests.swift | 28 +++++++++++++++++++ .../DeletePushSubscriptionService.swift | 4 +-- .../NotifyProposeResponseSubscriber.swift | 13 ++++----- .../NotifyProposeResponder.swift | 9 ++++-- .../Client/Wallet/WalletPushClient.swift | 13 --------- .../Wallet/WalletPushClientFactory.swift | 2 -- 6 files changed, 43 insertions(+), 26 deletions(-) diff --git a/Example/IntegrationTests/Push/PushTests.swift b/Example/IntegrationTests/Push/PushTests.swift index 34826e30c..d58fc905b 100644 --- a/Example/IntegrationTests/Push/PushTests.swift +++ b/Example/IntegrationTests/Push/PushTests.swift @@ -149,6 +149,34 @@ final class PushTests: XCTestCase { wait(for: [expectation], timeout: InputConfig.defaultTimeout) } + + func testDeletePushSubscription() async { + let expectation = expectation(description: "expects to delete push subscription") + let uri = try! await dappPairingClient.create() + try! await walletPairingClient.pair(uri: uri) + try! await dappPushClient.propose(account: Account.stub(), topic: uri.topic) + var subscriptionTopic: String! + + walletPushClient.requestPublisher.sink { [unowned self] (id, _, _) in + Task(priority: .high) { try! await walletPushClient.approve(id: id, onSign: sign) } + }.store(in: &publishers) + + dappPushClient.proposalResponsePublisher.sink { [unowned self] (result) in + guard case .success(let pushSubscription) = result else { + XCTFail() + return + } + subscriptionTopic = pushSubscription.topic + Task(priority: .userInitiated) { try! await walletPushClient.deleteSubscription(topic: pushSubscription.topic)} + }.store(in: &publishers) + + dappPushClient.deleteSubscriptionPublisher.sink { topic in + XCTAssertEqual(subscriptionTopic, topic) + expectation.fulfill() + }.store(in: &publishers) + wait(for: [expectation], timeout: InputConfig.defaultTimeout) + } + func testWalletCreatesAndUpdatesSubscription() async { let expectation = expectation(description: "expects to create and update push subscription") let metadata = AppMetadata(name: "GM Dapp", description: "", url: "https://gm-dapp-xi.vercel.app/", icons: []) diff --git a/Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionService.swift b/Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionService.swift index eceb48b29..3f32e75df 100644 --- a/Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionService.swift +++ b/Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionService.swift @@ -24,11 +24,11 @@ class DeletePushSubscriptionService { } func delete(topic: String) async throws { + let params = PushDeleteParams.userDisconnected + logger.debug("Will delete push subscription for reason: message: \(params.message) code: \(params.code), topic: \(topic)") guard let _ = try? pushSubscriptionStore.get(key: topic) else { throw Errors.pushSubscriptionNotFound} let protocolMethod = PushDeleteProtocolMethod() - let params = PushDeleteParams.userDisconnected - logger.debug("Will delete push subscription for reason: message: \(params.message) code: \(params.code)") pushSubscriptionStore.delete(forKey: topic) pushMessagesDatabase?.deletePushMessages(topic: topic) let request = RPCRequest(method: protocolMethod.method, params: params) diff --git a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift index abf3f35a2..48061be1e 100644 --- a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift +++ b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift @@ -43,18 +43,17 @@ class NotifyProposeResponseSubscriber { }.store(in: &publishers) } - /// Implemented only for integration testing purpose, dapp client is not supported func handleResponse(payload: ResponseSubscriptionPayload) async throws -> PushSubscription { let (_, claims) = try SubscriptionJWTPayload.decodeAndVerify(from: payload.response.subscriptionAuth) logger.debug("subscriptionAuth JWT validated") - let expiry = Date(timeIntervalSince1970: TimeInterval(claims.exp)) - - let updateTopic = "update_topic" - + let subscriptionKey = try SymmetricKey(hex: payload.response.subscriptionSymKey) + let subscriptionTopic = subscriptionKey.rawRepresentation.sha256().toHexString() let relay = RelayProtocolOptions(protocol: "irn", data: nil) - - return PushSubscription(topic: updateTopic, account: payload.request.account, relay: relay, metadata: metadata, scope: [:], expiry: expiry) + let subscription = PushSubscription(topic: subscriptionTopic, account: payload.request.account, relay: relay, metadata: metadata, scope: [:], expiry: expiry) + try kms.setSymmetricKey(subscriptionKey, for: subscriptionTopic) + try await networkingInteractor.subscribe(topic: subscriptionTopic) + return subscription } private func subscribeForProposalErrors() { diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift index 475ab0a41..2680cefa6 100644 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift @@ -6,6 +6,7 @@ class NotifyProposeResponder { enum Errors: Error { case recordForIdNotFound case malformedRequestParams + case subscriptionNotFound } private let networkingInteractor: NetworkInteracting private let kms: KeyManagementServiceProtocol @@ -40,12 +41,14 @@ class NotifyProposeResponder { let subscriptionAuthWrapper = try await pushSubscribeRequester.subscribe(metadata: proposal.metadata, account: proposal.account, onSign: onSign) + var pushSubscription: PushSubscription! try await withCheckedThrowingContinuation { continuation in subscriptionResponsePublisher .first() .sink(receiveValue: { value in switch value { - case .success: + case .success(let subscription): + pushSubscription = subscription continuation.resume() case .failure(let error): continuation.resume(throwing: error) @@ -63,7 +66,9 @@ class NotifyProposeResponder { try kms.setSymmetricKey(keys.sharedKey, for: responseTopic) - let responseParams = NotifyProposeResponseParams(subscriptionAuth: subscriptionAuthWrapper, subscriptionSymKey: keys.sharedKey.hexRepresentation) + guard let subscriptionKey = kms.getSymmetricKeyRepresentable(for: pushSubscription.topic)?.toHexString() else { throw Errors.subscriptionNotFound } + + let responseParams = NotifyProposeResponseParams(subscriptionAuth: subscriptionAuthWrapper, subscriptionSymKey: subscriptionKey) let response = RPCResponse(id: requestId, result: responseParams) diff --git a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift index bd1a9ca88..fa8076d76 100644 --- a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift +++ b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift @@ -34,18 +34,11 @@ public class WalletPushClient { pushMessagePublisherSubject.eraseToAnyPublisher() } - private let deleteSubscriptionPublisherSubject = PassthroughSubject() - - public var deleteSubscriptionPublisher: AnyPublisher { - deleteSubscriptionPublisherSubject.eraseToAnyPublisher() - } - public var updateSubscriptionPublisher: AnyPublisher, Never> { return notifyUpdateResponseSubscriber.updateSubscriptionPublisher } private let deletePushSubscriptionService: DeletePushSubscriptionService - private let deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber private let pushSubscribeRequester: PushSubscribeRequester public let logger: ConsoleLogging @@ -69,7 +62,6 @@ public class WalletPushClient { subscriptionsProvider: SubscriptionsProvider, pushMessagesDatabase: PushMessagesDatabase, deletePushSubscriptionService: DeletePushSubscriptionService, - deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber, resubscribeService: PushResubscribeService, pushSubscriptionsObserver: PushSubscriptionsObserver, pushSubscribeRequester: PushSubscribeRequester, @@ -85,7 +77,6 @@ public class WalletPushClient { self.subscriptionsProvider = subscriptionsProvider self.pushMessagesDatabase = pushMessagesDatabase self.deletePushSubscriptionService = deletePushSubscriptionService - self.deletePushSubscriptionSubscriber = deletePushSubscriptionSubscriber self.resubscribeService = resubscribeService self.pushSubscriptionsObserver = pushSubscriptionsObserver self.pushSubscribeRequester = pushSubscribeRequester @@ -144,10 +135,6 @@ private extension WalletPushClient { pushMessageSubscriber.onPushMessage = { [unowned self] pushMessageRecord in pushMessagePublisherSubject.send(pushMessageRecord) } - - deletePushSubscriptionSubscriber.onDelete = {[unowned self] topic in - deleteSubscriptionPublisherSubject.send(topic) - } } } diff --git a/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift b/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift index 11a0539f0..55dd526f0 100644 --- a/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift +++ b/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift @@ -47,7 +47,6 @@ public struct WalletPushClientFactory { let pushMessageSubscriber = PushMessageSubscriber(networkingInteractor: networkInteractor, pushMessagesDatabase: pushMessagesDatabase, logger: logger) let subscriptionProvider = SubscriptionsProvider(store: subscriptionStore) let deletePushSubscriptionService = DeletePushSubscriptionService(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushSubscriptionStore: subscriptionStore, pushMessagesDatabase: pushMessagesDatabase) - let deletePushSubscriptionSubscriber = DeletePushSubscriptionSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushSubscriptionStore: subscriptionStore) let resubscribeService = PushResubscribeService(networkInteractor: networkInteractor, subscriptionsStorage: subscriptionStore) let pushSubscriptionsObserver = PushSubscriptionsObserver(store: subscriptionStore) @@ -73,7 +72,6 @@ public struct WalletPushClientFactory { subscriptionsProvider: subscriptionProvider, pushMessagesDatabase: pushMessagesDatabase, deletePushSubscriptionService: deletePushSubscriptionService, - deletePushSubscriptionSubscriber: deletePushSubscriptionSubscriber, resubscribeService: resubscribeService, pushSubscriptionsObserver: pushSubscriptionsObserver, pushSubscribeRequester: pushSubscribeRequester, From 9e9cc139b1dcbb4bbd6b34b0931b60f9a8ee0dff Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 29 May 2023 11:10:07 +0200 Subject: [PATCH 29/33] fix wallet build --- .../PushClientProxy/PushClientRequestSubscriber.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Sources/Web3Inbox/PushClientProxy/PushClientRequestSubscriber.swift b/Sources/Web3Inbox/PushClientProxy/PushClientRequestSubscriber.swift index 7cb07c45a..156de1516 100644 --- a/Sources/Web3Inbox/PushClientProxy/PushClientRequestSubscriber.swift +++ b/Sources/Web3Inbox/PushClientProxy/PushClientRequestSubscriber.swift @@ -27,10 +27,6 @@ final class PushClientRequestSubscriber { handle(event: .pushMessage, params: record) }.store(in: &publishers) - client.deleteSubscriptionPublisher.sink { [unowned self] record in - handle(event: .pushDelete, params: record) - }.store(in: &publishers) - client.subscriptionPublisher.sink { [unowned self] record in switch record { case .success(let subscription): From c96ce48f7832d11eb695b4cceffd49d6cc792bab Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 29 May 2023 14:06:54 +0200 Subject: [PATCH 30/33] fix propose response bug --- .../wc_notifyPropose/NotifyProposeResponseSubscriber.swift | 3 ++- .../wc_notifyPropose/NotifyProposeResponder.swift | 2 +- .../RPCRequests/NotifyProposeResponseParams.swift | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift index 48061be1e..75874af9f 100644 --- a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift +++ b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift @@ -44,7 +44,8 @@ class NotifyProposeResponseSubscriber { } func handleResponse(payload: ResponseSubscriptionPayload) async throws -> PushSubscription { - let (_, claims) = try SubscriptionJWTPayload.decodeAndVerify(from: payload.response.subscriptionAuth) + let jwtWrapper = SubscriptionJWTPayload.Wrapper(jwtString: payload.response.subscriptionAuth) + let (_, claims) = try SubscriptionJWTPayload.decodeAndVerify(from: jwtWrapper) logger.debug("subscriptionAuth JWT validated") let expiry = Date(timeIntervalSince1970: TimeInterval(claims.exp)) let subscriptionKey = try SymmetricKey(hex: payload.response.subscriptionSymKey) diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift index 2680cefa6..800fe67aa 100644 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift @@ -68,7 +68,7 @@ class NotifyProposeResponder { guard let subscriptionKey = kms.getSymmetricKeyRepresentable(for: pushSubscription.topic)?.toHexString() else { throw Errors.subscriptionNotFound } - let responseParams = NotifyProposeResponseParams(subscriptionAuth: subscriptionAuthWrapper, subscriptionSymKey: subscriptionKey) + let responseParams = NotifyProposeResponseParams(subscriptionAuth: subscriptionAuthWrapper.subscriptionAuth, subscriptionSymKey: subscriptionKey) let response = RPCResponse(id: requestId, result: responseParams) diff --git a/Sources/WalletConnectPush/RPCRequests/NotifyProposeResponseParams.swift b/Sources/WalletConnectPush/RPCRequests/NotifyProposeResponseParams.swift index a74c795bb..afc24746d 100644 --- a/Sources/WalletConnectPush/RPCRequests/NotifyProposeResponseParams.swift +++ b/Sources/WalletConnectPush/RPCRequests/NotifyProposeResponseParams.swift @@ -2,6 +2,7 @@ import Foundation struct NotifyProposeResponseParams: Codable { - let subscriptionAuth: SubscriptionJWTPayload.Wrapper + let subscriptionAuth: String let subscriptionSymKey: String } + From 1337945ebd0273a01d5184077113c27c01bfea88 Mon Sep 17 00:00:00 2001 From: radeknovis Date: Mon, 29 May 2023 15:44:07 +0000 Subject: [PATCH 31/33] Set User Agent --- Sources/WalletConnectRelay/PackageConfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/WalletConnectRelay/PackageConfig.json b/Sources/WalletConnectRelay/PackageConfig.json index bade60780..7a1a2d657 100644 --- a/Sources/WalletConnectRelay/PackageConfig.json +++ b/Sources/WalletConnectRelay/PackageConfig.json @@ -1 +1 @@ -{"version": "1.6.4"} +{"version": "1.6.5"} From cd04595f96916892332f10dc156f02ce27212f0b Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Mon, 29 May 2023 18:20:09 +0200 Subject: [PATCH 32/33] unsubscribe on error response --- .../Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift index 75874af9f..beed07644 100644 --- a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift +++ b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift @@ -62,6 +62,7 @@ class NotifyProposeResponseSubscriber { networkingInteractor.responseErrorSubscription(on: protocolMethod) .sink { [unowned self] (payload: ResponseSubscriptionErrorPayload) in kms.deletePrivateKey(for: payload.request.publicKey) + networkingInteractor.unsubscribe(topic: payload.topic) guard let error = PushError(code: payload.error.code) else { return } proposalResponsePublisherSubject.send(.failure(error)) }.store(in: &publishers) From 9316bdc4e437a214b67133c0e58a994bb288796d Mon Sep 17 00:00:00 2001 From: llbartekll Date: Tue, 30 May 2023 05:00:56 +0000 Subject: [PATCH 33/33] Set User Agent --- Sources/WalletConnectRelay/PackageConfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/WalletConnectRelay/PackageConfig.json b/Sources/WalletConnectRelay/PackageConfig.json index 7a1a2d657..a529721f4 100644 --- a/Sources/WalletConnectRelay/PackageConfig.json +++ b/Sources/WalletConnectRelay/PackageConfig.json @@ -1 +1 @@ -{"version": "1.6.5"} +{"version": "1.6.6"}