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/PNDecryptionService/Info.plist b/Example/PNDecryptionService/Info.plist
index 57421ebf9..cb9e830fd 100644
--- a/Example/PNDecryptionService/Info.plist
+++ b/Example/PNDecryptionService/Info.plist
@@ -1,13 +1,20 @@
-
- NSExtension
-
- NSExtensionPointIdentifier
- com.apple.usernotifications.service
- NSExtensionPrincipalClass
- $(PRODUCT_MODULE_NAME).NotificationService
-
-
+
+ NSExtension
+
+ NSExtensionAttributes
+
+ IntentsSupported
+
+ INSendMessageIntent
+
+
+ NSExtensionPointIdentifier
+ com.apple.usernotifications.service
+ NSExtensionPrincipalClass
+ $(PRODUCT_MODULE_NAME).NotificationService
+
+
diff --git a/Example/PNDecryptionService/NotificationService.swift b/Example/PNDecryptionService/NotificationService.swift
index 50023dd13..70232ec62 100644
--- a/Example/PNDecryptionService/NotificationService.swift
+++ b/Example/PNDecryptionService/NotificationService.swift
@@ -1,42 +1,115 @@
import UserNotifications
import WalletConnectNotify
-import os
+import Intents
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
- var bestAttemptContent: UNMutableNotificationContent?
+ var bestAttemptContent: UNNotificationContent?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
- bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
- if let bestAttemptContent = bestAttemptContent {
- let topic = bestAttemptContent.userInfo["topic"] as! String
- let ciphertext = bestAttemptContent.userInfo["blob"] as! String
- NSLog("Push decryption, topic=%@", topic)
+ self.bestAttemptContent = request.content
+
+ if let content = bestAttemptContent,
+ let topic = content.userInfo["topic"] as? String,
+ let ciphertext = content.userInfo["blob"] as? String {
+
do {
let service = NotifyDecryptionService(groupIdentifier: "group.com.walletconnect.sdk")
let pushMessage = try service.decryptMessage(topic: topic, ciphertext: ciphertext)
- bestAttemptContent.title = pushMessage.title
- bestAttemptContent.body = pushMessage.body
- contentHandler(bestAttemptContent)
- return
+ let updatedContent = try handle(content: content, pushMessage: pushMessage, topic: topic)
+
+ let mutableContent = updatedContent.mutableCopy() as! UNMutableNotificationContent
+ mutableContent.title = pushMessage.title
+ mutableContent.body = pushMessage.body
+
+ contentHandler(mutableContent)
}
catch {
- NSLog("Push decryption, error=%@", error.localizedDescription)
- bestAttemptContent.title = ""
- bestAttemptContent.body = error.localizedDescription
+ let mutableContent = content.mutableCopy() as! UNMutableNotificationContent
+ mutableContent.title = "Error"
+ mutableContent.body = error.localizedDescription
+
+ contentHandler(mutableContent)
}
- contentHandler(bestAttemptContent)
}
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
- if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
+ if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
+}
+
+private extension NotificationService {
+
+ func handle(content: UNNotificationContent, pushMessage: NotifyMessage, topic: String) throws -> UNNotificationContent {
+ let iconUrl = try pushMessage.icon.asURL()
+
+ let senderThumbnailImageData = try Data(contentsOf: iconUrl)
+ let senderThumbnailImageFileUrl = try downloadAttachment(data: senderThumbnailImageData, fileName: iconUrl.lastPathComponent)
+ let senderThumbnailImageFileData = try Data(contentsOf: senderThumbnailImageFileUrl)
+ let senderAvatar = INImage(imageData: senderThumbnailImageFileData)
+
+ var personNameComponents = PersonNameComponents()
+ personNameComponents.nickname = pushMessage.title
+
+ let senderPerson = INPerson(
+ personHandle: INPersonHandle(value: topic, type: .unknown),
+ nameComponents: personNameComponents,
+ displayName: pushMessage.title,
+ image: senderAvatar,
+ contactIdentifier: nil,
+ customIdentifier: topic,
+ isMe: false,
+ suggestionType: .none
+ )
+
+ let selfPerson = INPerson(
+ personHandle: INPersonHandle(value: "0", type: .unknown),
+ nameComponents: nil,
+ displayName: nil,
+ image: nil,
+ contactIdentifier: nil,
+ customIdentifier: nil,
+ isMe: true,
+ suggestionType: .none
+ )
+
+ let incomingMessagingIntent = INSendMessageIntent(
+ recipients: [selfPerson],
+ outgoingMessageType: .outgoingMessageText,
+ content: pushMessage.body,
+ speakableGroupName: nil,
+ conversationIdentifier: pushMessage.type,
+ serviceName: nil,
+ sender: senderPerson,
+ attachments: []
+ )
+
+ incomingMessagingIntent.setImage(senderAvatar, forParameterNamed: \.sender)
+
+ let interaction = INInteraction(intent: incomingMessagingIntent, response: nil)
+ interaction.direction = .incoming
+ interaction.donate(completion: nil)
+
+ return try content.updating(from: incomingMessagingIntent)
+ }
+
+ func downloadAttachment(data: Data, fileName: String) throws -> URL {
+ let fileManager = FileManager.default
+ let tmpSubFolderName = ProcessInfo.processInfo.globallyUniqueString
+ let tmpSubFolderURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(tmpSubFolderName, isDirectory: true)
+
+ try fileManager.createDirectory(at: tmpSubFolderURL, withIntermediateDirectories: true, attributes: nil)
+ let fileURL = tmpSubFolderURL.appendingPathComponent(fileName)
+ try data.write(to: fileURL)
+
+ return fileURL
+ }
}
diff --git a/Example/WalletApp/Other/Info.plist b/Example/WalletApp/Other/Info.plist
index 2f0f15d26..25606209f 100644
--- a/Example/WalletApp/Other/Info.plist
+++ b/Example/WalletApp/Other/Info.plist
@@ -2,6 +2,10 @@
+ NSUserActivityTypes
+
+ INSendMessageIntent
+
CFBundleIconName
AppIcon
CFBundleURLTypes
diff --git a/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsView.swift b/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsView.swift
index 65a838b11..5174bc80e 100644
--- a/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsView.swift
+++ b/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsView.swift
@@ -2,7 +2,13 @@ import SwiftUI
struct ConnectionDetailsView: View {
@EnvironmentObject var presenter: ConnectionDetailsPresenter
-
+
+ private var dateFormatter: DateFormatter {
+ let formatter = DateFormatter()
+ formatter.dateFormat = "HH:mm:ss E, d MMM y"
+ return formatter
+ }
+
var body: some View {
ZStack {
Color.grey100
@@ -145,6 +151,39 @@ struct ConnectionDetailsView: View {
.padding(.top, 30)
}
+ VStack(alignment: .leading) {
+ Text("Expiry")
+ .font(.system(size: 15, weight: .semibold, design: .rounded))
+ .foregroundColor(.whiteBackground)
+ .padding(.horizontal, 8)
+ .padding(.vertical, 5)
+ .background(Color.grey70)
+ .cornerRadius(28, corners: .allCorners)
+ .padding(.leading, 15)
+ .padding(.top, 9)
+
+ VStack(spacing: 0) {
+ TagsView(items: [dateFormatter.string(from: presenter.session.expiryDate)]) {
+ Text($0)
+ .foregroundColor(.cyanBackround)
+ .font(.system(size: 13, weight: .semibold, design: .rounded))
+ .padding(.horizontal, 8)
+ .padding(.vertical, 3)
+ .background(Color.cyanBackround.opacity(0.2))
+ .cornerRadius(10, corners: .allCorners)
+ }
+ .padding(10)
+ }
+ .background(Color.whiteBackground)
+ .cornerRadius(20, corners: .allCorners)
+ .padding(.horizontal, 5)
+ .padding(.bottom, 5)
+ }
+ .background(Color("grey95"))
+ .cornerRadius(25, corners: .allCorners)
+ .padding(.horizontal, 20)
+ .padding(.top, 30)
+
Button {
presenter.onDelete()
} label: {
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/Example/WalletApp/WalletApp.entitlements b/Example/WalletApp/WalletApp.entitlements
index 2dccdcca9..46de0afbc 100644
--- a/Example/WalletApp/WalletApp.entitlements
+++ b/Example/WalletApp/WalletApp.entitlements
@@ -4,6 +4,8 @@
aps-environment
development
+ com.apple.developer.usernotifications.communication
+
com.apple.security.application-groups
group.com.walletconnect.sdk
diff --git a/Example/WalletApp/WalletAppRelease.entitlements b/Example/WalletApp/WalletAppRelease.entitlements
index 315a4dbfc..7028e40d9 100644
--- a/Example/WalletApp/WalletAppRelease.entitlements
+++ b/Example/WalletApp/WalletAppRelease.entitlements
@@ -4,6 +4,8 @@
aps-environment
production
+ com.apple.developer.usernotifications.communication
+
com.apple.security.application-groups
group.com.walletconnect.sdk
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/Sources/WalletConnectRelay/PackageConfig.json b/Sources/WalletConnectRelay/PackageConfig.json
index 7db8bf6cc..78e802ff1 100644
--- a/Sources/WalletConnectRelay/PackageConfig.json
+++ b/Sources/WalletConnectRelay/PackageConfig.json
@@ -1 +1 @@
-{"version": "1.9.1"}
+{"version": "1.9.2"}
diff --git a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift
index 3e6cec6bf..185d0d444 100644
--- a/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift
+++ b/Sources/WalletConnectSign/Engine/Common/SessionEngine.swift
@@ -50,9 +50,9 @@ final class SessionEngine {
func hasSession(for topic: String) -> Bool {
return sessionStore.hasSession(forTopic: topic)
}
-
+
func getSessions() -> [Session] {
- sessionStore.getAll().map {$0.publicRepresentation()}
+ sessionStore.getAll().map { $0.publicRepresentation() }
}
func request(_ request: Request) async throws {
diff --git a/Sources/WalletConnectSign/Engine/Common/SessionExtendRequestSubscriber.swift b/Sources/WalletConnectSign/Engine/Common/SessionExtendRequestSubscriber.swift
new file mode 100644
index 000000000..af291b26e
--- /dev/null
+++ b/Sources/WalletConnectSign/Engine/Common/SessionExtendRequestSubscriber.swift
@@ -0,0 +1,66 @@
+import Foundation
+import Combine
+
+final class SessionExtendRequestSubscriber {
+ var onExtend: ((String, Date) -> Void)?
+
+ private let sessionStore: WCSessionStorage
+ private let networkingInteractor: NetworkInteracting
+ private var publishers = [AnyCancellable]()
+ private let logger: ConsoleLogging
+
+ init(
+ networkingInteractor: NetworkInteracting,
+ sessionStore: WCSessionStorage,
+ logger: ConsoleLogging
+ ) {
+ self.networkingInteractor = networkingInteractor
+ self.sessionStore = sessionStore
+ self.logger = logger
+
+ setupSubscriptions()
+ }
+}
+
+// MARK: - Private functions
+extension SessionExtendRequestSubscriber {
+ private func setupSubscriptions() {
+ networkingInteractor.requestSubscription(on: SessionExtendProtocolMethod())
+ .sink { [unowned self] (payload: RequestSubscriptionPayload) in
+ onSessionUpdateExpiry(payload: payload, updateExpiryParams: payload.request)
+ }.store(in: &publishers)
+ }
+
+ private func onSessionUpdateExpiry(payload: SubscriptionPayload, updateExpiryParams: SessionType.UpdateExpiryParams) {
+ let protocolMethod = SessionExtendProtocolMethod()
+ let topic = payload.topic
+ guard var session = sessionStore.getSession(forTopic: topic) else {
+ return respondError(payload: payload, reason: .noSessionForTopic, protocolMethod: protocolMethod)
+ }
+ guard session.peerIsController else {
+ return respondError(payload: payload, reason: .unauthorizedExtendRequest, protocolMethod: protocolMethod)
+ }
+ do {
+ try session.updateExpiry(to: updateExpiryParams.expiry)
+ } catch {
+ return respondError(payload: payload, reason: .invalidExtendRequest, protocolMethod: protocolMethod)
+ }
+ sessionStore.setSession(session)
+
+ Task(priority: .high) {
+ try await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, protocolMethod: protocolMethod)
+ }
+
+ onExtend?(session.topic, session.expiryDate)
+ }
+
+ private func respondError(payload: SubscriptionPayload, reason: SignReasonCode, protocolMethod: ProtocolMethod) {
+ Task(priority: .high) {
+ do {
+ try await networkingInteractor.respondError(topic: payload.topic, requestId: payload.id, protocolMethod: protocolMethod, reason: reason)
+ } catch {
+ logger.error("Respond Error failed with: \(error.localizedDescription)")
+ }
+ }
+ }
+}
diff --git a/Sources/WalletConnectSign/Engine/Common/SessionExtendRequester.swift b/Sources/WalletConnectSign/Engine/Common/SessionExtendRequester.swift
new file mode 100644
index 000000000..5b815cb14
--- /dev/null
+++ b/Sources/WalletConnectSign/Engine/Common/SessionExtendRequester.swift
@@ -0,0 +1,27 @@
+import Foundation
+
+final class SessionExtendRequester {
+ private let sessionStore: WCSessionStorage
+ private let networkingInteractor: NetworkInteracting
+
+ init(
+ sessionStore: WCSessionStorage,
+ networkingInteractor: NetworkInteracting
+ ) {
+ self.sessionStore = sessionStore
+ self.networkingInteractor = networkingInteractor
+ }
+
+ func extend(topic: String, by ttl: Int64) async throws {
+ guard var session = sessionStore.getSession(forTopic: topic) else {
+ throw WalletConnectError.noSessionMatchingTopic(topic)
+ }
+
+ let protocolMethod = SessionExtendProtocolMethod()
+ try session.updateExpiry(by: ttl)
+ let newExpiry = Int64(session.expiryDate.timeIntervalSince1970)
+ sessionStore.setSession(session)
+ let request = RPCRequest(method: protocolMethod.method, params: SessionType.UpdateExpiryParams(expiry: newExpiry))
+ try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod)
+ }
+}
diff --git a/Sources/WalletConnectSign/Engine/Common/SessionExtendResponseSubscriber.swift b/Sources/WalletConnectSign/Engine/Common/SessionExtendResponseSubscriber.swift
new file mode 100644
index 000000000..a802da680
--- /dev/null
+++ b/Sources/WalletConnectSign/Engine/Common/SessionExtendResponseSubscriber.swift
@@ -0,0 +1,48 @@
+import Foundation
+import Combine
+
+final class SessionExtendResponseSubscriber {
+ var onExtend: ((String, Date) -> Void)?
+
+ private let sessionStore: WCSessionStorage
+ private let networkingInteractor: NetworkInteracting
+ private var publishers = [AnyCancellable]()
+ private let logger: ConsoleLogging
+
+ init(
+ networkingInteractor: NetworkInteracting,
+ sessionStore: WCSessionStorage,
+ logger: ConsoleLogging
+ ) {
+ self.networkingInteractor = networkingInteractor
+ self.sessionStore = sessionStore
+ self.logger = logger
+
+ setupSubscriptions()
+ }
+
+ // MARK: - Handle Response
+ private func setupSubscriptions() {
+ networkingInteractor.responseSubscription(on: SessionExtendProtocolMethod())
+ .sink { [unowned self] (payload: ResponseSubscriptionPayload) in
+ handleUpdateExpiryResponse(payload: payload)
+ }
+ .store(in: &publishers)
+ }
+
+ private func handleUpdateExpiryResponse(payload: ResponseSubscriptionPayload) {
+ guard var session = sessionStore.getSession(forTopic: payload.topic) else { return }
+ switch payload.response {
+ case .response:
+ do {
+ try session.updateExpiry(to: payload.request.expiry)
+ sessionStore.setSession(session)
+ onExtend?(session.topic, session.expiryDate)
+ } catch {
+ logger.error("Update expiry error: \(error.localizedDescription)")
+ }
+ case .error:
+ logger.error("Peer failed to extend session")
+ }
+ }
+}
diff --git a/Sources/WalletConnectSign/Engine/Controller/ControllerSessionStateMachine.swift b/Sources/WalletConnectSign/Engine/Controller/ControllerSessionStateMachine.swift
index eb6becf93..d95a2b8b1 100644
--- a/Sources/WalletConnectSign/Engine/Controller/ControllerSessionStateMachine.swift
+++ b/Sources/WalletConnectSign/Engine/Controller/ControllerSessionStateMachine.swift
@@ -2,9 +2,7 @@ import Foundation
import Combine
final class ControllerSessionStateMachine {
-
var onNamespacesUpdate: ((String, [String: SessionNamespace]) -> Void)?
- var onExtend: ((String, Date) -> Void)?
private let sessionStore: WCSessionStorage
private let networkingInteractor: NetworkInteracting
@@ -35,31 +33,13 @@ final class ControllerSessionStateMachine {
try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod)
}
- func extend(topic: String, by ttl: Int64) async throws {
- var session = try getSession(for: topic)
- let protocolMethod = SessionExtendProtocolMethod()
- try validateController(session)
- try session.updateExpiry(by: ttl)
- let newExpiry = Int64(session.expiryDate.timeIntervalSince1970 )
- sessionStore.setSession(session)
- let request = RPCRequest(method: protocolMethod.method, params: SessionType.UpdateExpiryParams(expiry: newExpiry))
- try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod)
- }
-
// MARK: - Handle Response
-
private func setupSubscriptions() {
networkingInteractor.responseSubscription(on: SessionUpdateProtocolMethod())
.sink { [unowned self] (payload: ResponseSubscriptionPayload) in
handleUpdateResponse(payload: payload)
}
.store(in: &publishers)
-
- networkingInteractor.responseSubscription(on: SessionExtendProtocolMethod())
- .sink { [unowned self] (payload: ResponseSubscriptionPayload) in
- handleUpdateExpiryResponse(payload: payload)
- }
- .store(in: &publishers)
}
private func handleUpdateResponse(payload: ResponseSubscriptionPayload) {
@@ -80,22 +60,6 @@ final class ControllerSessionStateMachine {
}
}
- private func handleUpdateExpiryResponse(payload: ResponseSubscriptionPayload) {
- guard var session = sessionStore.getSession(forTopic: payload.topic) else { return }
- switch payload.response {
- case .response:
- do {
- try session.updateExpiry(to: payload.request.expiry)
- sessionStore.setSession(session)
- onExtend?(session.topic, session.expiryDate)
- } catch {
- logger.error("Update expiry error: \(error.localizedDescription)")
- }
- case .error:
- logger.error("Peer failed to extend session")
- }
- }
-
// MARK: - Private
private func getSession(for topic: String) throws -> WCSession {
if let session = sessionStore.getSession(forTopic: topic) {
diff --git a/Sources/WalletConnectSign/Engine/NonController/NonControllerSessionStateMachine.swift b/Sources/WalletConnectSign/Engine/NonController/NonControllerSessionStateMachine.swift
index c7dba0e07..b371a579b 100644
--- a/Sources/WalletConnectSign/Engine/NonController/NonControllerSessionStateMachine.swift
+++ b/Sources/WalletConnectSign/Engine/NonController/NonControllerSessionStateMachine.swift
@@ -2,9 +2,7 @@ import Foundation
import Combine
final class NonControllerSessionStateMachine {
-
var onNamespacesUpdate: ((String, [String: SessionNamespace]) -> Void)?
- var onExtend: ((String, Date) -> Void)?
private let sessionStore: WCSessionStorage
private let networkingInteractor: NetworkInteracting
@@ -12,10 +10,12 @@ final class NonControllerSessionStateMachine {
private var publishers = [AnyCancellable]()
private let logger: ConsoleLogging
- init(networkingInteractor: NetworkInteracting,
- kms: KeyManagementServiceProtocol,
- sessionStore: WCSessionStorage,
- logger: ConsoleLogging) {
+ init(
+ networkingInteractor: NetworkInteracting,
+ kms: KeyManagementServiceProtocol,
+ sessionStore: WCSessionStorage,
+ logger: ConsoleLogging
+ ) {
self.networkingInteractor = networkingInteractor
self.kms = kms
self.sessionStore = sessionStore
@@ -28,11 +28,6 @@ final class NonControllerSessionStateMachine {
.sink { [unowned self] (payload: RequestSubscriptionPayload) in
onSessionUpdateNamespacesRequest(payload: payload, updateParams: payload.request)
}.store(in: &publishers)
-
- networkingInteractor.requestSubscription(on: SessionExtendProtocolMethod())
- .sink { [unowned self] (payload: RequestSubscriptionPayload) in
- onSessionUpdateExpiry(payload: payload, updateExpiryParams: payload.request)
- }.store(in: &publishers)
}
private func respondError(payload: SubscriptionPayload, reason: SignReasonCode, protocolMethod: ProtocolMethod) {
@@ -72,27 +67,4 @@ final class NonControllerSessionStateMachine {
onNamespacesUpdate?(session.topic, updateParams.namespaces)
}
-
- private func onSessionUpdateExpiry(payload: SubscriptionPayload, updateExpiryParams: SessionType.UpdateExpiryParams) {
- let protocolMethod = SessionExtendProtocolMethod()
- let topic = payload.topic
- guard var session = sessionStore.getSession(forTopic: topic) else {
- return respondError(payload: payload, reason: .noSessionForTopic, protocolMethod: protocolMethod)
- }
- guard session.peerIsController else {
- return respondError(payload: payload, reason: .unauthorizedExtendRequest, protocolMethod: protocolMethod)
- }
- do {
- try session.updateExpiry(to: updateExpiryParams.expiry)
- } catch {
- return respondError(payload: payload, reason: .invalidExtendRequest, protocolMethod: protocolMethod)
- }
- sessionStore.setSession(session)
-
- Task(priority: .high) {
- try await networkingInteractor.respondSuccess(topic: payload.topic, requestId: payload.id, protocolMethod: protocolMethod)
- }
-
- onExtend?(session.topic, session.expiryDate)
- }
}
diff --git a/Sources/WalletConnectSign/Sign/SignClient.swift b/Sources/WalletConnectSign/Sign/SignClient.swift
index 57dd897a2..1ea172041 100644
--- a/Sources/WalletConnectSign/Sign/SignClient.swift
+++ b/Sources/WalletConnectSign/Sign/SignClient.swift
@@ -109,6 +109,9 @@ public final class SignClient: SignClientProtocol {
private let sessionPingService: SessionPingService
private let nonControllerSessionStateMachine: NonControllerSessionStateMachine
private let controllerSessionStateMachine: ControllerSessionStateMachine
+ private let sessionExtendRequester: SessionExtendRequester
+ private let sessionExtendRequestSubscriber: SessionExtendRequestSubscriber
+ private let sessionExtendResponseSubscriber: SessionExtendResponseSubscriber
private let appProposeService: AppProposeService
private let historyService: HistoryService
private let cleanupService: SignCleanupService
@@ -138,6 +141,9 @@ public final class SignClient: SignClientProtocol {
sessionPingService: SessionPingService,
nonControllerSessionStateMachine: NonControllerSessionStateMachine,
controllerSessionStateMachine: ControllerSessionStateMachine,
+ sessionExtendRequester: SessionExtendRequester,
+ sessionExtendRequestSubscriber: SessionExtendRequestSubscriber,
+ sessionExtendResponseSubscriber: SessionExtendResponseSubscriber,
appProposeService: AppProposeService,
disconnectService: DisconnectService,
historyService: HistoryService,
@@ -152,6 +158,9 @@ public final class SignClient: SignClientProtocol {
self.sessionPingService = sessionPingService
self.nonControllerSessionStateMachine = nonControllerSessionStateMachine
self.controllerSessionStateMachine = controllerSessionStateMachine
+ self.sessionExtendRequester = sessionExtendRequester
+ self.sessionExtendRequestSubscriber = sessionExtendRequestSubscriber
+ self.sessionExtendResponseSubscriber = sessionExtendResponseSubscriber
self.appProposeService = appProposeService
self.historyService = historyService
self.cleanupService = cleanupService
@@ -259,13 +268,13 @@ public final class SignClient: SignClientProtocol {
try await controllerSessionStateMachine.update(topic: topic, namespaces: namespaces)
}
- /// For wallet to extend a session to 7 days
+ /// For dapp and wallet to extend a session to 7 days
/// - Parameters:
/// - topic: Topic of the session that is intended to be extended.
public func extend(topic: String) async throws {
let ttl: Int64 = Session.defaultTimeToLive
if sessionEngine.hasSession(for: topic) {
- try await controllerSessionStateMachine.extend(topic: topic, by: ttl)
+ try await sessionExtendRequester.extend(topic: topic, by: ttl)
}
}
@@ -399,13 +408,13 @@ public final class SignClient: SignClientProtocol {
controllerSessionStateMachine.onNamespacesUpdate = { [unowned self] topic, namespaces in
sessionUpdatePublisherSubject.send((topic, namespaces))
}
- controllerSessionStateMachine.onExtend = { [unowned self] topic, date in
- sessionExtendPublisherSubject.send((topic, date))
- }
nonControllerSessionStateMachine.onNamespacesUpdate = { [unowned self] topic, namespaces in
sessionUpdatePublisherSubject.send((topic, namespaces))
}
- nonControllerSessionStateMachine.onExtend = { [unowned self] topic, date in
+ sessionExtendRequestSubscriber.onExtend = { [unowned self] topic, date in
+ sessionExtendPublisherSubject.send((topic, date))
+ }
+ sessionExtendResponseSubscriber.onExtend = { [unowned self] topic, date in
sessionExtendPublisherSubject.send((topic, date))
}
sessionEngine.onEventReceived = { [unowned self] topic, event, chainId in
diff --git a/Sources/WalletConnectSign/Sign/SignClientFactory.swift b/Sources/WalletConnectSign/Sign/SignClientFactory.swift
index a7903a01b..cfaaf355f 100644
--- a/Sources/WalletConnectSign/Sign/SignClientFactory.swift
+++ b/Sources/WalletConnectSign/Sign/SignClientFactory.swift
@@ -30,6 +30,9 @@ public struct SignClientFactory {
let sessionEngine = SessionEngine(networkingInteractor: networkingClient, historyService: historyService, verifyContextStore: verifyContextStore, verifyClient: verifyClient, kms: kms, sessionStore: sessionStore, logger: logger)
let nonControllerSessionStateMachine = NonControllerSessionStateMachine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger)
let controllerSessionStateMachine = ControllerSessionStateMachine(networkingInteractor: networkingClient, kms: kms, sessionStore: sessionStore, logger: logger)
+ let sessionExtendRequester = SessionExtendRequester(sessionStore: sessionStore, networkingInteractor: networkingClient)
+ let sessionExtendRequestSubscriber = SessionExtendRequestSubscriber(networkingInteractor: networkingClient, sessionStore: sessionStore, logger: logger)
+ let sessionExtendResponseSubscriber = SessionExtendResponseSubscriber(networkingInteractor: networkingClient, sessionStore: sessionStore, logger: logger)
let sessionTopicToProposal = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: SignStorageIdentifiers.sessionTopicToProposal.rawValue)
let approveEngine = ApproveEngine(
networkingInteractor: networkingClient,
@@ -60,6 +63,9 @@ public struct SignClientFactory {
sessionPingService: sessionPingService,
nonControllerSessionStateMachine: nonControllerSessionStateMachine,
controllerSessionStateMachine: controllerSessionStateMachine,
+ sessionExtendRequester: sessionExtendRequester,
+ sessionExtendRequestSubscriber: sessionExtendRequestSubscriber,
+ sessionExtendResponseSubscriber: sessionExtendResponseSubscriber,
appProposeService: appProposerService,
disconnectService: disconnectService,
historyService: historyService,
diff --git a/Sources/WalletConnectSign/Types/Session/WCSession.swift b/Sources/WalletConnectSign/Types/Session/WCSession.swift
index f9acf29e6..30d237a85 100644
--- a/Sources/WalletConnectSign/Types/Session/WCSession.swift
+++ b/Sources/WalletConnectSign/Types/Session/WCSession.swift
@@ -158,7 +158,7 @@ struct WCSession: SequenceObject, Equatable {
mutating func updateExpiry(to expiry: Int64) throws {
let newExpiryDate = Date(timeIntervalSince1970: TimeInterval(expiry))
let maxExpiryDate = Date(timeIntervalSinceNow: TimeInterval(WCSession.defaultTimeToLive))
- guard newExpiryDate >= expiryDate && newExpiryDate <= maxExpiryDate else {
+ guard newExpiryDate.millisecondsSince1970 >= (expiryDate.millisecondsSince1970 / 1000) && newExpiryDate <= maxExpiryDate else {
throw WalletConnectError.invalidUpdateExpiryValue
}
self.expiryDate = newExpiryDate
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)
}
}
diff --git a/Tests/WalletConnectSignTests/ControllerSessionStateMachineTests.swift b/Tests/WalletConnectSignTests/ControllerSessionStateMachineTests.swift
index 539b335ec..f2a2a1772 100644
--- a/Tests/WalletConnectSignTests/ControllerSessionStateMachineTests.swift
+++ b/Tests/WalletConnectSignTests/ControllerSessionStateMachineTests.swift
@@ -6,6 +6,7 @@ import WalletConnectKMS
class ControllerSessionStateMachineTests: XCTestCase {
var sut: ControllerSessionStateMachine!
+ var sessionExtendRequester: SessionExtendRequester!
var networkingInteractor: NetworkingInteractorMock!
var storageMock: WCSessionStorageMock!
var cryptoMock: KeyManagementServiceMock!
@@ -15,6 +16,7 @@ class ControllerSessionStateMachineTests: XCTestCase {
storageMock = WCSessionStorageMock()
cryptoMock = KeyManagementServiceMock()
sut = ControllerSessionStateMachine(networkingInteractor: networkingInteractor, kms: cryptoMock, sessionStore: storageMock, logger: ConsoleLoggerMock())
+ sessionExtendRequester = SessionExtendRequester(sessionStore: storageMock, networkingInteractor: networkingInteractor)
}
override func tearDown() {
@@ -66,33 +68,17 @@ class ControllerSessionStateMachineTests: XCTestCase {
let session = WCSession.stub(isSelfController: true, expiryDate: tomorrow)
storageMock.setSession(session)
let twoDays = 2*Time.day
- await XCTAssertNoThrowAsync(try await sut.extend(topic: session.topic, by: Int64(twoDays)))
+ await XCTAssertNoThrowAsync(try await sessionExtendRequester.extend(topic: session.topic, by: Int64(twoDays)))
let extendedSession = storageMock.getAll().first {$0.topic == session.topic}!
XCTAssertEqual(extendedSession.expiryDate.timeIntervalSinceReferenceDate, TimeTraveler.dateByAdding(days: 2).timeIntervalSinceReferenceDate, accuracy: 1)
}
- func testUpdateExpirySessionNotSettled() async {
- let tomorrow = TimeTraveler.dateByAdding(days: 1)
- let session = WCSession.stub(isSelfController: false, expiryDate: tomorrow, acknowledged: false)
- storageMock.setSession(session)
- let twoDays = 2*Time.day
- await XCTAssertThrowsErrorAsync(try await sut.extend(topic: session.topic, by: Int64(twoDays)))
- }
-
- func testUpdateExpiryOnNonControllerClient() async {
- let tomorrow = TimeTraveler.dateByAdding(days: 1)
- let session = WCSession.stub(isSelfController: false, expiryDate: tomorrow)
- storageMock.setSession(session)
- let twoDays = 2*Time.day
- await XCTAssertThrowsErrorAsync( try await sut.extend(topic: session.topic, by: Int64(twoDays)))
- }
-
func testUpdateExpiryTtlTooHigh() async {
let tomorrow = TimeTraveler.dateByAdding(days: 1)
let session = WCSession.stub(isSelfController: true, expiryDate: tomorrow)
storageMock.setSession(session)
let tenDays = 10*Time.day
- await XCTAssertThrowsErrorAsync( try await sut.extend(topic: session.topic, by: Int64(tenDays)))
+ await XCTAssertThrowsErrorAsync( try await sessionExtendRequester.extend(topic: session.topic, by: Int64(tenDays)))
}
func testUpdateExpiryTtlTooLow() async {
@@ -100,6 +86,6 @@ class ControllerSessionStateMachineTests: XCTestCase {
let session = WCSession.stub(isSelfController: true, expiryDate: dayAfterTommorow)
storageMock.setSession(session)
let oneDay = Int64(1*Time.day)
- await XCTAssertThrowsErrorAsync( try await sut.extend(topic: session.topic, by: oneDay))
+ await XCTAssertThrowsErrorAsync( try await sessionExtendRequester.extend(topic: session.topic, by: oneDay))
}
}
diff --git a/Tests/WalletConnectSignTests/NonControllerSessionStateMachineTests.swift b/Tests/WalletConnectSignTests/NonControllerSessionStateMachineTests.swift
index aa4c917ca..7304ea2c8 100644
--- a/Tests/WalletConnectSignTests/NonControllerSessionStateMachineTests.swift
+++ b/Tests/WalletConnectSignTests/NonControllerSessionStateMachineTests.swift
@@ -67,20 +67,6 @@ class NonControllerSessionStateMachineTests: XCTestCase {
}
// MARK: - Update Expiry
-
- func testPeerUpdateExpirySuccess() {
- let tomorrow = TimeTraveler.dateByAdding(days: 1)
- let session = WCSession.stub(isSelfController: false, expiryDate: tomorrow)
- storageMock.setSession(session)
- let twoDaysFromNowTimestamp = Int64(TimeTraveler.dateByAdding(days: 2).timeIntervalSince1970)
-
- networkingInteractor.requestPublisherSubject.send((session.topic, RPCRequest.stubUpdateExpiry(expiry: twoDaysFromNowTimestamp), Data(), Date(), nil))
- let extendedSession = storageMock.getAll().first {$0.topic == session.topic}!
- print(extendedSession.expiryDate)
-
- XCTAssertEqual(extendedSession.expiryDate.timeIntervalSince1970, TimeTraveler.dateByAdding(days: 2).timeIntervalSince1970, accuracy: 1)
- }
-
func testPeerUpdateExpiryUnauthorized() {
let tomorrow = TimeTraveler.dateByAdding(days: 1)
let session = WCSession.stub(isSelfController: true, expiryDate: tomorrow)