From 726e357a628eeb825fda604be7ca65a289bd61b7 Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 5 Jul 2023 11:20:11 +0200 Subject: [PATCH 1/4] add SubscriptionsAutoUpdater --- .../Wallet/SubscriptionsAutoUpdater.swift | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 Sources/WalletConnectPush/Client/Wallet/SubscriptionsAutoUpdater.swift diff --git a/Sources/WalletConnectPush/Client/Wallet/SubscriptionsAutoUpdater.swift b/Sources/WalletConnectPush/Client/Wallet/SubscriptionsAutoUpdater.swift new file mode 100644 index 000000000..9d3495ef9 --- /dev/null +++ b/Sources/WalletConnectPush/Client/Wallet/SubscriptionsAutoUpdater.swift @@ -0,0 +1,47 @@ + +import Foundation + +class SubscriptionsAutoUpdater { + private let notifyUpdateRequester: NotifyUpdateRequester + private let logger: ConsoleLogging + private let pushStorage: PushStorage + + + init(notifyUpdateRequester: NotifyUpdateRequester, + logger: ConsoleLogging, + pushStorage: PushStorage) { + self.notifyUpdateRequester = notifyUpdateRequester + self.logger = logger + self.pushStorage = pushStorage + } + + func updateSubscriptionsIfNeeded() { + for subscription in pushStorage.getSubscriptions() { + if shouldUpdate(subscription: subscription) { + let scope = Set(subscription.scope.filter{ $0.value.enabled == true }.keys) + let topic = subscription.topic + Task { + do { + try await notifyUpdateRequester.update(topic: topic, scope: scope) + } catch { + logger.error("Failed to update subscription for topic: \(topic)") + } + } + } + } + } + + private func shouldUpdate(subscription: PushSubscription) -> Bool { + let currentDate = Date() + let calendar = Calendar.current + let expiryDate = subscription.expiry + let dateComponents = calendar.dateComponents([.day], from: currentDate, to: expiryDate) + if let numberOfDays = dateComponents.day, + numberOfDays < 14 { + return true + } else { + return false + } + } + +} From 8891eb6b7c4da7c96a3b5984be21c39986069afb Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 5 Jul 2023 13:16:25 +0200 Subject: [PATCH 2/4] add unit tests - SubscriptionsAutoUpdaterTests add notify tests target --- .../xcschemes/WalletConnectPush.xcscheme | 24 +++++ Package.swift | 3 + .../NotifyUpdateRequester.swift | 4 + .../Client/Wallet/PushStorage.swift | 9 +- .../Wallet/SubscriptionsAutoUpdater.swift | 13 ++- .../SubscriptionsAutoUpdaterTests.swift | 97 +++++++++++++++++++ 6 files changed, 142 insertions(+), 8 deletions(-) create mode 100644 Tests/NotifyTests/SubscriptionsAutoUpdaterTests.swift diff --git a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectPush.xcscheme b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectPush.xcscheme index 48c7aad62..c779cb9d1 100644 --- a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectPush.xcscheme +++ b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectPush.xcscheme @@ -20,6 +20,20 @@ ReferencedContainer = "container:.."> + + + + + + + + ) async throws +} + class NotifyUpdateRequester { enum Errors: Error { case noSubscriptionForGivenTopic diff --git a/Sources/WalletConnectPush/Client/Wallet/PushStorage.swift b/Sources/WalletConnectPush/Client/Wallet/PushStorage.swift index 8393c734f..091b390ec 100644 --- a/Sources/WalletConnectPush/Client/Wallet/PushStorage.swift +++ b/Sources/WalletConnectPush/Client/Wallet/PushStorage.swift @@ -1,7 +1,14 @@ import Foundation import Combine -final class PushStorage { +protocol PushStoring { + func getSubscriptions() -> [PushSubscription] + func getSubscription(topic: String) -> PushSubscription? + func setSubscription(_ subscription: PushSubscription) async throws + func deleteSubscription(topic: String) async throws +} + +final class PushStorage: PushStoring { private var publishers = Set() diff --git a/Sources/WalletConnectPush/Client/Wallet/SubscriptionsAutoUpdater.swift b/Sources/WalletConnectPush/Client/Wallet/SubscriptionsAutoUpdater.swift index 9d3495ef9..d6e939a5b 100644 --- a/Sources/WalletConnectPush/Client/Wallet/SubscriptionsAutoUpdater.swift +++ b/Sources/WalletConnectPush/Client/Wallet/SubscriptionsAutoUpdater.swift @@ -2,20 +2,20 @@ import Foundation class SubscriptionsAutoUpdater { - private let notifyUpdateRequester: NotifyUpdateRequester + private let notifyUpdateRequester: NotifyUpdateRequesting private let logger: ConsoleLogging - private let pushStorage: PushStorage + private let pushStorage: PushStoring - - init(notifyUpdateRequester: NotifyUpdateRequester, + init(notifyUpdateRequester: NotifyUpdateRequesting, logger: ConsoleLogging, - pushStorage: PushStorage) { + pushStorage: PushStoring) { self.notifyUpdateRequester = notifyUpdateRequester self.logger = logger self.pushStorage = pushStorage + updateSubscriptionsIfNeeded() } - func updateSubscriptionsIfNeeded() { + private func updateSubscriptionsIfNeeded() { for subscription in pushStorage.getSubscriptions() { if shouldUpdate(subscription: subscription) { let scope = Set(subscription.scope.filter{ $0.value.enabled == true }.keys) @@ -43,5 +43,4 @@ class SubscriptionsAutoUpdater { return false } } - } diff --git a/Tests/NotifyTests/SubscriptionsAutoUpdaterTests.swift b/Tests/NotifyTests/SubscriptionsAutoUpdaterTests.swift new file mode 100644 index 000000000..4d8d9100f --- /dev/null +++ b/Tests/NotifyTests/SubscriptionsAutoUpdaterTests.swift @@ -0,0 +1,97 @@ +import Foundation +import XCTest +import TestingUtils +@testable import WalletConnectPush + +class SubscriptionsAutoUpdaterTests: XCTestCase { + var sut: SubscriptionsAutoUpdater! + + func testUpdateSubscriptionsIfNeeded() async { + let subscriptions: [PushSubscription] = [ + PushSubscription.stub(topic: "topic1", expiry: Date().addingTimeInterval(60 * 60 * 24 * 20)), + PushSubscription.stub(topic: "topic2", expiry: Date().addingTimeInterval(60 * 60 * 24 * 10)), + PushSubscription.stub(topic: "topic3", expiry: Date().addingTimeInterval(60 * 60 * 24 * 30)) + ] + + let expectation = expectation(description: "update") + + let notifyUpdateRequester = MockNotifyUpdateRequester() + let logger = ConsoleLoggerMock() + let pushStorage = MockPushStoring(subscriptions: subscriptions) + + notifyUpdateRequester.completionHandler = { + if notifyUpdateRequester.updatedTopics.contains("topic2") { + expectation.fulfill() + } + } + + sut = SubscriptionsAutoUpdater(notifyUpdateRequester: notifyUpdateRequester, + logger: logger, + pushStorage: pushStorage) + + await waitForExpectations(timeout: 1, handler: nil) + + XCTAssertEqual(notifyUpdateRequester.updatedTopics, ["topic2"]) + } +} + + +extension PushSubscription { + static func stub(topic: String, expiry: Date) -> PushSubscription { + let account = Account(chainIdentifier: "eip155:1", address: "0x15bca56b6e2728aec2532df9d436bd1600e86688")! + let relay = RelayProtocolOptions.stub() + let metadata = AppMetadata.stub() + let symKey = "key1" + + return PushSubscription( + topic: topic, + account: account, + relay: relay, + metadata: metadata, + scope: ["test": ScopeValue(description: "desc", enabled: true)], + expiry: expiry, + symKey: symKey + ) + } +} + + +class MockNotifyUpdateRequester: NotifyUpdateRequesting { + var updatedTopics: [String] = [] + var completionHandler: (() -> Void)? + + func update(topic: String, scope: Set) async throws { + updatedTopics.append(topic) + completionHandler?() + } +} + + + +class MockPushStoring: PushStoring { + var subscriptions: [PushSubscription] + + init(subscriptions: [PushSubscription]) { + self.subscriptions = subscriptions + } + + func getSubscriptions() -> [PushSubscription] { + return subscriptions + } + + func getSubscription(topic: String) -> PushSubscription? { + return subscriptions.first { $0.topic == topic } + } + + func setSubscription(_ subscription: PushSubscription) async throws { + if let index = subscriptions.firstIndex(where: { $0.topic == subscription.topic }) { + subscriptions[index] = subscription + } else { + subscriptions.append(subscription) + } + } + + func deleteSubscription(topic: String) async throws { + subscriptions.removeAll(where: { $0.topic == topic }) + } +} From 646d35766e76ce8615c02778bca2876f07960e3b Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 5 Jul 2023 13:25:46 +0200 Subject: [PATCH 3/4] extract mocks and stubs --- .../Mocks/MockNotifyUpdateRequester.swift | 15 +++++ Tests/NotifyTests/Mocks/MockPushStoring.swift | 31 ++++++++++ .../NotifyTests/Stubs/PushSubscription.swift | 23 +++++++ .../SubscriptionsAutoUpdaterTests.swift | 61 ------------------- 4 files changed, 69 insertions(+), 61 deletions(-) create mode 100644 Tests/NotifyTests/Mocks/MockNotifyUpdateRequester.swift create mode 100644 Tests/NotifyTests/Mocks/MockPushStoring.swift create mode 100644 Tests/NotifyTests/Stubs/PushSubscription.swift diff --git a/Tests/NotifyTests/Mocks/MockNotifyUpdateRequester.swift b/Tests/NotifyTests/Mocks/MockNotifyUpdateRequester.swift new file mode 100644 index 000000000..8f4d698e1 --- /dev/null +++ b/Tests/NotifyTests/Mocks/MockNotifyUpdateRequester.swift @@ -0,0 +1,15 @@ + +import Foundation +@testable import WalletConnectPush + + +class MockNotifyUpdateRequester: NotifyUpdateRequesting { + var updatedTopics: [String] = [] + var completionHandler: (() -> Void)? + + func update(topic: String, scope: Set) async throws { + updatedTopics.append(topic) + completionHandler?() + } +} + diff --git a/Tests/NotifyTests/Mocks/MockPushStoring.swift b/Tests/NotifyTests/Mocks/MockPushStoring.swift new file mode 100644 index 000000000..06dc07960 --- /dev/null +++ b/Tests/NotifyTests/Mocks/MockPushStoring.swift @@ -0,0 +1,31 @@ + +import Foundation +@testable import WalletConnectPush + +class MockPushStoring: PushStoring { + var subscriptions: [PushSubscription] + + init(subscriptions: [PushSubscription]) { + self.subscriptions = subscriptions + } + + func getSubscriptions() -> [PushSubscription] { + return subscriptions + } + + func getSubscription(topic: String) -> PushSubscription? { + return subscriptions.first { $0.topic == topic } + } + + func setSubscription(_ subscription: PushSubscription) async throws { + if let index = subscriptions.firstIndex(where: { $0.topic == subscription.topic }) { + subscriptions[index] = subscription + } else { + subscriptions.append(subscription) + } + } + + func deleteSubscription(topic: String) async throws { + subscriptions.removeAll(where: { $0.topic == topic }) + } +} diff --git a/Tests/NotifyTests/Stubs/PushSubscription.swift b/Tests/NotifyTests/Stubs/PushSubscription.swift new file mode 100644 index 000000000..de40c5772 --- /dev/null +++ b/Tests/NotifyTests/Stubs/PushSubscription.swift @@ -0,0 +1,23 @@ + +import Foundation +@testable import WalletConnectPush + +extension PushSubscription { + static func stub(topic: String, expiry: Date) -> PushSubscription { + let account = Account(chainIdentifier: "eip155:1", address: "0x15bca56b6e2728aec2532df9d436bd1600e86688")! + let relay = RelayProtocolOptions.stub() + let metadata = AppMetadata.stub() + let symKey = "key1" + + return PushSubscription( + topic: topic, + account: account, + relay: relay, + metadata: metadata, + scope: ["test": ScopeValue(description: "desc", enabled: true)], + expiry: expiry, + symKey: symKey + ) + } +} + diff --git a/Tests/NotifyTests/SubscriptionsAutoUpdaterTests.swift b/Tests/NotifyTests/SubscriptionsAutoUpdaterTests.swift index 4d8d9100f..60f0c09c3 100644 --- a/Tests/NotifyTests/SubscriptionsAutoUpdaterTests.swift +++ b/Tests/NotifyTests/SubscriptionsAutoUpdaterTests.swift @@ -34,64 +34,3 @@ class SubscriptionsAutoUpdaterTests: XCTestCase { XCTAssertEqual(notifyUpdateRequester.updatedTopics, ["topic2"]) } } - - -extension PushSubscription { - static func stub(topic: String, expiry: Date) -> PushSubscription { - let account = Account(chainIdentifier: "eip155:1", address: "0x15bca56b6e2728aec2532df9d436bd1600e86688")! - let relay = RelayProtocolOptions.stub() - let metadata = AppMetadata.stub() - let symKey = "key1" - - return PushSubscription( - topic: topic, - account: account, - relay: relay, - metadata: metadata, - scope: ["test": ScopeValue(description: "desc", enabled: true)], - expiry: expiry, - symKey: symKey - ) - } -} - - -class MockNotifyUpdateRequester: NotifyUpdateRequesting { - var updatedTopics: [String] = [] - var completionHandler: (() -> Void)? - - func update(topic: String, scope: Set) async throws { - updatedTopics.append(topic) - completionHandler?() - } -} - - - -class MockPushStoring: PushStoring { - var subscriptions: [PushSubscription] - - init(subscriptions: [PushSubscription]) { - self.subscriptions = subscriptions - } - - func getSubscriptions() -> [PushSubscription] { - return subscriptions - } - - func getSubscription(topic: String) -> PushSubscription? { - return subscriptions.first { $0.topic == topic } - } - - func setSubscription(_ subscription: PushSubscription) async throws { - if let index = subscriptions.firstIndex(where: { $0.topic == subscription.topic }) { - subscriptions[index] = subscription - } else { - subscriptions.append(subscription) - } - } - - func deleteSubscription(topic: String) async throws { - subscriptions.removeAll(where: { $0.topic == topic }) - } -} From de21e62085381d6f1c105d5e1d14172e1e4c41af Mon Sep 17 00:00:00 2001 From: Bartosz Rozwarski Date: Wed, 5 Jul 2023 13:28:38 +0200 Subject: [PATCH 4/4] update wallet push factory --- .../wc_notifyUpdate/NotifyUpdateRequester.swift | 2 +- .../WalletConnectPush/Client/Wallet/WalletPushClient.swift | 5 ++++- .../Client/Wallet/WalletPushClientFactory.swift | 5 ++++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift index 65813f9c5..aa1c4fffe 100644 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift @@ -4,7 +4,7 @@ protocol NotifyUpdateRequesting { func update(topic: String, scope: Set) async throws } -class NotifyUpdateRequester { +class NotifyUpdateRequester: NotifyUpdateRequesting { enum Errors: Error { case noSubscriptionForGivenTopic } diff --git a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift index ee6b5c6ca..0e41dbdd2 100644 --- a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift +++ b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift @@ -52,6 +52,7 @@ public class WalletPushClient { private let notifyUpdateResponseSubscriber: NotifyUpdateResponseSubscriber private let notifyProposeResponder: NotifyProposeResponder private let notifyProposeSubscriber: NotifyProposeSubscriber + private let subscriptionsAutoUpdater: SubscriptionsAutoUpdater init(logger: ConsoleLogging, kms: KeyManagementServiceProtocol, @@ -68,7 +69,8 @@ public class WalletPushClient { notifyUpdateRequester: NotifyUpdateRequester, notifyUpdateResponseSubscriber: NotifyUpdateResponseSubscriber, notifyProposeResponder: NotifyProposeResponder, - notifyProposeSubscriber: NotifyProposeSubscriber + notifyProposeSubscriber: NotifyProposeSubscriber, + subscriptionsAutoUpdater: SubscriptionsAutoUpdater ) { self.logger = logger self.echoClient = echoClient @@ -85,6 +87,7 @@ public class WalletPushClient { self.notifyUpdateResponseSubscriber = notifyUpdateResponseSubscriber self.notifyProposeResponder = notifyProposeResponder self.notifyProposeSubscriber = notifyProposeSubscriber + self.subscriptionsAutoUpdater = subscriptionsAutoUpdater } public func enableSync(account: Account, onSign: @escaping SigningCallback) async throws { diff --git a/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift b/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift index f57d7b7ef..f65c4928b 100644 --- a/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift +++ b/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift @@ -62,6 +62,8 @@ public struct WalletPushClientFactory { let deletePushSubscriptionSubscriber = DeletePushSubscriptionSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushStorage: pushStorage) + let subscriptionsAutoUpdater = SubscriptionsAutoUpdater(notifyUpdateRequester: notifyUpdateRequester, logger: logger, pushStorage: pushStorage) + return WalletPushClient( logger: logger, kms: kms, @@ -78,7 +80,8 @@ public struct WalletPushClientFactory { notifyUpdateRequester: notifyUpdateRequester, notifyUpdateResponseSubscriber: notifyUpdateResponseSubscriber, notifyProposeResponder: notifyProposeResponder, - notifyProposeSubscriber: notifyProposeSubscriber + notifyProposeSubscriber: notifyProposeSubscriber, + subscriptionsAutoUpdater: subscriptionsAutoUpdater ) } }