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: NotifyUpdateRequesting { 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 new file mode 100644 index 000000000..d6e939a5b --- /dev/null +++ b/Sources/WalletConnectPush/Client/Wallet/SubscriptionsAutoUpdater.swift @@ -0,0 +1,46 @@ + +import Foundation + +class SubscriptionsAutoUpdater { + private let notifyUpdateRequester: NotifyUpdateRequesting + private let logger: ConsoleLogging + private let pushStorage: PushStoring + + init(notifyUpdateRequester: NotifyUpdateRequesting, + logger: ConsoleLogging, + pushStorage: PushStoring) { + self.notifyUpdateRequester = notifyUpdateRequester + self.logger = logger + self.pushStorage = pushStorage + updateSubscriptionsIfNeeded() + } + + private 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 + } + } +} 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 ) } } 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 new file mode 100644 index 000000000..60f0c09c3 --- /dev/null +++ b/Tests/NotifyTests/SubscriptionsAutoUpdaterTests.swift @@ -0,0 +1,36 @@ +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"]) + } +}