diff --git a/Example/IntegrationTests/Push/NotifyTests.swift b/Example/IntegrationTests/Push/NotifyTests.swift index 97edfdd0a..9c9fdc96d 100644 --- a/Example/IntegrationTests/Push/NotifyTests.swift +++ b/Example/IntegrationTests/Push/NotifyTests.swift @@ -136,13 +136,13 @@ final class NotifyTests: XCTestCase { let expectation = expectation(description: "expects client B to receive subscription after both clients are registered and client A creates one") expectation.assertForOverFulfill = false + var subscription: NotifySubscription! + let clientB = makeWalletClient(prefix: "👐🏼 Wallet B: ") clientB.subscriptionsPublisher.sink { subscriptions in - guard let subscription = subscriptions.first else { return } - Task(priority: .high) { - try await clientB.deleteSubscription(topic: subscription.topic) - expectation.fulfill() - } + guard let newSubscription = subscriptions.first else { return } + subscription = newSubscription + expectation.fulfill() }.store(in: &publishers) try! await walletNotifyClientA.register(account: account, domain: gmDappDomain, onSign: sign) @@ -150,6 +150,8 @@ final class NotifyTests: XCTestCase { try! await walletNotifyClientA.subscribe(appDomain: gmDappDomain, account: account) wait(for: [expectation], timeout: InputConfig.defaultTimeout) + + try await clientB.deleteSubscription(topic: subscription.topic) } func testWalletCreatesAndUpdatesSubscription() async { diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsInteractor.swift index 3eeeb8ea3..1a1bd5b8f 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsInteractor.swift @@ -36,20 +36,12 @@ final class NotificationsInteractor { return try await withCheckedThrowingContinuation { continuation in var cancellable: AnyCancellable? cancellable = subscriptionsPublisher - .setFailureType(to: Error.self) - .timeout(10, scheduler: RunLoop.main, customError: { Errors.subscribeTimeout }) - .sink(receiveCompletion: { completion in - defer { cancellable?.cancel() } - switch completion { - case .failure(let error): continuation.resume(with: .failure(error)) - case .finished: break - } - }, receiveValue: { subscriptions in + .sink { subscriptions in guard subscriptions.contains(where: { $0.metadata.url == domain }) else { return } cancellable?.cancel() continuation.resume(with: .success(())) - }) - + } + Task { [cancellable] in do { try await Notify.instance.subscribe(appDomain: domain, account: importAccount.account) diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift index 29a58e77f..b4e04c5be 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift @@ -78,7 +78,7 @@ public class NotifyClient { public func register(account: Account, domain: String, isLimited: Bool = false, onSign: @escaping SigningCallback) async throws { try await identityService.register(account: account, domain: domain, isLimited: isLimited, onSign: onSign) notifyAccountProvider.setAccount(account) - subscriptionWatcher.start() + try await subscriptionWatcher.start() } public func unregister(account: Account) async throws { @@ -132,6 +132,11 @@ public class NotifyClient { #if targetEnvironment(simulator) extension NotifyClient { + + public var subscriptionChangedPublisher: AnyPublisher<[NotifySubscription], Never> { + return notifySubscriptionsChangedRequestSubscriber.subscriptionChangedPublisher + } + public func register(deviceToken: String) async throws { try await pushClient.register(deviceToken: deviceToken) } diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift index f04b62a14..9d0e41f4a 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift @@ -47,7 +47,7 @@ public struct NotifyClientFactory { let identityClient = IdentityClientFactory.create(keyserver: keyserverURL, keychain: keychainStorage, logger: logger) let notifyMessageSubscriber = NotifyMessageSubscriber(keyserver: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, notifyStorage: notifyStorage, crypto: crypto, logger: logger) let webDidResolver = NotifyWebDidResolver() - let deleteNotifySubscriptionRequester = DeleteNotifySubscriptionRequester(keyserver: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, webDidResolver: webDidResolver, kms: kms, logger: logger, notifyStorage: notifyStorage) + let deleteNotifySubscriptionRequester = DeleteNotifySubscriptionRequester(keyserver: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, kms: kms, logger: logger, notifyStorage: notifyStorage) let resubscribeService = NotifyResubscribeService(networkInteractor: networkInteractor, notifyStorage: notifyStorage, logger: logger) let notifyConfigProvider = NotifyConfigProvider(projectId: projectId, explorerHost: explorerHost) @@ -56,7 +56,7 @@ public struct NotifyClientFactory { let notifySubscribeResponseSubscriber = NotifySubscribeResponseSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, groupKeychainStorage: groupKeychainStorage, notifyStorage: notifyStorage, notifyConfigProvider: notifyConfigProvider) - let notifyUpdateRequester = NotifyUpdateRequester(keyserverURL: keyserverURL, webDidResolver: webDidResolver, identityClient: identityClient, networkingInteractor: networkInteractor, notifyConfigProvider: notifyConfigProvider, logger: logger, notifyStorage: notifyStorage) + let notifyUpdateRequester = NotifyUpdateRequester(keyserverURL: keyserverURL, identityClient: identityClient, networkingInteractor: networkInteractor, notifyConfigProvider: notifyConfigProvider, logger: logger, notifyStorage: notifyStorage) let notifyUpdateResponseSubscriber = NotifyUpdateResponseSubscriber(networkingInteractor: networkInteractor, logger: logger, notifyConfigProvider: notifyConfigProvider, notifyStorage: notifyStorage) diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift index a7e2c92b8..ab873dcf1 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift @@ -89,7 +89,7 @@ final class NotifyStorage: NotifyStoring { func updateSubscription(_ subscription: NotifySubscription, scope: [String: ScopeValue], expiry: UInt64) { let expiry = Date(timeIntervalSince1970: TimeInterval(expiry)) - let updated = NotifySubscription(topic: subscription.topic, account: subscription.account, relay: subscription.relay, metadata: subscription.metadata, scope: scope, expiry: expiry, symKey: subscription.symKey) + let updated = NotifySubscription(topic: subscription.topic, account: subscription.account, relay: subscription.relay, metadata: subscription.metadata, scope: scope, expiry: expiry, symKey: subscription.symKey, appAuthenticationKey: subscription.appAuthenticationKey) subscriptionStore.set(element: updated, for: updated.account.absoluteString) updateSubscriptionSubject.send(updated) } diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifySubscriptionsBuilder.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifySubscriptionsBuilder.swift index d2c9cd582..0426ce460 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifySubscriptionsBuilder.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifySubscriptionsBuilder.swift @@ -24,7 +24,8 @@ class NotifySubscriptionsBuilder { metadata: config.metadata, scope: scope, expiry: subscription.expiry, - symKey: subscription.symKey + symKey: subscription.symKey, + appAuthenticationKey: subscription.appAuthenticationKey )) } catch { continue diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyWebDidResolver.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyWebDidResolver.swift index 5312e28bd..33a8449e6 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/NotifyWebDidResolver.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyWebDidResolver.swift @@ -5,23 +5,21 @@ final class NotifyWebDidResolver { private static var subscribeKey = "wc-notify-subscribe-key" private static var authenticationKey = "wc-notify-authentication-key" - func resolveAgreementKey(domain: String) async throws -> AgreementPublicKey { - let didDoc = try await resolveDidDoc(domainUrl: domain) - let subscribeKeyPath = "\(didDoc.id)#\(Self.subscribeKey)" - guard let verificationMethod = didDoc.verificationMethod.first(where: { verificationMethod in verificationMethod.id == subscribeKeyPath }) else { throw Errors.noVerificationMethodForKey } - guard verificationMethod.publicKeyJwk.crv == .X25519 else { throw Errors.unsupportedCurve} - let pubKeyBase64Url = verificationMethod.publicKeyJwk.x - return try AgreementPublicKey(base64url: pubKeyBase64Url) + func resolveDidDoc(appDomain: String) async throws -> WebDidDoc { + guard let didDocUrl = URL(string: "https://\(appDomain)/.well-known/did.json") else { throw Errors.invalidUrl } + let (data, _) = try await URLSession.shared.data(from: didDocUrl) + return try JSONDecoder().decode(WebDidDoc.self, from: data) } - // TODO - Add cache for diddocs + func resolveAgreementKey(didDoc: WebDidDoc) throws -> AgreementPublicKey { + let keypath = "\(didDoc.id)#\(Self.subscribeKey)" + let pubKeyBase64Url = try resolveKey(didDoc: didDoc, curve: .X25519, keyPath: keypath) + return try AgreementPublicKey(base64url: pubKeyBase64Url) + } - func resolveAuthenticationKey(domain: String) async throws -> Data { - let didDoc = try await resolveDidDoc(domainUrl: domain) - let authenticationKeyPath = "\(didDoc.id)#\(Self.authenticationKey)" - guard let verificationMethod = didDoc.verificationMethod.first(where: { verificationMethod in verificationMethod.id == authenticationKeyPath }) else { throw Errors.noVerificationMethodForKey } - guard verificationMethod.publicKeyJwk.crv == .Ed25519 else { throw Errors.unsupportedCurve} - let pubKeyBase64Url = verificationMethod.publicKeyJwk.x + func resolveAuthenticationKey(didDoc: WebDidDoc) throws -> Data { + let keyPath = "\(didDoc.id)#\(Self.authenticationKey)" + let pubKeyBase64Url = try resolveKey(didDoc: didDoc, curve: .Ed25519, keyPath: keyPath) guard let raw = Data(base64url: pubKeyBase64Url) else { throw Errors.invalidBase64urlString } return raw } @@ -36,9 +34,9 @@ private extension NotifyWebDidResolver { case unsupportedCurve } - func resolveDidDoc(domainUrl: String) async throws -> WebDidDoc { - guard let didDocUrl = URL(string: "https://\(domainUrl)/.well-known/did.json") else { throw Errors.invalidUrl } - let (data, _) = try await URLSession.shared.data(from: didDocUrl) - return try JSONDecoder().decode(WebDidDoc.self, from: data) + func resolveKey(didDoc: WebDidDoc, curve: WebDidDoc.PublicKeyJwk.Curve, keyPath: String) throws -> String { + guard let verificationMethod = didDoc.verificationMethod.first(where: { verificationMethod in verificationMethod.id == keyPath }) else { throw Errors.noVerificationMethodForKey } + guard verificationMethod.publicKeyJwk.crv == curve else { throw Errors.unsupportedCurve } + return verificationMethod.publicKeyJwk.x } } diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/DeleteNotifySubscriptionRequester.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/DeleteNotifySubscriptionRequester.swift index ea3281849..a94e86420 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/DeleteNotifySubscriptionRequester.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyDelete/DeleteNotifySubscriptionRequester.swift @@ -7,7 +7,6 @@ class DeleteNotifySubscriptionRequester { private let keyserver: URL private let networkingInteractor: NetworkInteracting private let identityClient: IdentityClient - private let webDidResolver: NotifyWebDidResolver private let kms: KeyManagementServiceProtocol private let logger: ConsoleLogging private let notifyStorage: NotifyStorage @@ -16,7 +15,6 @@ class DeleteNotifySubscriptionRequester { keyserver: URL, networkingInteractor: NetworkInteracting, identityClient: IdentityClient, - webDidResolver: NotifyWebDidResolver, kms: KeyManagementServiceProtocol, logger: ConsoleLogging, notifyStorage: NotifyStorage @@ -24,7 +22,6 @@ class DeleteNotifySubscriptionRequester { self.keyserver = keyserver self.networkingInteractor = networkingInteractor self.identityClient = identityClient - self.webDidResolver = webDidResolver self.kms = kms self.logger = logger self.notifyStorage = notifyStorage @@ -37,10 +34,10 @@ class DeleteNotifySubscriptionRequester { else { throw Errors.notifySubscriptionNotFound} let protocolMethod = NotifyDeleteProtocolMethod() - let dappAuthenticationKey = try await webDidResolver.resolveAuthenticationKey(domain: subscription.metadata.url) + let dappAuthenticationKey = try DIDKey(did: subscription.appAuthenticationKey) let wrapper = try createJWTWrapper( - dappPubKey: DIDKey(rawData: dappAuthenticationKey), + dappPubKey: dappAuthenticationKey, reason: NotifyDeleteParams.userDisconnected.message, app: DIDWeb(host: subscription.metadata.url), account: subscription.account @@ -57,11 +54,7 @@ class DeleteNotifySubscriptionRequester { logger.debug("Subscription removed, topic: \(topic)") kms.deleteSymmetricKey(for: topic) - } - - - - + } } private extension DeleteNotifySubscriptionRequester { diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifySubscriptionsChanged/NotifySubscriptionsChangedRequestSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifySubscriptionsChanged/NotifySubscriptionsChangedRequestSubscriber.swift index 95d92a0a1..18c8bbc31 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifySubscriptionsChanged/NotifySubscriptionsChangedRequestSubscriber.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifySubscriptionsChanged/NotifySubscriptionsChangedRequestSubscriber.swift @@ -11,6 +11,12 @@ class NotifySubscriptionsChangedRequestSubscriber { private let notifyStorage: NotifyStorage private let notifySubscriptionsBuilder: NotifySubscriptionsBuilder + private let subscriptionChangedSubject = PassthroughSubject<[NotifySubscription], Never>() + + var subscriptionChangedPublisher: AnyPublisher<[NotifySubscription], Never> { + return subscriptionChangedSubject.eraseToAnyPublisher() + } + init( keyserver: URL, networkingInteractor: NetworkInteracting, @@ -47,37 +53,39 @@ class NotifySubscriptionsChangedRequestSubscriber { let oldSubscriptions = notifyStorage.getSubscriptions(account: account) let newSubscriptions = try await notifySubscriptionsBuilder.buildSubscriptions(jwtPayload.subscriptions) - + + subscriptionChangedSubject.send(newSubscriptions) + try Task.checkCancellation() let subscriptions = oldSubscriptions.difference(from: newSubscriptions) logger.debug("Received: \(newSubscriptions.count), changed: \(subscriptions.count)") - guard subscriptions.count > 0 else { return } + if subscriptions.count > 0 { + notifyStorage.replaceAllSubscriptions(newSubscriptions, account: account) - notifyStorage.replaceAllSubscriptions(newSubscriptions, account: account) + for subscription in newSubscriptions { + let symKey = try SymmetricKey(hex: subscription.symKey) + try groupKeychainStorage.add(symKey, forKey: subscription.topic) + try kms.setSymmetricKey(symKey, for: subscription.topic) + } - for subscription in newSubscriptions { - let symKey = try SymmetricKey(hex: subscription.symKey) - try groupKeychainStorage.add(symKey, forKey: subscription.topic) - try kms.setSymmetricKey(symKey, for: subscription.topic) - } + let topics = newSubscriptions.map { $0.topic } - let topics = newSubscriptions.map { $0.topic } + try await networkingInteractor.batchSubscribe(topics: topics) - try await networkingInteractor.batchSubscribe(topics: topics) + try Task.checkCancellation() - try Task.checkCancellation() + var logProperties = ["rpcId": payload.id.string] + for (index, subscription) in newSubscriptions.enumerated() { + let key = "subscription_\(index + 1)" + logProperties[key] = subscription.topic + } - var logProperties = ["rpcId": payload.id.string] - for (index, subscription) in newSubscriptions.enumerated() { - let key = "subscription_\(index + 1)" - logProperties[key] = subscription.topic + logger.debug("Updated Subscriptions by Subscriptions Changed Request", properties: logProperties) } - logger.debug("Updated Subscriptions by Subscriptions Changed Request", properties: logProperties) - try await respond(topic: payload.topic, account: jwtPayload.account, rpcId: payload.id, notifyServerAuthenticationKey: jwtPayload.notifyServerAuthenticationKey) } } diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift index 7ddb67379..104c5d31a 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift @@ -10,7 +10,6 @@ class NotifyUpdateRequester: NotifyUpdateRequesting { } private let keyserverURL: URL - private let webDidResolver: NotifyWebDidResolver private let identityClient: IdentityClient private let networkingInteractor: NetworkInteracting private let notifyConfigProvider: NotifyConfigProvider @@ -19,7 +18,6 @@ class NotifyUpdateRequester: NotifyUpdateRequesting { init( keyserverURL: URL, - webDidResolver: NotifyWebDidResolver, identityClient: IdentityClient, networkingInteractor: NetworkInteracting, notifyConfigProvider: NotifyConfigProvider, @@ -27,7 +25,6 @@ class NotifyUpdateRequester: NotifyUpdateRequesting { notifyStorage: NotifyStorage ) { self.keyserverURL = keyserverURL - self.webDidResolver = webDidResolver self.identityClient = identityClient self.networkingInteractor = networkingInteractor self.notifyConfigProvider = notifyConfigProvider @@ -40,10 +37,10 @@ class NotifyUpdateRequester: NotifyUpdateRequesting { guard let subscription = notifyStorage.getSubscription(topic: topic) else { throw Errors.noSubscriptionForGivenTopic } - let dappAuthenticationKey = try await webDidResolver.resolveAuthenticationKey(domain: subscription.metadata.url) + let dappAuthenticationKey = try DIDKey(did: subscription.appAuthenticationKey) let request = try createJWTRequest( - dappPubKey: DIDKey(rawData: dappAuthenticationKey), + dappPubKey: dappAuthenticationKey, subscriptionAccount: subscription.account, appDomain: subscription.metadata.url, scope: scope ) diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyWatchSubscriptions/NotifyWatchSubscriptionsRequester.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyWatchSubscriptions/NotifyWatchSubscriptionsRequester.swift index 72702c588..3b38ad939 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyWatchSubscriptions/NotifyWatchSubscriptionsRequester.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyWatchSubscriptions/NotifyWatchSubscriptionsRequester.swift @@ -41,8 +41,10 @@ class NotifyWatchSubscriptionsRequester: NotifyWatchSubscriptionsRequesting { logger.debug("Watching subscriptions") - let notifyServerPublicKey = try await webDidResolver.resolveAgreementKey(domain: notifyHost) - let notifyServerAuthenticationKey = try await webDidResolver.resolveAuthenticationKey(domain: notifyHost) + let didDoc = try await webDidResolver.resolveDidDoc(appDomain: notifyHost) + let notifyServerPublicKey = try webDidResolver.resolveAgreementKey(didDoc: didDoc) + let notifyServerAuthenticationKey = try webDidResolver.resolveAuthenticationKey(didDoc: didDoc) + let notifyServerAuthenticationDidKey = DIDKey(rawData: notifyServerAuthenticationKey) let watchSubscriptionsTopic = notifyServerPublicKey.rawRepresentation.sha256().toHexString() diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyWatchSubscriptions/NotifyWatchSubscriptionsResponseSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyWatchSubscriptions/NotifyWatchSubscriptionsResponseSubscriber.swift index 298932dec..55323843e 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyWatchSubscriptions/NotifyWatchSubscriptionsResponseSubscriber.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyWatchSubscriptions/NotifyWatchSubscriptionsResponseSubscriber.swift @@ -49,27 +49,28 @@ class NotifyWatchSubscriptionsResponseSubscriber { logger.debug("Received: \(newSubscriptions.count), changed: \(subscriptions.count)") - guard subscriptions.count > 0 else { return } - // TODO: unsubscribe for oldSubscriptions topics that are not included in new subscriptions - notifyStorage.replaceAllSubscriptions(newSubscriptions, account: account) - - for subscription in newSubscriptions { - let symKey = try SymmetricKey(hex: subscription.symKey) - try groupKeychainStorage.add(symKey, forKey: subscription.topic) - try kms.setSymmetricKey(symKey, for: subscription.topic) - } + if subscriptions.count > 0 { + // TODO: unsubscribe for oldSubscriptions topics that are not included in new subscriptions + notifyStorage.replaceAllSubscriptions(newSubscriptions, account: account) - try await networkingInteractor.batchSubscribe(topics: newSubscriptions.map { $0.topic }) + for subscription in newSubscriptions { + let symKey = try SymmetricKey(hex: subscription.symKey) + try groupKeychainStorage.add(symKey, forKey: subscription.topic) + try kms.setSymmetricKey(symKey, for: subscription.topic) + } - try Task.checkCancellation() + try await networkingInteractor.batchSubscribe(topics: newSubscriptions.map { $0.topic }) - var logProperties = [String: String]() - for (index, subscription) in newSubscriptions.enumerated() { - let key = "subscription_\(index + 1)" - logProperties[key] = subscription.topic - } + try Task.checkCancellation() - logger.debug("Updated Subscriptions with Watch Subscriptions Update, number of subscriptions: \(newSubscriptions.count)", properties: logProperties) + var logProperties = [String: String]() + for (index, subscription) in newSubscriptions.enumerated() { + let key = "subscription_\(index + 1)" + logProperties[key] = subscription.topic + } + + logger.debug("Updated Subscriptions with Watch Subscriptions Update, number of subscriptions: \(newSubscriptions.count)", properties: logProperties) + } } } diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeRequester.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeRequester.swift index ad5c0cee0..32a875b43 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeRequester.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeRequester.swift @@ -36,9 +36,10 @@ class NotifySubscribeRequester { logger.debug("Subscribing for Notify, dappUrl: \(appDomain)") - let config = try await notifyConfigProvider.resolveNotifyConfig(appDomain: appDomain) + let config = await notifyConfigProvider.resolveNotifyConfig(appDomain: appDomain) - let peerPublicKey = try await webDidResolver.resolveAgreementKey(domain: appDomain) + let didDoc = try await webDidResolver.resolveDidDoc(appDomain: appDomain) + let peerPublicKey = try webDidResolver.resolveAgreementKey(didDoc: didDoc) let subscribeTopic = peerPublicKey.rawRepresentation.sha256().toHexString() let keysY = try generateAgreementKeys(peerPublicKey: peerPublicKey) diff --git a/Sources/WalletConnectNotify/Client/Wallet/SubscriptionWatcher.swift b/Sources/WalletConnectNotify/Client/Wallet/SubscriptionWatcher.swift index 00ae7bebc..a08c95d6b 100644 --- a/Sources/WalletConnectNotify/Client/Wallet/SubscriptionWatcher.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/SubscriptionWatcher.swift @@ -6,16 +6,13 @@ import UIKit class SubscriptionWatcher { - private var timerCancellable: AnyCancellable? + private var timer: Timer? private var appLifecycleCancellable: AnyCancellable? private var notifyWatchSubscriptionsRequester: NotifyWatchSubscriptionsRequesting private let logger: ConsoleLogging - private let backgroundQueue = DispatchQueue(label: "com.walletconnect.subscriptionWatcher", qos: .background) private let notificationCenter: NotificationPublishing - private var watchSubscriptionsWorkItem: DispatchWorkItem? var timerInterval: TimeInterval = 5 * 60 - var debounceInterval: TimeInterval = 0.5 var onSetupTimer: (() -> Void)? init(notifyWatchSubscriptionsRequester: NotifyWatchSubscriptionsRequesting, @@ -28,57 +25,46 @@ class SubscriptionWatcher { deinit { stop() } - func start() { + func start() async throws { + setupAppLifecyclePublisher() setupTimer() - watchAppLifecycle() - watchSubscriptions() + + try await notifyWatchSubscriptionsRequester.watchSubscriptions() } func stop() { - timerCancellable?.cancel() + timer?.invalidate() appLifecycleCancellable?.cancel() - watchSubscriptionsWorkItem?.cancel() } } -internal extension SubscriptionWatcher { - - func watchSubscriptions() { - watchSubscriptionsWorkItem?.cancel() - - let workItem = DispatchWorkItem { [weak self] in - self?.logger.debug("Will watch subscriptions") - Task(priority: .background) { [weak self] in try await self?.notifyWatchSubscriptionsRequester.watchSubscriptions() } - } - - watchSubscriptionsWorkItem = workItem - DispatchQueue.main.asyncAfter(deadline: .now() + debounceInterval, execute: workItem) - } +private extension SubscriptionWatcher { - func watchAppLifecycle() { + func setupAppLifecyclePublisher() { #if os(iOS) appLifecycleCancellable = notificationCenter.publisher(for: UIApplication.willEnterForegroundNotification) .receive(on: RunLoop.main) .sink { [weak self] _ in - self?.logger.debug("Will setup Subscription Watcher after app entered foreground") - self?.setupTimer() - self?.backgroundQueue.async { - self?.watchSubscriptions() - } + guard let self else { return } + self.logger.debug("SubscriptionWatcher entered foreground event") + self.watchSubscriptions() } #endif } func setupTimer() { - onSetupTimer?() - logger.debug("Setting up Subscription Watcher timer") - timerCancellable?.cancel() - timerCancellable = Timer.publish(every: timerInterval, on: .main, in: .common) - .autoconnect() - .sink { [weak self] _ in - self?.backgroundQueue.async { - self?.watchSubscriptions() - } - } + timer?.invalidate() + timer = Timer.scheduledTimer(withTimeInterval: timerInterval, repeats: true) { [weak self] _ in + guard let self else { return } + self.logger.debug("SubscriptionWatcher scheduled event") + self.watchSubscriptions() + } + RunLoop.main.add(timer!, forMode: .common) + } + + func watchSubscriptions() { + Task(priority: .high) { + try await self.notifyWatchSubscriptionsRequester.watchSubscriptions() + } } } diff --git a/Sources/WalletConnectNotify/Types/DataStructures/NotifyServerSubscription.swift b/Sources/WalletConnectNotify/Types/DataStructures/NotifyServerSubscription.swift index 56ebbd352..bdce74bac 100644 --- a/Sources/WalletConnectNotify/Types/DataStructures/NotifyServerSubscription.swift +++ b/Sources/WalletConnectNotify/Types/DataStructures/NotifyServerSubscription.swift @@ -6,4 +6,5 @@ struct NotifyServerSubscription: Codable, Equatable { let scope: [String] let symKey: String let expiry: Date + let appAuthenticationKey: String } diff --git a/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift b/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift index cc47fce34..cb4982555 100644 --- a/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift +++ b/Sources/WalletConnectNotify/Types/DataStructures/NotifySubscription.swift @@ -8,6 +8,7 @@ public struct NotifySubscription: DatabaseObject { public let scope: [String: ScopeValue] public let expiry: Date public let symKey: String + public let appAuthenticationKey: String public var databaseId: String { return topic diff --git a/Tests/NotifyTests/Stubs/NotifySubscription.swift b/Tests/NotifyTests/Stubs/NotifySubscription.swift index 3e9a1892e..7fd10597e 100644 --- a/Tests/NotifyTests/Stubs/NotifySubscription.swift +++ b/Tests/NotifyTests/Stubs/NotifySubscription.swift @@ -15,7 +15,8 @@ extension NotifySubscription { metadata: metadata, scope: ["test": ScopeValue(id: "id", name: "name", description: "desc", enabled: true)], expiry: expiry, - symKey: symKey + symKey: symKey, + appAuthenticationKey: "did:key:z6MkpTEGT75mnz8TiguXYYVnS1GbsNCdLo72R7kUCLShTuFV" ) } } diff --git a/Tests/NotifyTests/SubscriptionWatcherTests.swift b/Tests/NotifyTests/SubscriptionWatcherTests.swift index e2d389759..a00bd7362 100644 --- a/Tests/NotifyTests/SubscriptionWatcherTests.swift +++ b/Tests/NotifyTests/SubscriptionWatcherTests.swift @@ -16,8 +16,6 @@ class SubscriptionWatcherTests: XCTestCase { mockLogger = ConsoleLoggerMock() mockNotificationCenter = MockNotificationCenter() sut = SubscriptionWatcher(notifyWatchSubscriptionsRequester: mockRequester, logger: mockLogger, notificationCenter: mockNotificationCenter) - sut.debounceInterval = 0.0001 - sut.start() } override func tearDown() { @@ -28,39 +26,36 @@ class SubscriptionWatcherTests: XCTestCase { super.tearDown() } - func testWatchSubscriptions() { + func testWatchSubscriptions() async throws { let expectation = XCTestExpectation(description: "Expect watchSubscriptions to be called") mockRequester.onWatchSubscriptions = { expectation.fulfill() } - sut.watchSubscriptions() + try await sut.start() wait(for: [expectation], timeout: 0.5) } - func testWatchAppLifecycleReactsToEnterForegroundNotification() { - let setupExpectation = XCTestExpectation(description: "Expect setupTimer to be called on app enter foreground") + func testWatchAppLifecycleReactsToEnterForegroundNotification() async throws { let watchSubscriptionsExpectation = XCTestExpectation(description: "Expect watchSubscriptions to be called on app enter foreground") - - sut.onSetupTimer = { - setupExpectation.fulfill() - } + watchSubscriptionsExpectation.expectedFulfillmentCount = 2 mockRequester.onWatchSubscriptions = { watchSubscriptionsExpectation.fulfill() } - mockNotificationCenter.post(name: UIApplication.willEnterForegroundNotification) + try await sut.start() - wait(for: [setupExpectation, watchSubscriptionsExpectation], timeout: 0.5) + await mockNotificationCenter.post(name: UIApplication.willEnterForegroundNotification) + + wait(for: [watchSubscriptionsExpectation], timeout: 0.5) } - func testTimerTriggeringWatchSubscriptionsMultipleTimes() { + func testTimerTriggeringWatchSubscriptionsMultipleTimes() async throws { sut.timerInterval = 0.0001 - sut.setupTimer() let expectation = XCTestExpectation(description: "Expect watchSubscriptions to be called multiple times") expectation.expectedFulfillmentCount = 3 @@ -69,6 +64,8 @@ class SubscriptionWatcherTests: XCTestCase { expectation.fulfill() } + try await sut.start() + wait(for: [expectation], timeout: 0.5) } }