diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index f84ab55e2..af251ea96 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 54; + objectVersion = 52; objects = { /* Begin PBXBuildFile section */ diff --git a/Example/IntegrationTests/Auth/AuthTests.swift b/Example/IntegrationTests/Auth/AuthTests.swift index 6fda1d84a..a90de5067 100644 --- a/Example/IntegrationTests/Auth/AuthTests.swift +++ b/Example/IntegrationTests/Auth/AuthTests.swift @@ -32,9 +32,15 @@ final class AuthTests: XCTestCase { func makeClients(prefix: String, iatProvider: IATProvider) -> (PairingClient, AuthClient) { let logger = ConsoleLogger(suffix: prefix, loggingLevel: .debug) - let keychain = KeychainStorageMock() - let relayClient = RelayClient(relayHost: InputConfig.relayHost, projectId: InputConfig.projectId, keychainStorage: keychain, socketFactory: DefaultSocketFactory(), logger: logger) let keyValueStorage = RuntimeKeyValueStorage() + let keychain = KeychainStorageMock() + let relayClient = RelayClientFactory.create( + relayHost: InputConfig.relayHost, + projectId: InputConfig.projectId, + keyValueStorage: keyValueStorage, + keychainStorage: keychain, + socketFactory: DefaultSocketFactory(), + logger: logger) let networkingClient = NetworkingClientFactory.create( relayClient: relayClient, diff --git a/Example/IntegrationTests/Chat/ChatTests.swift b/Example/IntegrationTests/Chat/ChatTests.swift index c2aaab6c5..69f013444 100644 --- a/Example/IntegrationTests/Chat/ChatTests.swift +++ b/Example/IntegrationTests/Chat/ChatTests.swift @@ -48,9 +48,16 @@ final class ChatTests: XCTestCase { func makeClient(prefix: String, account: Account) -> ChatClient { let keyserverURL = URL(string: "https://keys.walletconnect.com")! let logger = ConsoleLogger(suffix: prefix, loggingLevel: .debug) - let keychain = KeychainStorageMock() - let relayClient = RelayClient(relayHost: InputConfig.relayHost, projectId: InputConfig.projectId, keychainStorage: keychain, socketFactory: DefaultSocketFactory(), logger: logger) let keyValueStorage = RuntimeKeyValueStorage() + let keychain = KeychainStorageMock() + let relayClient = RelayClientFactory.create( + relayHost: InputConfig.relayHost, + projectId: InputConfig.projectId, + keyValueStorage: keyValueStorage, + keychainStorage: keychain, + socketFactory: DefaultSocketFactory(), + logger: logger) + let networkingInteractor = NetworkingClientFactory.create( relayClient: relayClient, logger: logger, diff --git a/Example/IntegrationTests/History/HistoryTests.swift b/Example/IntegrationTests/History/HistoryTests.swift index ebd8f202e..8667a9224 100644 --- a/Example/IntegrationTests/History/HistoryTests.swift +++ b/Example/IntegrationTests/History/HistoryTests.swift @@ -24,7 +24,7 @@ final class HistoryTests: XCTestCase { } private func makeRelayClient(prefix: String, keychain: KeychainStorageProtocol) -> RelayClient { - return RelayClient( + return RelayClientFactory.create( relayHost: InputConfig.relayHost, projectId: InputConfig.projectId, keyValueStorage: RuntimeKeyValueStorage(), diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift index 267ed68d8..7a9a82635 100644 --- a/Example/IntegrationTests/Pairing/PairingTests.swift +++ b/Example/IntegrationTests/Pairing/PairingTests.swift @@ -8,6 +8,7 @@ import WalletConnectNetworking import WalletConnectEcho @testable import WalletConnectPush @testable import WalletConnectPairing +@testable import WalletConnectSync final class PairingTests: XCTestCase { @@ -21,7 +22,7 @@ final class PairingTests: XCTestCase { private var publishers = [AnyCancellable]() - func makeClientDependencies(prefix: String) -> (PairingClient, NetworkInteracting, KeychainStorageProtocol, KeyValueStorage) { + func makeClientDependencies(prefix: String) -> (PairingClient, NetworkInteracting, SyncClient, KeychainStorageProtocol, KeyValueStorage) { let keychain = KeychainStorageMock() let keyValueStorage = RuntimeKeyValueStorage() @@ -29,7 +30,7 @@ final class PairingTests: XCTestCase { let pairingLogger = ConsoleLogger(suffix: prefix + " [Pairing]", loggingLevel: .debug) let networkingLogger = ConsoleLogger(suffix: prefix + " [Networking]", loggingLevel: .debug) - let relayClient = RelayClient( + let relayClient = RelayClientFactory.create( relayHost: InputConfig.relayHost, projectId: InputConfig.projectId, keyValueStorage: RuntimeKeyValueStorage(), @@ -49,27 +50,30 @@ final class PairingTests: XCTestCase { keychainStorage: keychain, networkingClient: networkingClient) + let syncClient = SyncClientFactory.create(networkInteractor: networkingClient, bip44: DefaultBIP44Provider(), keychain: keychain) + let clientId = try! networkingClient.getClientId() networkingLogger.debug("My client id is: \(clientId)") - return (pairingClient, networkingClient, keychain, keyValueStorage) + return (pairingClient, networkingClient, syncClient, keychain, keyValueStorage) } func makeDappClients() { let prefix = "🤖 Dapp: " - let (pairingClient, networkingInteractor, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix) + let (pairingClient, networkingInteractor, syncClient, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix) let pushLogger = ConsoleLogger(suffix: prefix + " [Push]", loggingLevel: .debug) appPairingClient = pairingClient appPushClient = DappPushClientFactory.create(metadata: AppMetadata(name: name, description: "", url: "", icons: [""]), - logger: pushLogger, - keyValueStorage: keyValueStorage, - keychainStorage: keychain, - networkInteractor: networkingInteractor) + logger: pushLogger, + keyValueStorage: keyValueStorage, + keychainStorage: keychain, + networkInteractor: networkingInteractor, + syncClient: syncClient) } func makeWalletClients() { let prefix = "🐶 Wallet: " - let (pairingClient, networkingInteractor, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix) + let (pairingClient, networkingInteractor, syncClient, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix) let pushLogger = ConsoleLogger(suffix: prefix + " [Push]", loggingLevel: .debug) walletPairingClient = pairingClient let echoClient = EchoClientFactory.create(projectId: "", @@ -84,12 +88,13 @@ final class PairingTests: XCTestCase { groupKeychainStorage: KeychainStorageMock(), networkInteractor: networkingInteractor, pairingRegisterer: pairingClient, - echoClient: echoClient) + echoClient: echoClient, + syncClient: syncClient) } func makeWalletPairingClient() { let prefix = "🐶 Wallet: " - let (pairingClient, _, _, _) = makeClientDependencies(prefix: prefix) + let (pairingClient, _, _, _, _) = makeClientDependencies(prefix: prefix) walletPairingClient = pairingClient } diff --git a/Example/IntegrationTests/Push/PushTests.swift b/Example/IntegrationTests/Push/PushTests.swift index 328ea02b3..e2cefc982 100644 --- a/Example/IntegrationTests/Push/PushTests.swift +++ b/Example/IntegrationTests/Push/PushTests.swift @@ -1,6 +1,7 @@ import Foundation import XCTest import WalletConnectUtils +import Web3 @testable import WalletConnectKMS import WalletConnectRelay import Combine @@ -8,6 +9,7 @@ import WalletConnectNetworking import WalletConnectEcho @testable import WalletConnectPush @testable import WalletConnectPairing +@testable import WalletConnectSync import WalletConnectIdentity import WalletConnectSigner @@ -21,10 +23,19 @@ final class PushTests: XCTestCase { var pairingStorage: PairingStorage! + let pk = try! EthereumPrivateKey() + + var privateKey: Data { + return Data(pk.rawPrivateKey) + } + + var account: Account { + return Account("eip155:1:" + pk.address.hex(eip55: true))! + } private var publishers = [AnyCancellable]() - func makeClientDependencies(prefix: String) -> (PairingClient, NetworkInteracting, KeychainStorageProtocol, KeyValueStorage) { + func makeClientDependencies(prefix: String) -> (PairingClient, NetworkInteracting, SyncClient, KeychainStorageProtocol, KeyValueStorage) { let keychain = KeychainStorageMock() let keyValueStorage = RuntimeKeyValueStorage() @@ -32,10 +43,10 @@ final class PushTests: XCTestCase { let pairingLogger = ConsoleLogger(suffix: prefix + " [Pairing]", loggingLevel: .debug) let networkingLogger = ConsoleLogger(suffix: prefix + " [Networking]", loggingLevel: .debug) - let relayClient = RelayClient( + let relayClient = RelayClientFactory.create( relayHost: InputConfig.relayHost, projectId: InputConfig.projectId, - keyValueStorage: RuntimeKeyValueStorage(), + keyValueStorage: keyValueStorage, keychainStorage: keychain, socketFactory: DefaultSocketFactory(), logger: relayLogger) @@ -52,26 +63,29 @@ final class PushTests: XCTestCase { keychainStorage: keychain, networkingClient: networkingClient) + let syncClient = SyncClientFactory.create(networkInteractor: networkingClient, bip44: DefaultBIP44Provider(), keychain: keychain) + let clientId = try! networkingClient.getClientId() networkingLogger.debug("My client id is: \(clientId)") - return (pairingClient, networkingClient, keychain, keyValueStorage) + return (pairingClient, networkingClient, syncClient, keychain, keyValueStorage) } func makeDappClients() { let prefix = "🦄 Dapp: " - let (pairingClient, networkingInteractor, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix) + let (pairingClient, networkingInteractor, syncClient, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix) let pushLogger = ConsoleLogger(suffix: prefix + " [Push]", loggingLevel: .debug) dappPairingClient = pairingClient dappPushClient = DappPushClientFactory.create(metadata: AppMetadata(name: "GM Dapp", description: "", url: "https://gm-dapp-xi.vercel.app/", icons: []), logger: pushLogger, keyValueStorage: keyValueStorage, keychainStorage: keychain, - networkInteractor: networkingInteractor) + networkInteractor: networkingInteractor, + syncClient: syncClient) } func makeWalletClients() { let prefix = "🦋 Wallet: " - let (pairingClient, networkingInteractor, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix) + let (pairingClient, networkingInteractor, syncClient, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix) let pushLogger = ConsoleLogger(suffix: prefix + " [Push]", loggingLevel: .debug) walletPairingClient = pairingClient let echoClient = EchoClientFactory.create(projectId: "", @@ -86,7 +100,8 @@ final class PushTests: XCTestCase { groupKeychainStorage: KeychainStorageMock(), networkInteractor: networkingInteractor, pairingRegisterer: pairingClient, - echoClient: echoClient) + echoClient: echoClient, + syncClient: syncClient) } override func setUp() { @@ -99,7 +114,8 @@ final class PushTests: XCTestCase { let uri = try! await dappPairingClient.create() try! await walletPairingClient.pair(uri: uri) - try! await dappPushClient.propose(account: Account.stub(), topic: uri.topic) + try! await walletPushClient.enableSync(account: account, onSign: sign) + try! await dappPushClient.propose(account: account, topic: uri.topic) walletPushClient.requestPublisher.sink { [unowned self] (id, _, _) in Task(priority: .high) { try! await walletPushClient.approve(id: id, onSign: sign) } @@ -121,7 +137,7 @@ final class PushTests: XCTestCase { let uri = try! await dappPairingClient.create() try! await walletPairingClient.pair(uri: uri) - try! await dappPushClient.propose(account: Account.stub(), topic: uri.topic) + try! await dappPushClient.propose(account: account, topic: uri.topic) walletPushClient.requestPublisher.sink { [unowned self] (id, _, _) in Task(priority: .high) { try! await walletPushClient.reject(id: id) } @@ -141,7 +157,8 @@ final class PushTests: XCTestCase { func testWalletCreatesSubscription() async { let expectation = expectation(description: "expects to create push subscription") let metadata = AppMetadata(name: "GM Dapp", description: "", url: "https://gm-dapp-xi.vercel.app/", icons: []) - try! await walletPushClient.subscribe(metadata: metadata, account: Account.stub(), onSign: sign) + try! await walletPushClient.enableSync(account: account, onSign: sign) + try! await walletPushClient.subscribe(metadata: metadata, account: account, onSign: sign) walletPushClient.subscriptionsPublisher .first() .sink { [unowned self] subscriptions in @@ -152,12 +169,12 @@ final class PushTests: XCTestCase { wait(for: [expectation], timeout: InputConfig.defaultTimeout) } - func testDeletePushSubscription() async { let expectation = expectation(description: "expects to delete push subscription") let uri = try! await dappPairingClient.create() try! await walletPairingClient.pair(uri: uri) - try! await dappPushClient.propose(account: Account.stub(), topic: uri.topic) + try! await walletPushClient.enableSync(account: account, onSign: sign) + try! await dappPushClient.propose(account: account, topic: uri.topic) var subscriptionTopic: String! walletPushClient.requestPublisher.sink { [unowned self] (id, _, _) in @@ -185,7 +202,8 @@ final class PushTests: XCTestCase { let expectation = expectation(description: "expects to create and update push subscription") let metadata = AppMetadata(name: "GM Dapp", description: "", url: "https://gm-dapp-xi.vercel.app/", icons: []) let updateScope: Set = ["alerts"] - try! await walletPushClient.subscribe(metadata: metadata, account: Account.stub(), onSign: sign) + try! await walletPushClient.enableSync(account: account, onSign: sign) + try! await walletPushClient.subscribe(metadata: metadata, account: account, onSign: sign) walletPushClient.subscriptionsPublisher .first() .sink { [unowned self] subscriptions in @@ -212,7 +230,8 @@ final class PushTests: XCTestCase { let pushMessage = PushMessage.stub() let metadata = AppMetadata(name: "GM Dapp", description: "", url: "https://gm-dapp-xi.vercel.app/", icons: []) - try! await walletPushClient.subscribe(metadata: metadata, account: Account.stub(), onSign: sign) + try! await walletPushClient.enableSync(account: account, onSign: sign) + try! await walletPushClient.subscribe(metadata: metadata, account: account, onSign: sign) var subscription: PushSubscription! walletPushClient.subscriptionsPublisher .first() @@ -239,7 +258,6 @@ final class PushTests: XCTestCase { private extension PushTests { func sign(_ message: String) -> SigningResult { - let privateKey = Data(hex: "305c6cde3846927892cd32762f6120539f3ec74c9e3a16b9b798b1e85351ae2a") let signer = MessageSignerFactory(signerFactory: DefaultSignerFactory()).create(projectId: InputConfig.projectId) return .signed(try! signer.sign(message: message, privateKey: privateKey, type: .eip191)) } diff --git a/Example/IntegrationTests/Sign/SignClientTests.swift b/Example/IntegrationTests/Sign/SignClientTests.swift index 583cd886a..941a42d7c 100644 --- a/Example/IntegrationTests/Sign/SignClientTests.swift +++ b/Example/IntegrationTests/Sign/SignClientTests.swift @@ -14,16 +14,15 @@ final class SignClientTests: XCTestCase { static private func makeClientDelegate(name: String) -> ClientDelegate { let logger = ConsoleLogger(suffix: name, loggingLevel: .debug) let keychain = KeychainStorageMock() - let relayClient = RelayClient( + let keyValueStorage = RuntimeKeyValueStorage() + let relayClient = RelayClientFactory.create( relayHost: InputConfig.relayHost, projectId: InputConfig.projectId, - keyValueStorage: RuntimeKeyValueStorage(), + keyValueStorage: keyValueStorage, keychainStorage: keychain, socketFactory: DefaultSocketFactory(), - socketConnectionType: .automatic, logger: logger ) - let keyValueStorage = RuntimeKeyValueStorage() let networkingClient = NetworkingClientFactory.create( relayClient: relayClient, diff --git a/Example/IntegrationTests/Sync/SyncTests.swift b/Example/IntegrationTests/Sync/SyncTests.swift index 595052dd0..3f6464da4 100644 --- a/Example/IntegrationTests/Sync/SyncTests.swift +++ b/Example/IntegrationTests/Sync/SyncTests.swift @@ -57,7 +57,13 @@ final class SyncTests: XCTestCase { let kms = KeyManagementService(keychain: keychain) let derivationService = SyncDerivationService(syncStorage: syncSignatureStore, bip44: DefaultBIP44Provider(), kms: kms) let logger = ConsoleLogger(suffix: suffix, loggingLevel: .debug) - let relayClient = RelayClient(relayHost: InputConfig.relayHost, projectId: InputConfig.projectId, keychainStorage: keychain, socketFactory: DefaultSocketFactory(), logger: logger) + let relayClient = RelayClientFactory.create( + relayHost: InputConfig.relayHost, + projectId: InputConfig.projectId, + keyValueStorage: RuntimeKeyValueStorage(), + keychainStorage: keychain, + socketFactory: DefaultSocketFactory(), + logger: logger) let networkingInteractor = NetworkingClientFactory.create( relayClient: relayClient, logger: logger, diff --git a/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift b/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift index fd273e35a..e33ab1cec 100644 --- a/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift +++ b/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift @@ -50,7 +50,17 @@ final class RelayClientEndToEndTests: XCTestCase { socketConnectionType: .manual, logger: logger ) - let relayClient = RelayClient(dispatcher: dispatcher, logger: logger, keyValueStorage: RuntimeKeyValueStorage(), clientIdStorage: clientIdStorage) + let keychain = KeychainStorageMock() + let keyValueStorage = RuntimeKeyValueStorage() + let relayClient = RelayClientFactory.create( + relayHost: InputConfig.relayHost, + projectId: InputConfig.projectId, + keyValueStorage: keyValueStorage, + keychainStorage: keychain, + socketFactory: DefaultSocketFactory(), + socketConnectionType: .manual, + logger: logger + ) let clientId = try! relayClient.getClientId() logger.debug("My client id is: \(clientId)") diff --git a/Example/WalletApp/ApplicationLayer/EthKeyStore.swift b/Example/WalletApp/ApplicationLayer/EthKeyStore.swift index 782a1d3a0..7b4608a34 100644 --- a/Example/WalletApp/ApplicationLayer/EthKeyStore.swift +++ b/Example/WalletApp/ApplicationLayer/EthKeyStore.swift @@ -7,7 +7,7 @@ class EthKeyStore { let privateKey: EthereumPrivateKey var address: String { - return privateKey.address.hex(eip55: false) + return privateKey.address.hex(eip55: true) } var privateKeyRaw: Data { diff --git a/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsView.swift b/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsView.swift index 91f2f6f57..65a838b11 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsView.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/ConnectionDetails/ConnectionDetailsView.swift @@ -110,6 +110,41 @@ struct ConnectionDetailsView: View { .padding(.top, 30) } + if let sessionProperties = presenter.session.sessionProperties { + VStack(alignment: .leading) { + Text("sessionProperties") + .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: sessionProperties.map { "\($0): \($1)" }) { + 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/SessionProposal/SessionProposalInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift index 0055364f4..3df2dece3 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/SessionProposal/SessionProposalInteractor.swift @@ -28,7 +28,7 @@ final class SessionProposalInteractor { events: Array(supportedEvents), accounts: supportedAccounts ) - try await Web3Wallet.instance.approve(proposalId: proposal.id, namespaces: sessionNamespaces) + try await Web3Wallet.instance.approve(proposalId: proposal.id, namespaces: sessionNamespaces, sessionProperties: proposal.sessionProperties) } catch { print(error) } diff --git a/Package.swift b/Package.swift index 23d31ae0f..bd74fd149 100644 --- a/Package.swift +++ b/Package.swift @@ -76,7 +76,7 @@ let package = Package( path: "Sources/Web3Wallet"), .target( name: "WalletConnectPush", - dependencies: ["WalletConnectPairing", "WalletConnectEcho", "WalletConnectNetworking", "WalletConnectIdentity", "WalletConnectSigner"], + dependencies: ["WalletConnectPairing", "WalletConnectEcho", "WalletConnectIdentity", "WalletConnectSync"], path: "Sources/WalletConnectPush"), .target( name: "WalletConnectEcho", diff --git a/Sources/Auth/Services/Wallet/PendingRequestsProvider.swift b/Sources/Auth/Services/Wallet/PendingRequestsProvider.swift index 524c029d5..5050ef778 100644 --- a/Sources/Auth/Services/Wallet/PendingRequestsProvider.swift +++ b/Sources/Auth/Services/Wallet/PendingRequestsProvider.swift @@ -12,7 +12,7 @@ class PendingRequestsProvider { .filter {$0.request.method == "wc_authRequest"} .compactMap { guard let params = try? $0.request.params?.get(AuthRequestParams.self) else { return nil } - return AuthRequest(id: $0.request.id!, payload: params.payloadParams) + return AuthRequest(id: $0.request.id!, topic: $0.topic, payload: params.payloadParams) } return pendingRequests } diff --git a/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift b/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift index 18cda08d3..fd575188d 100644 --- a/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift +++ b/Sources/Auth/Services/Wallet/WalletRequestSubscriber.swift @@ -38,7 +38,7 @@ class WalletRequestSubscriber { peerMetadata: payload.request.requester.metadata ) - let request = AuthRequest(id: payload.id, payload: payload.request.payloadParams) + let request = AuthRequest(id: payload.id, topic: payload.topic, payload: payload.request.payloadParams) guard let verifyClient else { onRequest?((request, nil)) diff --git a/Sources/Auth/Types/Public/AuthRequest.swift b/Sources/Auth/Types/Public/AuthRequest.swift index b5095751c..4cee66bb1 100644 --- a/Sources/Auth/Types/Public/AuthRequest.swift +++ b/Sources/Auth/Types/Public/AuthRequest.swift @@ -2,5 +2,6 @@ import Foundation public struct AuthRequest: Equatable, Codable { public let id: RPCID + public let topic: String public let payload: AuthPayload } diff --git a/Sources/WalletConnectPush/APNSEnvironment.swift b/Sources/WalletConnectPush/APNSEnvironment.swift deleted file mode 100644 index 567373443..000000000 --- a/Sources/WalletConnectPush/APNSEnvironment.swift +++ /dev/null @@ -1,3 +0,0 @@ -import WalletConnectEcho - -public typealias APNSEnvironment = WalletConnectEcho.APNSEnvironment diff --git a/Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionService.swift b/Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionService.swift index 3f32e75df..287cdb61a 100644 --- a/Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionService.swift +++ b/Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionService.swift @@ -1,6 +1,4 @@ import Foundation -import WalletConnectKMS -import WalletConnectUtils class DeletePushSubscriptionService { enum Errors: Error { @@ -9,27 +7,27 @@ class DeletePushSubscriptionService { private let networkingInteractor: NetworkInteracting private let kms: KeyManagementServiceProtocol private let logger: ConsoleLogging - private let pushSubscriptionStore: CodableStore + private let pushStorage: PushStorage private let pushMessagesDatabase: PushMessagesDatabase? init(networkingInteractor: NetworkInteracting, kms: KeyManagementServiceProtocol, logger: ConsoleLogging, - pushSubscriptionStore: CodableStore, + pushStorage: PushStorage, pushMessagesDatabase: PushMessagesDatabase?) { self.networkingInteractor = networkingInteractor self.kms = kms self.logger = logger self.pushMessagesDatabase = pushMessagesDatabase - self.pushSubscriptionStore = pushSubscriptionStore + self.pushStorage = pushStorage } func delete(topic: String) async throws { let params = PushDeleteParams.userDisconnected logger.debug("Will delete push subscription for reason: message: \(params.message) code: \(params.code), topic: \(topic)") - guard let _ = try? pushSubscriptionStore.get(key: topic) + guard let _ = pushStorage.getSubscription(topic: topic) else { throw Errors.pushSubscriptionNotFound} let protocolMethod = PushDeleteProtocolMethod() - pushSubscriptionStore.delete(forKey: topic) + try await pushStorage.deleteSubscription(topic: topic) pushMessagesDatabase?.deletePushMessages(topic: topic) let request = RPCRequest(method: protocolMethod.method, params: params) try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) diff --git a/Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionSubscriber.swift b/Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionSubscriber.swift index bab7a8afa..7ec0f6f3a 100644 --- a/Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionSubscriber.swift +++ b/Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionSubscriber.swift @@ -1,30 +1,22 @@ import Foundation import Combine -import WalletConnectKMS -import WalletConnectPairing class DeletePushSubscriptionSubscriber { private let networkingInteractor: NetworkInteracting private let kms: KeyManagementServiceProtocol private let logger: ConsoleLogging private var publishers = [AnyCancellable]() - private let pushSubscriptionStore: CodableStore - - private let deleteSubscriptionPublisherSubject = PassthroughSubject() - - public var deleteSubscriptionPublisher: AnyPublisher { - deleteSubscriptionPublisherSubject.eraseToAnyPublisher() - } + private let pushStorage: PushStorage init(networkingInteractor: NetworkInteracting, kms: KeyManagementServiceProtocol, logger: ConsoleLogging, - pushSubscriptionStore: CodableStore + pushStorage: PushStorage ) { self.networkingInteractor = networkingInteractor self.kms = kms self.logger = logger - self.pushSubscriptionStore = pushSubscriptionStore + self.pushStorage = pushStorage subscribeForDeleteSubscription() } @@ -35,9 +27,10 @@ class DeletePushSubscriptionSubscriber { logger.debug("Peer deleted subscription") let topic = payload.topic networkingInteractor.unsubscribe(topic: topic) - pushSubscriptionStore.delete(forKey: topic) + Task(priority: .high) { + try await pushStorage.deleteSubscription(topic: topic) + } kms.deleteSymmetricKey(for: topic) - deleteSubscriptionPublisherSubject.send(payload.topic) }.store(in: &publishers) } } diff --git a/Sources/WalletConnectPush/Client/Common/PushDecryptionService.swift b/Sources/WalletConnectPush/Client/Common/PushDecryptionService.swift index e2179fc95..995ff4e4a 100644 --- a/Sources/WalletConnectPush/Client/Common/PushDecryptionService.swift +++ b/Sources/WalletConnectPush/Client/Common/PushDecryptionService.swift @@ -1,7 +1,5 @@ import Foundation -import WalletConnectKMS - public class PushDecryptionService { enum Errors: Error { case malformedPushMessage diff --git a/Sources/WalletConnectPush/Client/Common/PushResubscribeService.swift b/Sources/WalletConnectPush/Client/Common/PushResubscribeService.swift index 227d8d2cb..c19769f4d 100644 --- a/Sources/WalletConnectPush/Client/Common/PushResubscribeService.swift +++ b/Sources/WalletConnectPush/Client/Common/PushResubscribeService.swift @@ -1,5 +1,4 @@ import Foundation -import WalletConnectNetworking import Combine final class PushResubscribeService { @@ -7,11 +6,11 @@ final class PushResubscribeService { private var publishers = Set() private let networkInteractor: NetworkInteracting - private let subscriptionsStorage: CodableStore + private let pushStorage: PushStorage - init(networkInteractor: NetworkInteracting, subscriptionsStorage: CodableStore) { + init(networkInteractor: NetworkInteracting, pushStorage: PushStorage) { self.networkInteractor = networkInteractor - self.subscriptionsStorage = subscriptionsStorage + self.pushStorage = pushStorage setUpResubscription() } @@ -19,7 +18,7 @@ final class PushResubscribeService { networkInteractor.socketConnectionStatusPublisher .sink { [unowned self] status in guard status == .connected else { return } - let topics = subscriptionsStorage.getAll().map{$0.topic} + let topics = pushStorage.getSubscriptions().map{$0.topic} Task(priority: .high) { try await networkInteractor.batchSubscribe(topics: topics) } diff --git a/Sources/WalletConnectPush/Client/Common/SubscriptionsProvider.swift b/Sources/WalletConnectPush/Client/Common/SubscriptionsProvider.swift deleted file mode 100644 index 7db605eaf..000000000 --- a/Sources/WalletConnectPush/Client/Common/SubscriptionsProvider.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation - -class SubscriptionsProvider { - let store: CodableStore - - init(store: CodableStore) { - self.store = store - } - - public func getActiveSubscriptions() -> [PushSubscription] { - store.getAll() - } -} diff --git a/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift b/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift index 116885340..9c7d820ea 100644 --- a/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift +++ b/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift @@ -1,6 +1,5 @@ import Foundation import Combine -import WalletConnectUtils public class DappPushClient { public var proposalResponsePublisher: AnyPublisher, Never> { @@ -8,26 +7,26 @@ public class DappPushClient { } public var deleteSubscriptionPublisher: AnyPublisher { - deletePushSubscriptionSubscriber.deleteSubscriptionPublisher + return pushStorage.deleteSubscriptionPublisher } public let logger: ConsoleLogging private let notifyProposer: NotifyProposer - private let subscriptionsProvider: SubscriptionsProvider + private let pushStorage: PushStorage private let deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber private let resubscribeService: PushResubscribeService private let notifyProposeResponseSubscriber: NotifyProposeResponseSubscriber init(logger: ConsoleLogging, kms: KeyManagementServiceProtocol, - subscriptionsProvider: SubscriptionsProvider, + pushStorage: PushStorage, deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber, resubscribeService: PushResubscribeService, notifyProposer: NotifyProposer, notifyProposeResponseSubscriber: NotifyProposeResponseSubscriber) { self.logger = logger - self.subscriptionsProvider = subscriptionsProvider + self.pushStorage = pushStorage self.deletePushSubscriptionSubscriber = deletePushSubscriptionSubscriber self.resubscribeService = resubscribeService self.notifyProposer = notifyProposer @@ -39,6 +38,6 @@ public class DappPushClient { } public func getActiveSubscriptions() -> [PushSubscription] { - subscriptionsProvider.getActiveSubscriptions() + pushStorage.getSubscriptions() } } diff --git a/Sources/WalletConnectPush/Client/Dapp/DappPushClientFactory.swift b/Sources/WalletConnectPush/Client/Dapp/DappPushClientFactory.swift index 8764901a3..18737a113 100644 --- a/Sources/WalletConnectPush/Client/Dapp/DappPushClientFactory.swift +++ b/Sources/WalletConnectPush/Client/Dapp/DappPushClientFactory.swift @@ -1,9 +1,8 @@ import Foundation -import WalletConnectPairing public struct DappPushClientFactory { - public static func create(metadata: AppMetadata, networkInteractor: NetworkInteracting) -> DappPushClient { + public static func create(metadata: AppMetadata, networkInteractor: NetworkInteracting, syncClient: SyncClient) -> DappPushClient { let logger = ConsoleLogger(loggingLevel: .off) let keyValueStorage = UserDefaults.standard let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk") @@ -12,22 +11,25 @@ public struct DappPushClientFactory { logger: logger, keyValueStorage: keyValueStorage, keychainStorage: keychainStorage, - networkInteractor: networkInteractor + networkInteractor: networkInteractor, + syncClient: syncClient ) } - static func create(metadata: AppMetadata, logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, networkInteractor: NetworkInteracting) -> DappPushClient { + static func create(metadata: AppMetadata, logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, networkInteractor: NetworkInteracting, syncClient: SyncClient) -> DappPushClient { let kms = KeyManagementService(keychain: keychainStorage) - let subscriptionStore = CodableStore(defaults: keyValueStorage, identifier: PushStorageIdntifiers.pushSubscription) - let subscriptionProvider = SubscriptionsProvider(store: subscriptionStore) - let deletePushSubscriptionSubscriber = DeletePushSubscriptionSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushSubscriptionStore: subscriptionStore) - let resubscribeService = PushResubscribeService(networkInteractor: networkInteractor, subscriptionsStorage: subscriptionStore) + let subscriptionStore: SyncStore = SyncStoreFactory.create(name: PushStorageIdntifiers.pushSubscription, syncClient: syncClient, storage: keyValueStorage) + + let subscriptionStoreDelegate = PushSubscriptionStoreDelegate(networkingInteractor: networkInteractor, kms: kms) + let pushStorage = PushStorage(subscriptionStore: subscriptionStore, subscriptionStoreDelegate: subscriptionStoreDelegate) + let deletePushSubscriptionSubscriber = DeletePushSubscriptionSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushStorage: pushStorage) + let resubscribeService = PushResubscribeService(networkInteractor: networkInteractor, pushStorage: pushStorage) let notifyProposer = NotifyProposer(networkingInteractor: networkInteractor, kms: kms, appMetadata: metadata, logger: logger) let notifyProposeResponseSubscriber = NotifyProposeResponseSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, metadata: metadata) return DappPushClient( logger: logger, kms: kms, - subscriptionsProvider: subscriptionProvider, + pushStorage: pushStorage, deletePushSubscriptionSubscriber: deletePushSubscriptionSubscriber, resubscribeService: resubscribeService, notifyProposer: notifyProposer, diff --git a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift index beed07644..ee294a384 100644 --- a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift +++ b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift @@ -51,7 +51,7 @@ class NotifyProposeResponseSubscriber { let subscriptionKey = try SymmetricKey(hex: payload.response.subscriptionSymKey) let subscriptionTopic = subscriptionKey.rawRepresentation.sha256().toHexString() let relay = RelayProtocolOptions(protocol: "irn", data: nil) - let subscription = PushSubscription(topic: subscriptionTopic, account: payload.request.account, relay: relay, metadata: metadata, scope: [:], expiry: expiry) + let subscription = PushSubscription(topic: subscriptionTopic, account: payload.request.account, relay: relay, metadata: metadata, scope: [:], expiry: expiry, symKey: subscriptionKey.hexRepresentation) try kms.setSymmetricKey(subscriptionKey, for: subscriptionTopic) try await networkingInteractor.subscribe(topic: subscriptionTopic) return subscription diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift index 21b52257a..01b4fbcf2 100644 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift @@ -11,24 +11,25 @@ class NotifyProposeResponder { private let networkingInteractor: NetworkInteracting private let kms: KeyManagementServiceProtocol private let logger: ConsoleLogging + private let pushStorage: PushStorage private let pushSubscribeRequester: PushSubscribeRequester private let rpcHistory: RPCHistory - private var subscriptionResponsePublisher: AnyPublisher, Never> private var publishers = [AnyCancellable]() init(networkingInteractor: NetworkInteracting, kms: KeyManagementServiceProtocol, logger: ConsoleLogging, + pushStorage: PushStorage, pushSubscribeRequester: PushSubscribeRequester, rpcHistory: RPCHistory, - pushSubscribeResponseSubscriber: PushSubscribeResponseSubscriber + pushSubscribeResponseSubscriber: PushSubscribeResponseSubscriber ) { self.networkingInteractor = networkingInteractor self.kms = kms self.logger = logger + self.pushStorage = pushStorage self.pushSubscribeRequester = pushSubscribeRequester - self.subscriptionResponsePublisher = pushSubscribeResponseSubscriber.subscriptionPublisher self.rpcHistory = rpcHistory } @@ -42,18 +43,13 @@ class NotifyProposeResponder { let subscriptionAuthWrapper = try await pushSubscribeRequester.subscribe(metadata: proposal.metadata, account: proposal.account, onSign: onSign) var pushSubscription: PushSubscription! - try await withCheckedThrowingContinuation { continuation in - subscriptionResponsePublisher + try await withCheckedThrowingContinuation { [unowned self] continuation in + pushStorage.newSubscriptionPublisher .first() - .sink(receiveValue: { value in - switch value { - case .success(let subscription): - pushSubscription = subscription + .sink { value in + pushSubscription = value continuation.resume() - case .failure(let error): - continuation.resume(throwing: error) - } - }).store(in: &publishers) + }.store(in: &publishers) } guard let peerPublicKey = try? AgreementPublicKey(hex: proposal.publicKey) else { diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeSubscriber.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeSubscriber.swift index a9b99b4ac..3ea41d805 100644 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeSubscriber.swift +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeSubscriber.swift @@ -6,7 +6,7 @@ class NotifyProposeSubscriber { private let requestPublisherSubject = PassthroughSubject() private let networkingInteractor: NetworkInteracting - private let subscriptionsStore: CodableStore + private let pushStorage: PushStorage private var publishers = Set() public var requestPublisher: AnyPublisher { requestPublisherSubject.eraseToAnyPublisher() @@ -15,12 +15,12 @@ class NotifyProposeSubscriber { private let pairingRegisterer: PairingRegisterer init(networkingInteractor: NetworkInteracting, - subscriptionsStore: CodableStore, + pushStorage: PushStorage, publishers: Set = Set(), logger: ConsoleLogging, pairingRegisterer: PairingRegisterer) { self.networkingInteractor = networkingInteractor - self.subscriptionsStore = subscriptionsStore + self.pushStorage = pushStorage self.publishers = publishers self.logger = logger self.pairingRegisterer = pairingRegisterer @@ -40,7 +40,7 @@ class NotifyProposeSubscriber { } func hasNoSubscription(for domain: String) -> Bool { - subscriptionsStore.getAll().first {$0.metadata.url == domain} == nil + pushStorage.getSubscriptions().first { $0.metadata.url == domain } == nil } func respondError(requestId: RPCID, pairingTopic: String) async throws { diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift index 814a08d2e..565c57753 100644 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift @@ -1,4 +1,3 @@ - import Foundation class NotifyUpdateRequester { @@ -10,25 +9,25 @@ class NotifyUpdateRequester { private let identityClient: IdentityClient private let networkingInteractor: NetworkInteracting private let logger: ConsoleLogging - private let subscriptionsStore: CodableStore + private let pushStorage: PushStorage init(keyserverURL: URL, identityClient: IdentityClient, networkingInteractor: NetworkInteracting, logger: ConsoleLogging, - subscriptionsStore: CodableStore + pushStorage: PushStorage ) { self.keyserverURL = keyserverURL self.identityClient = identityClient self.networkingInteractor = networkingInteractor self.logger = logger - self.subscriptionsStore = subscriptionsStore + self.pushStorage = pushStorage } func update(topic: String, scope: Set) async throws { - logger.debug("NotifyUpdateRequester: updating subscription for topic: \(topic)") - guard let subscription = try subscriptionsStore.get(key: topic) else { throw Errors.noSubscriptionForGivenTopic } + + guard let subscription = pushStorage.getSubscription(topic: topic) else { throw Errors.noSubscriptionForGivenTopic } let request = try createJWTRequest(subscriptionAccount: subscription.account, dappUrl: subscription.metadata.url, scope: scope) diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift index ef7e24ea0..e3b77d8ca 100644 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift @@ -10,7 +10,7 @@ class NotifyUpdateResponseSubscriber { private let networkingInteractor: NetworkInteracting private var publishers = [AnyCancellable]() private let logger: ConsoleLogging - private let subscriptionsStore: CodableStore + private let pushStorage: PushStorage private let subscriptionScopeProvider: SubscriptionScopeProvider private var subscriptionPublisherSubject = PassthroughSubject, Never>() var updateSubscriptionPublisher: AnyPublisher, Never> { @@ -20,11 +20,11 @@ class NotifyUpdateResponseSubscriber { init(networkingInteractor: NetworkInteracting, logger: ConsoleLogging, subscriptionScopeProvider: SubscriptionScopeProvider, - subscriptionsStore: CodableStore + pushStorage: PushStorage ) { self.networkingInteractor = networkingInteractor self.logger = logger - self.subscriptionsStore = subscriptionsStore + self.pushStorage = pushStorage self.subscriptionScopeProvider = subscriptionScopeProvider subscribeForUpdateResponse() } @@ -41,16 +41,16 @@ class NotifyUpdateResponseSubscriber { let (_, claims) = try SubscriptionJWTPayload.decodeAndVerify(from: payload.request) let scope = try await buildScope(selected: claims.scp, dappUrl: claims.aud) - guard let oldSubscription = try? subscriptionsStore.get(key: subscriptionTopic) else { + guard let oldSubscription = pushStorage.getSubscription(topic: subscriptionTopic) else { logger.debug("NotifyUpdateResponseSubscriber Subscription does not exist") subscriptionPublisherSubject.send(.failure(Errors.subscriptionDoesNotExist)) return } let expiry = Date(timeIntervalSince1970: TimeInterval(claims.exp)) - let updatedSubscription = PushSubscription(topic: subscriptionTopic, account: oldSubscription.account, relay: oldSubscription.relay, metadata: oldSubscription.metadata, scope: scope, expiry: expiry) + let updatedSubscription = PushSubscription(topic: subscriptionTopic, account: oldSubscription.account, relay: oldSubscription.relay, metadata: oldSubscription.metadata, scope: scope, expiry: expiry, symKey: oldSubscription.symKey) - subscriptionsStore.set(updatedSubscription, forKey: subscriptionTopic) + try await pushStorage.setSubscription(updatedSubscription) subscriptionPublisherSubject.send(.success(updatedSubscription)) diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushMessage/PushMessageSubscriber.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushMessage/PushMessageSubscriber.swift index ba95b837c..27dfa0ee9 100644 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushMessage/PushMessageSubscriber.swift +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushMessage/PushMessageSubscriber.swift @@ -1,7 +1,5 @@ import Foundation import Combine -import WalletConnectKMS -import WalletConnectPairing class PushMessageSubscriber { private let networkingInteractor: NetworkInteracting diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift index 2a4f12782..cfd061084 100644 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift @@ -7,6 +7,7 @@ class PushSubscribeRequester { case didDocDoesNotContainKeyAgreement case noVerificationMethodForKey case unsupportedCurve + case signatureRejected } private let keyserverURL: URL diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeResponseSubscriber.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeResponseSubscriber.swift index b5aa6af71..d8c5fe66c 100644 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeResponseSubscriber.swift +++ b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeResponseSubscriber.swift @@ -1,4 +1,3 @@ - import Foundation import Combine @@ -7,24 +6,26 @@ class PushSubscribeResponseSubscriber { case couldNotCreateSubscription } + private let subscriptionErrorSubject = PassthroughSubject() + + var subscriptionErrorPublisher: AnyPublisher { + return subscriptionErrorSubject.eraseToAnyPublisher() + } + private let networkingInteractor: NetworkInteracting private let kms: KeyManagementServiceProtocol private var publishers = [AnyCancellable]() private let logger: ConsoleLogging - private let subscriptionsStore: CodableStore + private let pushStorage: PushStorage private let groupKeychainStorage: KeychainStorageProtocol private let dappsMetadataStore: CodableStore private let subscriptionScopeProvider: SubscriptionScopeProvider - private var subscriptionPublisherSubject = PassthroughSubject, Never>() - var subscriptionPublisher: AnyPublisher, Never> { - return subscriptionPublisherSubject.eraseToAnyPublisher() - } init(networkingInteractor: NetworkInteracting, kms: KeyManagementServiceProtocol, logger: ConsoleLogging, groupKeychainStorage: KeychainStorageProtocol, - subscriptionsStore: CodableStore, + pushStorage: PushStorage, dappsMetadataStore: CodableStore, subscriptionScopeProvider: SubscriptionScopeProvider ) { @@ -32,7 +33,7 @@ class PushSubscribeResponseSubscriber { self.kms = kms self.logger = logger self.groupKeychainStorage = groupKeychainStorage - self.subscriptionsStore = subscriptionsStore + self.pushStorage = pushStorage self.dappsMetadataStore = dappsMetadataStore self.subscriptionScopeProvider = subscriptionScopeProvider subscribeForSubscriptionResponse() @@ -47,8 +48,7 @@ class PushSubscribeResponseSubscriber { guard let responseKeys = kms.getAgreementSecret(for: payload.topic) else { logger.debug("PushSubscribeResponseSubscriber: no symmetric key for topic \(payload.topic)") - subscriptionPublisherSubject.send(.failure(Errors.couldNotCreateSubscription)) - return + return subscriptionErrorSubject.send(Errors.couldNotCreateSubscription) } // get keypair Y @@ -59,12 +59,13 @@ class PushSubscribeResponseSubscriber { var metadata: AppMetadata! var pushSubscriptionTopic: String! var subscribedTypes: Set! + var agreementKeysP: AgreementKeys! let (subscriptionPayload, claims) = try SubscriptionJWTPayload.decodeAndVerify(from: payload.request) let subscribedScope = subscriptionPayload.scope .components(separatedBy: " ") do { // generate symm key P - let agreementKeysP = try kms.performKeyAgreement(selfPublicKey: pubKeyY, peerPublicKey: peerPubKeyZ) + agreementKeysP = try kms.performKeyAgreement(selfPublicKey: pubKeyY, peerPublicKey: peerPubKeyZ) pushSubscriptionTopic = agreementKeysP.derivedTopic() try kms.setAgreementSecret(agreementKeysP, topic: pushSubscriptionTopic) try groupKeychainStorage.add(agreementKeysP, forKey: pushSubscriptionTopic) @@ -76,26 +77,22 @@ class PushSubscribeResponseSubscriber { try await networkingInteractor.subscribe(topic: pushSubscriptionTopic) } catch { logger.debug("PushSubscribeResponseSubscriber: error: \(error)") - subscriptionPublisherSubject.send(.failure(Errors.couldNotCreateSubscription)) - return + return subscriptionErrorSubject.send(Errors.couldNotCreateSubscription) } guard let metadata = metadata else { logger.debug("PushSubscribeResponseSubscriber: no metadata for topic: \(pushSubscriptionTopic!)") - subscriptionPublisherSubject.send(.failure(Errors.couldNotCreateSubscription)) - return + return subscriptionErrorSubject.send(Errors.couldNotCreateSubscription) } dappsMetadataStore.delete(forKey: payload.topic) let expiry = Date(timeIntervalSince1970: TimeInterval(claims.exp)) let scope: [String: ScopeValue] = subscribedTypes.reduce(into: [:]) { $0[$1.name] = ScopeValue(description: $1.description, enabled: true) } - let pushSubscription = PushSubscription(topic: pushSubscriptionTopic, account: account, relay: RelayProtocolOptions(protocol: "irn", data: nil), metadata: metadata, scope: scope, expiry: expiry) + let pushSubscription = PushSubscription(topic: pushSubscriptionTopic, account: account, relay: RelayProtocolOptions(protocol: "irn", data: nil), metadata: metadata, scope: scope, expiry: expiry, symKey: agreementKeysP.sharedKey.hexRepresentation) - subscriptionsStore.set(pushSubscription, forKey: pushSubscriptionTopic) + try await pushStorage.setSubscription(pushSubscription) logger.debug("PushSubscribeResponseSubscriber: unsubscribing response topic: \(payload.topic)") networkingInteractor.unsubscribe(topic: payload.topic) - - subscriptionPublisherSubject.send(.success(pushSubscription)) } }.store(in: &publishers) } diff --git a/Sources/WalletConnectPush/Client/Wallet/PushMessagesDatabase.swift b/Sources/WalletConnectPush/Client/Wallet/PushMessagesDatabase.swift index ecb31d791..8da65ce31 100644 --- a/Sources/WalletConnectPush/Client/Wallet/PushMessagesDatabase.swift +++ b/Sources/WalletConnectPush/Client/Wallet/PushMessagesDatabase.swift @@ -1,6 +1,4 @@ - import Foundation -import WalletConnectUtils import Combine class PushMessagesDatabase { diff --git a/Sources/WalletConnectPush/Client/Wallet/PushStorage.swift b/Sources/WalletConnectPush/Client/Wallet/PushStorage.swift new file mode 100644 index 000000000..8393c734f --- /dev/null +++ b/Sources/WalletConnectPush/Client/Wallet/PushStorage.swift @@ -0,0 +1,74 @@ +import Foundation +import Combine + +final class PushStorage { + + private var publishers = Set() + + private let subscriptionStore: SyncStore + + private let newSubscriptionSubject = PassthroughSubject() + private let deleteSubscriptionSubject = PassthroughSubject() + + private let subscriptionStoreDelegate: PushSubscriptionStoreDelegate + + var newSubscriptionPublisher: AnyPublisher { + return newSubscriptionSubject.eraseToAnyPublisher() + } + + var deleteSubscriptionPublisher: AnyPublisher { + return deleteSubscriptionSubject.eraseToAnyPublisher() + } + + var subscriptionsPublisher: AnyPublisher<[PushSubscription], Never> { + return subscriptionStore.dataUpdatePublisher + } + + init(subscriptionStore: SyncStore, subscriptionStoreDelegate: PushSubscriptionStoreDelegate) { + self.subscriptionStore = subscriptionStore + self.subscriptionStoreDelegate = subscriptionStoreDelegate + setupSubscriptions() + } + + func initialize(account: Account) async throws { + try await subscriptionStore.initialize(for: account) + } + + func setupSubscriptions(account: Account) async throws { + try subscriptionStore.setupSubscriptions(account: account) + } + + func getSubscriptions() -> [PushSubscription] { + return subscriptionStore.getAll() + } + + func getSubscription(topic: String) -> PushSubscription? { + return subscriptionStore.get(for: topic) + } + + func setSubscription(_ subscription: PushSubscription) async throws { + try await subscriptionStore.set(object: subscription, for: subscription.account) + newSubscriptionSubject.send(subscription) + } + + func deleteSubscription(topic: String) async throws { + try await subscriptionStore.delete(id: topic) + deleteSubscriptionSubject.send(topic) + } +} + +private extension PushStorage { + + func setupSubscriptions() { + subscriptionStore.syncUpdatePublisher.sink { [unowned self] (_, _, update) in + switch update { + case .set(let subscription): + subscriptionStoreDelegate.onUpdate(subscription) + newSubscriptionSubject.send(subscription) + case .delete(let id): + subscriptionStoreDelegate.onDelete(id) + deleteSubscriptionSubject.send(id) + } + }.store(in: &publishers) + } +} diff --git a/Sources/WalletConnectPush/Client/Wallet/PushSubscriptionStoreDelegate.swift b/Sources/WalletConnectPush/Client/Wallet/PushSubscriptionStoreDelegate.swift new file mode 100644 index 000000000..e38f5043c --- /dev/null +++ b/Sources/WalletConnectPush/Client/Wallet/PushSubscriptionStoreDelegate.swift @@ -0,0 +1,24 @@ +import Foundation + +final class PushSubscriptionStoreDelegate { + + private let networkingInteractor: NetworkInteracting + private let kms: KeyManagementServiceProtocol + + init(networkingInteractor: NetworkInteracting, kms: KeyManagementServiceProtocol) { + self.networkingInteractor = networkingInteractor + self.kms = kms + } + + func onUpdate(_ subscription: PushSubscription) { + Task(priority: .high) { + let symmetricKey = try SymmetricKey(hex: subscription.symKey) + try kms.setSymmetricKey(symmetricKey, for: subscription.topic) + try await networkingInteractor.subscribe(topic: subscription.topic) + } + } + + func onDelete(_ id: String) { + + } +} diff --git a/Sources/WalletConnectPush/Client/Wallet/PushSubscriptionsObserver.swift b/Sources/WalletConnectPush/Client/Wallet/PushSubscriptionsObserver.swift deleted file mode 100644 index a4e330ec3..000000000 --- a/Sources/WalletConnectPush/Client/Wallet/PushSubscriptionsObserver.swift +++ /dev/null @@ -1,25 +0,0 @@ -import Combine -import Foundation - -class PushSubscriptionsObserver { - private var publishers = [AnyCancellable]() - - public var subscriptionsPublisher: AnyPublisher<[PushSubscription], Never> { - subscriptionsPublisherSubject.eraseToAnyPublisher() - } - private let subscriptionsPublisherSubject = PassthroughSubject<[PushSubscription], Never>() - - private let store: CodableStore - - init(store: CodableStore) { - self.store = store - setUpSubscription() - } - - func setUpSubscription() { - store.storeUpdatePublisher.sink(receiveValue: { [unowned self] in - let subscriptions = store.getAll() - subscriptionsPublisherSubject.send(subscriptions) - }).store(in: &publishers) - } -} diff --git a/Sources/WalletConnectPush/Client/Wallet/PushSyncService.swift b/Sources/WalletConnectPush/Client/Wallet/PushSyncService.swift new file mode 100644 index 000000000..6b4a5d8d1 --- /dev/null +++ b/Sources/WalletConnectPush/Client/Wallet/PushSyncService.swift @@ -0,0 +1,26 @@ +import Foundation + +final class PushSyncService { + + private let syncClient: SyncClient + private let logger: ConsoleLogging + + init(syncClient: SyncClient, logger: ConsoleLogging) { + self.syncClient = syncClient + self.logger = logger + } + + func registerIfNeeded(account: Account, onSign: @escaping SigningCallback) async throws { + guard !syncClient.isRegistered(account: account) else { return } + + let result = await onSign(syncClient.getMessage(account: account)) + + switch result { + case .signed(let signature): + try await syncClient.register(account: account, signature: signature) + logger.debug("Sync pushSubscriptions store registered and initialized") + case .rejected: + throw PushError.registerSignatureRejected + } + } +} diff --git a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift index 40855c14f..ee6b5c6ca 100644 --- a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift +++ b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift @@ -1,30 +1,28 @@ import Foundation import Combine -import WalletConnectNetworking -import WalletConnectEcho public class WalletPushClient { private var publishers = Set() - private let deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber + /// publishes new subscriptions + public var newSubscriptionPublisher: AnyPublisher { + return pushStorage.newSubscriptionPublisher + } - public var deleteSubscriptionPublisher: AnyPublisher { - deletePushSubscriptionSubscriber.deleteSubscriptionPublisher + public var subscriptionErrorPublisher: AnyPublisher { + return pushSubscribeResponseSubscriber.subscriptionErrorPublisher } - /// publishes new subscriptions - public var subscriptionPublisher: AnyPublisher, Never> { - return pushSubscribeResponseSubscriber.subscriptionPublisher + public var deleteSubscriptionPublisher: AnyPublisher { + return pushStorage.deleteSubscriptionPublisher } public var subscriptionsPublisher: AnyPublisher<[PushSubscription], Never> { - return pushSubscriptionsObserver.subscriptionsPublisher + return pushStorage.subscriptionsPublisher } - private let pushSubscriptionsObserver: PushSubscriptionsObserver - public var requestPublisher: AnyPublisher { notifyProposeSubscriber.requestPublisher } @@ -43,11 +41,13 @@ public class WalletPushClient { public let logger: ConsoleLogging private let echoClient: EchoClient + private let pushStorage: PushStorage + private let pushSyncService: PushSyncService private let pushMessageSubscriber: PushMessageSubscriber - private let subscriptionsProvider: SubscriptionsProvider private let pushMessagesDatabase: PushMessagesDatabase private let resubscribeService: PushResubscribeService private let pushSubscribeResponseSubscriber: PushSubscribeResponseSubscriber + private let deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber private let notifyUpdateRequester: NotifyUpdateRequester private let notifyUpdateResponseSubscriber: NotifyUpdateResponseSubscriber private let notifyProposeResponder: NotifyProposeResponder @@ -57,34 +57,40 @@ public class WalletPushClient { kms: KeyManagementServiceProtocol, echoClient: EchoClient, pushMessageSubscriber: PushMessageSubscriber, - subscriptionsProvider: SubscriptionsProvider, + pushStorage: PushStorage, + pushSyncService: PushSyncService, pushMessagesDatabase: PushMessagesDatabase, deletePushSubscriptionService: DeletePushSubscriptionService, resubscribeService: PushResubscribeService, - pushSubscriptionsObserver: PushSubscriptionsObserver, pushSubscribeRequester: PushSubscribeRequester, pushSubscribeResponseSubscriber: PushSubscribeResponseSubscriber, + deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber, notifyUpdateRequester: NotifyUpdateRequester, notifyUpdateResponseSubscriber: NotifyUpdateResponseSubscriber, notifyProposeResponder: NotifyProposeResponder, - notifyProposeSubscriber: NotifyProposeSubscriber, - deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber + notifyProposeSubscriber: NotifyProposeSubscriber ) { self.logger = logger self.echoClient = echoClient self.pushMessageSubscriber = pushMessageSubscriber - self.subscriptionsProvider = subscriptionsProvider + self.pushStorage = pushStorage + self.pushSyncService = pushSyncService self.pushMessagesDatabase = pushMessagesDatabase self.deletePushSubscriptionService = deletePushSubscriptionService self.resubscribeService = resubscribeService - self.pushSubscriptionsObserver = pushSubscriptionsObserver self.pushSubscribeRequester = pushSubscribeRequester self.pushSubscribeResponseSubscriber = pushSubscribeResponseSubscriber + self.deletePushSubscriptionSubscriber = deletePushSubscriptionSubscriber self.notifyUpdateRequester = notifyUpdateRequester self.notifyUpdateResponseSubscriber = notifyUpdateResponseSubscriber self.notifyProposeResponder = notifyProposeResponder self.notifyProposeSubscriber = notifyProposeSubscriber - self.deletePushSubscriptionSubscriber = deletePushSubscriptionSubscriber + } + + public func enableSync(account: Account, onSign: @escaping SigningCallback) async throws { + try await pushSyncService.registerIfNeeded(account: account, onSign: onSign) + try await pushStorage.initialize(account: account) + try await pushStorage.setupSubscriptions(account: account) } public func subscribe(metadata: AppMetadata, account: Account, onSign: @escaping SigningCallback) async throws { @@ -104,7 +110,7 @@ public class WalletPushClient { } public func getActiveSubscriptions() -> [PushSubscription] { - subscriptionsProvider.getActiveSubscriptions() + return pushStorage.getSubscriptions() } public func getMessageHistory(topic: String) -> [PushMessageRecord] { @@ -124,7 +130,6 @@ public class WalletPushClient { } } - #if targetEnvironment(simulator) extension WalletPushClient { public func register(deviceToken: String) async throws { diff --git a/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift b/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift index 667e28311..f57d7b7ef 100644 --- a/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift +++ b/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift @@ -1,11 +1,8 @@ import Foundation -import WalletConnectUtils -import WalletConnectEcho -import WalletConnectIdentity public struct WalletPushClientFactory { - public static func create(networkInteractor: NetworkInteracting, pairingRegisterer: PairingRegisterer, echoClient: EchoClient) -> WalletPushClient { + public static func create(networkInteractor: NetworkInteracting, pairingRegisterer: PairingRegisterer, echoClient: EchoClient, syncClient: SyncClient) -> WalletPushClient { let logger = ConsoleLogger(suffix: "🔔",loggingLevel: .debug) let keyValueStorage = UserDefaults.standard let keyserverURL = URL(string: "https://keys.walletconnect.com")! @@ -20,7 +17,8 @@ public struct WalletPushClientFactory { groupKeychainStorage: groupKeychainService, networkInteractor: networkInteractor, pairingRegisterer: pairingRegisterer, - echoClient: echoClient + echoClient: echoClient, + syncClient: syncClient ) } @@ -32,58 +30,55 @@ public struct WalletPushClientFactory { groupKeychainStorage: KeychainStorageProtocol, networkInteractor: NetworkInteracting, pairingRegisterer: PairingRegisterer, - echoClient: EchoClient + echoClient: EchoClient, + syncClient: SyncClient ) -> WalletPushClient { let kms = KeyManagementService(keychain: keychainStorage) - let history = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage) - - let subscriptionStore = CodableStore(defaults: keyValueStorage, identifier: PushStorageIdntifiers.pushSubscription) - + let subscriptionStore: SyncStore = SyncStoreFactory.create(name: PushStorageIdntifiers.pushSubscription, syncClient: syncClient, storage: keyValueStorage) + let subscriptionStoreDelegate = PushSubscriptionStoreDelegate(networkingInteractor: networkInteractor, kms: kms) + let pushStorage = PushStorage(subscriptionStore: subscriptionStore, subscriptionStoreDelegate: subscriptionStoreDelegate) + let pushSyncService = PushSyncService(syncClient: syncClient, logger: logger) let identityClient = IdentityClientFactory.create(keyserver: keyserverURL, keychain: keychainStorage, logger: logger) - let pushMessagesRecordsStore = CodableStore(defaults: keyValueStorage, identifier: PushStorageIdntifiers.pushMessagesRecords) let pushMessagesDatabase = PushMessagesDatabase(store: pushMessagesRecordsStore) let pushMessageSubscriber = PushMessageSubscriber(networkingInteractor: networkInteractor, pushMessagesDatabase: pushMessagesDatabase, logger: logger) - let subscriptionProvider = SubscriptionsProvider(store: subscriptionStore) - let deletePushSubscriptionService = DeletePushSubscriptionService(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushSubscriptionStore: subscriptionStore, pushMessagesDatabase: pushMessagesDatabase) - let resubscribeService = PushResubscribeService(networkInteractor: networkInteractor, subscriptionsStorage: subscriptionStore) - let pushSubscriptionsObserver = PushSubscriptionsObserver(store: subscriptionStore) - + let deletePushSubscriptionService = DeletePushSubscriptionService(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushStorage: pushStorage, pushMessagesDatabase: pushMessagesDatabase) + let resubscribeService = PushResubscribeService(networkInteractor: networkInteractor, pushStorage: pushStorage) let dappsMetadataStore = CodableStore(defaults: keyValueStorage, identifier: PushStorageIdntifiers.dappsMetadataStore) let subscriptionScopeProvider = SubscriptionScopeProvider() let pushSubscribeRequester = PushSubscribeRequester(keyserverURL: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, logger: logger, kms: kms, subscriptionScopeProvider: subscriptionScopeProvider, dappsMetadataStore: dappsMetadataStore) - let pushSubscribeResponseSubscriber = PushSubscribeResponseSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, groupKeychainStorage: groupKeychainStorage, subscriptionsStore: subscriptionStore, dappsMetadataStore: dappsMetadataStore, subscriptionScopeProvider: subscriptionScopeProvider) + let pushSubscribeResponseSubscriber = PushSubscribeResponseSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, groupKeychainStorage: groupKeychainStorage, pushStorage: pushStorage, dappsMetadataStore: dappsMetadataStore, subscriptionScopeProvider: subscriptionScopeProvider) - let notifyUpdateRequester = NotifyUpdateRequester(keyserverURL: keyserverURL, identityClient: identityClient, networkingInteractor: networkInteractor, logger: logger, subscriptionsStore: subscriptionStore) + let notifyUpdateRequester = NotifyUpdateRequester(keyserverURL: keyserverURL, identityClient: identityClient, networkingInteractor: networkInteractor, logger: logger, pushStorage: pushStorage) - let notifyUpdateResponseSubscriber = NotifyUpdateResponseSubscriber(networkingInteractor: networkInteractor, logger: logger, subscriptionScopeProvider: subscriptionScopeProvider, subscriptionsStore: subscriptionStore) - let notifyProposeResponder = NotifyProposeResponder(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushSubscribeRequester: pushSubscribeRequester, rpcHistory: history, pushSubscribeResponseSubscriber: pushSubscribeResponseSubscriber) + let notifyUpdateResponseSubscriber = NotifyUpdateResponseSubscriber(networkingInteractor: networkInteractor, logger: logger, subscriptionScopeProvider: subscriptionScopeProvider, pushStorage: pushStorage) + let notifyProposeResponder = NotifyProposeResponder(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushStorage: pushStorage, pushSubscribeRequester: pushSubscribeRequester, rpcHistory: history, pushSubscribeResponseSubscriber: pushSubscribeResponseSubscriber) - let notifyProposeSubscriber = NotifyProposeSubscriber(networkingInteractor: networkInteractor, subscriptionsStore: subscriptionStore, logger: logger, pairingRegisterer: pairingRegisterer) + let notifyProposeSubscriber = NotifyProposeSubscriber(networkingInteractor: networkInteractor, pushStorage: pushStorage, logger: logger, pairingRegisterer: pairingRegisterer) - let deletePushSubscriptionSubscriber = DeletePushSubscriptionSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushSubscriptionStore: subscriptionStore) + let deletePushSubscriptionSubscriber = DeletePushSubscriptionSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushStorage: pushStorage) return WalletPushClient( logger: logger, kms: kms, echoClient: echoClient, pushMessageSubscriber: pushMessageSubscriber, - subscriptionsProvider: subscriptionProvider, + pushStorage: pushStorage, + pushSyncService: pushSyncService, pushMessagesDatabase: pushMessagesDatabase, deletePushSubscriptionService: deletePushSubscriptionService, resubscribeService: resubscribeService, - pushSubscriptionsObserver: pushSubscriptionsObserver, pushSubscribeRequester: pushSubscribeRequester, pushSubscribeResponseSubscriber: pushSubscribeResponseSubscriber, + deletePushSubscriptionSubscriber: deletePushSubscriptionSubscriber, notifyUpdateRequester: notifyUpdateRequester, notifyUpdateResponseSubscriber: notifyUpdateResponseSubscriber, notifyProposeResponder: notifyProposeResponder, - notifyProposeSubscriber: notifyProposeSubscriber, - deletePushSubscriptionSubscriber: deletePushSubscriptionSubscriber + notifyProposeSubscriber: notifyProposeSubscriber ) } } diff --git a/Sources/WalletConnectPush/ProtocolMethods/PushDeleteProtocolMethod.swift b/Sources/WalletConnectPush/ProtocolMethods/PushDeleteProtocolMethod.swift index 1aee08d1f..dd90fe7e2 100644 --- a/Sources/WalletConnectPush/ProtocolMethods/PushDeleteProtocolMethod.swift +++ b/Sources/WalletConnectPush/ProtocolMethods/PushDeleteProtocolMethod.swift @@ -1,5 +1,4 @@ import Foundation -import WalletConnectPairing struct PushDeleteProtocolMethod: ProtocolMethod { let method: String = "wc_pushDelete" diff --git a/Sources/WalletConnectPush/ProtocolMethods/PushMessageProtocolMethod.swift b/Sources/WalletConnectPush/ProtocolMethods/PushMessageProtocolMethod.swift index 808817290..6345d1dc8 100644 --- a/Sources/WalletConnectPush/ProtocolMethods/PushMessageProtocolMethod.swift +++ b/Sources/WalletConnectPush/ProtocolMethods/PushMessageProtocolMethod.swift @@ -1,5 +1,4 @@ import Foundation -import WalletConnectPairing struct PushMessageProtocolMethod: ProtocolMethod { let method: String = "wc_pushMessage" diff --git a/Sources/WalletConnectPush/Push.swift b/Sources/WalletConnectPush/Push.swift index 6f217deb0..2c16e81e5 100644 --- a/Sources/WalletConnectPush/Push.swift +++ b/Sources/WalletConnectPush/Push.swift @@ -1,14 +1,12 @@ import Foundation -import WalletConnectNetworking -import WalletConnectPairing -import WalletConnectEcho public class Push { public static var dapp: DappPushClient = { return DappPushClientFactory.create( metadata: Pair.metadata, - networkInteractor: Networking.interactor + networkInteractor: Networking.interactor, + syncClient: Sync.instance ) }() @@ -20,7 +18,8 @@ public class Push { return WalletPushClientFactory.create( networkInteractor: Networking.interactor, pairingRegisterer: Pair.registerer, - echoClient: Echo.instance + echoClient: Echo.instance, + syncClient: Sync.instance ) }() diff --git a/Sources/WalletConnectPush/PushConfig.swift b/Sources/WalletConnectPush/PushConfig.swift index 2e290caa5..9c955803a 100644 --- a/Sources/WalletConnectPush/PushConfig.swift +++ b/Sources/WalletConnectPush/PushConfig.swift @@ -1,5 +1,4 @@ import Foundation -import WalletConnectEcho extension Push { struct Config { diff --git a/Sources/WalletConnectPush/PushImports.swift b/Sources/WalletConnectPush/PushImports.swift index 98325f10c..44b2c7e9c 100644 --- a/Sources/WalletConnectPush/PushImports.swift +++ b/Sources/WalletConnectPush/PushImports.swift @@ -1,5 +1,6 @@ #if !CocoaPods @_exported import WalletConnectPairing -@_exported import WalletConnectSigner +@_exported import WalletConnectEcho @_exported import WalletConnectIdentity +@_exported import WalletConnectSync #endif diff --git a/Sources/WalletConnectPush/PushStorageIdntifiers.swift b/Sources/WalletConnectPush/PushStorageIdntifiers.swift index 57ac3611c..bc627ce69 100644 --- a/Sources/WalletConnectPush/PushStorageIdntifiers.swift +++ b/Sources/WalletConnectPush/PushStorageIdntifiers.swift @@ -1,7 +1,8 @@ import Foundation enum PushStorageIdntifiers { - static let pushSubscription = "com.walletconnect.sdk.pushSbscription" + static let pushSubscription = "com.walletconnect.notify.pushSubscription" + static let pushMessagesRecords = "com.walletconnect.sdk.pushMessagesRecords" static let dappsMetadataStore = "com.walletconnect.sdk.dappsMetadataStore" } diff --git a/Sources/WalletConnectPush/Types/PushError.swift b/Sources/WalletConnectPush/Types/PushError.swift index 5912bc917..82c16c00e 100644 --- a/Sources/WalletConnectPush/Types/PushError.swift +++ b/Sources/WalletConnectPush/Types/PushError.swift @@ -4,6 +4,7 @@ public enum PushError: Codable, Equatable, Error { case userRejeted case userHasExistingSubscription case methodUnsupported + case registerSignatureRejected } extension PushError: Reason { @@ -12,8 +13,12 @@ extension PushError: Reason { switch code { case Self.userRejeted.code: self = .userRejeted + case Self.userHasExistingSubscription.code: + self = .userHasExistingSubscription case Self.methodUnsupported.code: self = .methodUnsupported + case Self.registerSignatureRejected.code: + self = .registerSignatureRejected default: return nil } @@ -26,6 +31,8 @@ extension PushError: Reason { return 5000 case .userHasExistingSubscription: return 6001 + case .registerSignatureRejected: + return 1501 } } @@ -37,6 +44,8 @@ extension PushError: Reason { return "Push request rejected" case .userHasExistingSubscription: return "User Has Existing Subscription" + case .registerSignatureRejected: + return "Register signature rejected" } } diff --git a/Sources/WalletConnectPush/Types/PushRequest.swift b/Sources/WalletConnectPush/Types/PushRequest.swift index a27b87a9b..d1c4c8a92 100644 --- a/Sources/WalletConnectPush/Types/PushRequest.swift +++ b/Sources/WalletConnectPush/Types/PushRequest.swift @@ -1,5 +1,3 @@ - import Foundation -import WalletConnectPairing public typealias PushRequest = (id: RPCID, account: Account, metadata: AppMetadata) diff --git a/Sources/WalletConnectPush/Types/PushSubscription.swift b/Sources/WalletConnectPush/Types/PushSubscription.swift index bfd1fd1eb..756d33781 100644 --- a/Sources/WalletConnectPush/Types/PushSubscription.swift +++ b/Sources/WalletConnectPush/Types/PushSubscription.swift @@ -1,14 +1,17 @@ import Foundation -import WalletConnectUtils -import WalletConnectPairing -public struct PushSubscription: Codable, Equatable { +public struct PushSubscription: DatabaseObject { public let topic: String public let account: Account public let relay: RelayProtocolOptions public let metadata: AppMetadata public let scope: [String: ScopeValue] public let expiry: Date + public let symKey: String + + public var databaseId: String { + return topic + } } public struct ScopeValue: Codable, Equatable { diff --git a/Sources/WalletConnectPush/Types/WebDidDoc.swift b/Sources/WalletConnectPush/Types/WebDidDoc.swift index 28f301773..aca4975f0 100644 --- a/Sources/WalletConnectPush/Types/WebDidDoc.swift +++ b/Sources/WalletConnectPush/Types/WebDidDoc.swift @@ -1,4 +1,3 @@ - import Foundation // MARK: - WebDidDoc diff --git a/Sources/WalletConnectRelay/PackageConfig.json b/Sources/WalletConnectRelay/PackageConfig.json index 1aca540e3..eb68828e1 100644 --- a/Sources/WalletConnectRelay/PackageConfig.json +++ b/Sources/WalletConnectRelay/PackageConfig.json @@ -1 +1 @@ -{"version": "1.6.10"} +{"version": "1.6.11"} diff --git a/Sources/WalletConnectRelay/Relay.swift b/Sources/WalletConnectRelay/Relay.swift index 9e118558e..38390fbbe 100644 --- a/Sources/WalletConnectRelay/Relay.swift +++ b/Sources/WalletConnectRelay/Relay.swift @@ -12,7 +12,7 @@ public class Relay { guard let config = Relay.config else { fatalError("Error - you must call Relay.configure(_:) before accessing the shared instance.") } - return RelayClient( + return RelayClientFactory.create( relayHost: config.relayHost, projectId: config.projectId, socketFactory: config.socketFactory, diff --git a/Sources/WalletConnectRelay/RelayClient.swift b/Sources/WalletConnectRelay/RelayClient.swift index a84c4c264..ddb9cf91b 100644 --- a/Sources/WalletConnectRelay/RelayClient.swift +++ b/Sources/WalletConnectRelay/RelayClient.swift @@ -47,12 +47,12 @@ public final class RelayClient { init( dispatcher: Dispatching, logger: ConsoleLogging, - keyValueStorage: KeyValueStorage, + rpcHistory: RPCHistory, clientIdStorage: ClientIdStoring ) { self.logger = logger self.dispatcher = dispatcher - self.rpcHistory = RPCHistoryFactory.createForRelay(keyValueStorage: keyValueStorage) + self.rpcHistory = rpcHistory self.clientIdStorage = clientIdStorage setUpBindings() } @@ -63,41 +63,6 @@ public final class RelayClient { } } - /// Instantiates Relay Client - /// - Parameters: - /// - relayHost: proxy server host that your application will use to connect to Relay Network. If you register your project at `www.walletconnect.com` you can use `relay.walletconnect.com` - /// - projectId: an optional parameter used to access the public WalletConnect infrastructure. Go to `www.walletconnect.com` for info. - /// - keyValueStorage: by default WalletConnect SDK will store sequences in UserDefaults - /// - socketConnectionType: socket connection type - /// - logger: logger instance - public convenience init( - relayHost: String, - projectId: String, - keyValueStorage: KeyValueStorage = UserDefaults.standard, - keychainStorage: KeychainStorageProtocol = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk"), - socketFactory: WebSocketFactory, - socketConnectionType: SocketConnectionType = .automatic, - logger: ConsoleLogging = ConsoleLogger(loggingLevel: .debug) - ) { - let clientIdStorage = ClientIdStorage(keychain: keychainStorage) - let socketAuthenticator = ClientIdAuthenticator( - clientIdStorage: clientIdStorage, - url: "wss://\(relayHost)" - ) - let relayUrlFactory = RelayUrlFactory( - relayHost: relayHost, - projectId: projectId, - socketAuthenticator: socketAuthenticator - ) - let dispatcher = Dispatcher( - socketFactory: socketFactory, - relayUrlFactory: relayUrlFactory, - socketConnectionType: socketConnectionType, - logger: logger - ) - self.init(dispatcher: dispatcher, logger: logger, keyValueStorage: keyValueStorage, clientIdStorage: clientIdStorage) - } - /// Connects web socket /// /// Use this method for manual socket connection only diff --git a/Sources/WalletConnectRelay/RelayClientFactory.swift b/Sources/WalletConnectRelay/RelayClientFactory.swift new file mode 100644 index 000000000..0d6ce5a2c --- /dev/null +++ b/Sources/WalletConnectRelay/RelayClientFactory.swift @@ -0,0 +1,64 @@ +// +import Foundation + + +public struct RelayClientFactory { + + public static func create( + relayHost: String, + projectId: String, + socketFactory: WebSocketFactory, + socketConnectionType: SocketConnectionType + ) -> RelayClient { + + let keyValueStorage = UserDefaults.standard + + let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk") + + let logger = ConsoleLogger(suffix: "🚄" ,loggingLevel: .debug) + + return RelayClientFactory.create( + relayHost: relayHost, + projectId: projectId, + keyValueStorage: keyValueStorage, + keychainStorage: keychainStorage, + socketFactory: socketFactory, + socketConnectionType: socketConnectionType, + logger: logger + ) + } + + + public static func create( + relayHost: String, + projectId: String, + keyValueStorage: KeyValueStorage, + keychainStorage: KeychainStorageProtocol, + socketFactory: WebSocketFactory, + socketConnectionType: SocketConnectionType = .automatic, + logger: ConsoleLogging + ) -> RelayClient { + + let clientIdStorage = ClientIdStorage(keychain: keychainStorage) + + let socketAuthenticator = ClientIdAuthenticator( + clientIdStorage: clientIdStorage, + url: "wss://\(relayHost)" + ) + let relayUrlFactory = RelayUrlFactory( + relayHost: relayHost, + projectId: projectId, + socketAuthenticator: socketAuthenticator + ) + let dispatcher = Dispatcher( + socketFactory: socketFactory, + relayUrlFactory: relayUrlFactory, + socketConnectionType: socketConnectionType, + logger: logger + ) + + let rpcHistory = RPCHistoryFactory.createForRelay(keyValueStorage: keyValueStorage) + + return RelayClient(dispatcher: dispatcher, logger: logger, rpcHistory: rpcHistory, clientIdStorage: clientIdStorage) + } +} diff --git a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift index acb93af8b..7594c3a9f 100644 --- a/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift +++ b/Sources/WalletConnectSign/Engine/Common/ApproveEngine.swift @@ -56,8 +56,7 @@ final class ApproveEngine { setupResponseErrorSubscriptions() } - func approveProposal(proposerPubKey: String, validating sessionNamespaces: [String: SessionNamespace]) async throws { - + func approveProposal(proposerPubKey: String, validating sessionNamespaces: [String: SessionNamespace], sessionProperties: [String: String]? = nil) async throws { guard let payload = try proposalPayloadsStore.get(key: proposerPubKey) else { throw Errors.wrongRequestParams } @@ -87,9 +86,19 @@ final class ApproveEngine { let result = SessionType.ProposeResponse(relay: relay, responderPublicKey: selfPublicKey.hexRepresentation) let response = RPCResponse(id: payload.id, result: result) - async let proposeResponse: () = networkingInteractor.respond(topic: payload.topic, response: response, protocolMethod: SessionProposeProtocolMethod()) + async let proposeResponse: () = networkingInteractor.respond( + topic: payload.topic, + response: response, + protocolMethod: SessionProposeProtocolMethod() + ) - async let settleRequest: () = settle(topic: sessionTopic, proposal: proposal, namespaces: sessionNamespaces, pairingTopic: pairingTopic) + async let settleRequest: () = settle( + topic: sessionTopic, + proposal: proposal, + namespaces: sessionNamespaces, + sessionProperties: sessionProperties, + pairingTopic: pairingTopic + ) _ = try await [proposeResponse, settleRequest] @@ -108,7 +117,7 @@ final class ApproveEngine { // TODO: Delete pairing if inactive } - func settle(topic: String, proposal: SessionProposal, namespaces: [String: SessionNamespace], pairingTopic: String) async throws { + func settle(topic: String, proposal: SessionProposal, namespaces: [String: SessionNamespace], sessionProperties: [String: String]? = nil, pairingTopic: String) async throws { guard let agreementKeys = kms.getAgreementSecret(for: topic) else { throw Errors.agreementMissingOrInvalid } @@ -129,7 +138,9 @@ final class ApproveEngine { relay: relay, controller: selfParticipant, namespaces: namespaces, - expiry: Int64(expiry)) + sessionProperties: sessionProperties, + expiry: Int64(expiry) + ) let session = WCSession( topic: topic, @@ -139,7 +150,8 @@ final class ApproveEngine { peerParticipant: proposal.proposer, settleParams: settleParams, requiredNamespaces: proposal.requiredNamespaces, - acknowledged: false) + acknowledged: false + ) logger.debug("Sending session settle request") diff --git a/Sources/WalletConnectSign/Namespace.swift b/Sources/WalletConnectSign/Namespace.swift index 43ba9518a..6de73e1cc 100644 --- a/Sources/WalletConnectSign/Namespace.swift +++ b/Sources/WalletConnectSign/Namespace.swift @@ -265,11 +265,8 @@ public enum AutoNamespaces { guard !sessionMethods.isEmpty else { return } - + let sessionEvents = Set(proposalNamespace.events).intersection(Set(events)) - guard !sessionEvents.isEmpty else { - return - } let sessionNamespace = SessionNamespace( chains: sessionChains, @@ -305,9 +302,6 @@ public enum AutoNamespaces { } let sessionEvents = Set(proposalNamespace.events).intersection(Set(events)) - guard !sessionEvents.isEmpty else { - return - } let sessionNamespace = SessionNamespace( chains: Set([Blockchain(namespace: network, reference: chain)!]), diff --git a/Sources/WalletConnectSign/Session.swift b/Sources/WalletConnectSign/Session.swift index ef3a2205f..0d6ca96a8 100644 --- a/Sources/WalletConnectSign/Session.swift +++ b/Sources/WalletConnectSign/Session.swift @@ -7,7 +7,9 @@ public struct Session { public let topic: String public let pairingTopic: String public let peer: AppMetadata + public let requiredNamespaces: [String: ProposalNamespace] public let namespaces: [String: SessionNamespace] + public let sessionProperties: [String: String]? public let expiryDate: Date public static var defaultTimeToLive: Int64 { WCSession.defaultTimeToLive diff --git a/Sources/WalletConnectSign/Sign/SignClient.swift b/Sources/WalletConnectSign/Sign/SignClient.swift index 7d18d08ed..11830442c 100644 --- a/Sources/WalletConnectSign/Sign/SignClient.swift +++ b/Sources/WalletConnectSign/Sign/SignClient.swift @@ -239,8 +239,8 @@ public final class SignClient: SignClientProtocol { /// - Parameters: /// - proposalId: Session Proposal id /// - namespaces: namespaces for given session, needs to contain at least required namespaces proposed by dApp. - public func approve(proposalId: String, namespaces: [String: SessionNamespace]) async throws { - try await approveEngine.approveProposal(proposerPubKey: proposalId, validating: namespaces) + public func approve(proposalId: String, namespaces: [String: SessionNamespace], sessionProperties: [String: String]? = nil) async throws { + try await approveEngine.approveProposal(proposerPubKey: proposalId, validating: namespaces, sessionProperties: sessionProperties) } /// For the wallet to reject a session proposal. diff --git a/Sources/WalletConnectSign/Sign/SignClientProtocol.swift b/Sources/WalletConnectSign/Sign/SignClientProtocol.swift index 397a59bf4..f23857f04 100644 --- a/Sources/WalletConnectSign/Sign/SignClientProtocol.swift +++ b/Sources/WalletConnectSign/Sign/SignClientProtocol.swift @@ -13,7 +13,7 @@ public protocol SignClientProtocol { func connect(requiredNamespaces: [String: ProposalNamespace], optionalNamespaces: [String: ProposalNamespace]?, sessionProperties: [String: String]?, topic: String) async throws func request(params: Request) async throws - func approve(proposalId: String, namespaces: [String: SessionNamespace]) async throws + func approve(proposalId: String, namespaces: [String: SessionNamespace], sessionProperties: [String: String]?) async throws func reject(proposalId: String, reason: RejectionReason) async throws func update(topic: String, namespaces: [String: SessionNamespace]) async throws func extend(topic: String) async throws diff --git a/Sources/WalletConnectSign/Types/Session/SessionType.swift b/Sources/WalletConnectSign/Types/Session/SessionType.swift index 2731e448c..cc838f084 100644 --- a/Sources/WalletConnectSign/Types/Session/SessionType.swift +++ b/Sources/WalletConnectSign/Types/Session/SessionType.swift @@ -16,6 +16,7 @@ internal enum SessionType { let relay: RelayProtocolOptions let controller: Participant let namespaces: [String: SessionNamespace] + let sessionProperties: [String: String]? let expiry: Int64 } diff --git a/Sources/WalletConnectSign/Types/Session/WCSession.swift b/Sources/WalletConnectSign/Types/Session/WCSession.swift index 6778f66b5..6d4d2fa7a 100644 --- a/Sources/WalletConnectSign/Types/Session/WCSession.swift +++ b/Sources/WalletConnectSign/Types/Session/WCSession.swift @@ -18,6 +18,7 @@ struct WCSession: SequenceObject, Equatable { private(set) var timestamp: Date private(set) var namespaces: [String: SessionNamespace] private(set) var requiredNamespaces: [String: ProposalNamespace] + private(set) var sessionProperties: [String: String]? static var defaultTimeToLive: Int64 { Int64(7*Time.day) @@ -43,6 +44,7 @@ struct WCSession: SequenceObject, Equatable { self.selfParticipant = selfParticipant self.peerParticipant = peerParticipant self.namespaces = settleParams.namespaces + self.sessionProperties = settleParams.sessionProperties self.requiredNamespaces = requiredNamespaces self.acknowledged = acknowledged self.expiryDate = Date(timeIntervalSince1970: TimeInterval(settleParams.expiry)) @@ -58,6 +60,7 @@ struct WCSession: SequenceObject, Equatable { selfParticipant: Participant, peerParticipant: Participant, namespaces: [String: SessionNamespace], + sessionProperties: [String: String], requiredNamespaces: [String: ProposalNamespace], events: Set, accounts: Set, @@ -72,6 +75,7 @@ struct WCSession: SequenceObject, Equatable { self.selfParticipant = selfParticipant self.peerParticipant = peerParticipant self.namespaces = namespaces + self.sessionProperties = sessionProperties self.requiredNamespaces = requiredNamespaces self.acknowledged = acknowledged self.expiryDate = Date(timeIntervalSince1970: TimeInterval(expiry)) @@ -165,8 +169,11 @@ struct WCSession: SequenceObject, Equatable { topic: topic, pairingTopic: pairingTopic, peer: peerParticipant.metadata, + requiredNamespaces: requiredNamespaces, namespaces: namespaces, - expiryDate: expiryDate) + sessionProperties: sessionProperties, + expiryDate: expiryDate + ) } } @@ -175,7 +182,7 @@ struct WCSession: SequenceObject, Equatable { extension WCSession { enum CodingKeys: String, CodingKey { - case topic, pairingTopic, relay, selfParticipant, peerParticipant, expiryDate, acknowledged, controller, namespaces, timestamp, requiredNamespaces + case topic, pairingTopic, relay, selfParticipant, peerParticipant, expiryDate, acknowledged, controller, namespaces, timestamp, requiredNamespaces, sessionProperties } init(from decoder: Decoder) throws { @@ -186,6 +193,7 @@ extension WCSession { self.selfParticipant = try container.decode(Participant.self, forKey: .selfParticipant) self.peerParticipant = try container.decode(Participant.self, forKey: .peerParticipant) self.namespaces = try container.decode([String: SessionNamespace].self, forKey: .namespaces) + self.sessionProperties = try container.decode([String: String]?.self, forKey: .sessionProperties) self.acknowledged = try container.decode(Bool.self, forKey: .acknowledged) self.expiryDate = try container.decode(Date.self, forKey: .expiryDate) self.timestamp = try container.decode(Date.self, forKey: .timestamp) @@ -202,6 +210,7 @@ extension WCSession { try container.encode(selfParticipant, forKey: .selfParticipant) try container.encode(peerParticipant, forKey: .peerParticipant) try container.encode(namespaces, forKey: .namespaces) + try container.encode(sessionProperties, forKey: .sessionProperties) try container.encode(acknowledged, forKey: .acknowledged) try container.encode(expiryDate, forKey: .expiryDate) try container.encode(timestamp, forKey: .timestamp) diff --git a/Sources/WalletConnectSync/Stores/SyncStore.swift b/Sources/WalletConnectSync/Stores/SyncStore.swift index 57b5320b4..3ad9f6629 100644 --- a/Sources/WalletConnectSync/Stores/SyncStore.swift +++ b/Sources/WalletConnectSync/Stores/SyncStore.swift @@ -60,6 +60,10 @@ public final class SyncStore { return objectStore.getAll() } + public func get(for id: String) -> Object? { + return getAll().first(where: { $0.databaseId == id }) + } + public func set(object: Object, for account: Account) async throws { let record = try indexStore.getRecord(account: account, name: name) @@ -75,6 +79,14 @@ public final class SyncStore { try await syncClient.delete(account: account, store: record.store, key: id) } } + + public func delete(id: String) async throws { + guard let result = objectStore.find(id: id) else { + return + } + let record = try indexStore.getRecord(topic: result.key) + try await delete(id: id, for: record.account) + } } private extension SyncStore { diff --git a/Sources/WalletConnectUtils/KeyedDatabase.swift b/Sources/WalletConnectUtils/KeyedDatabase.swift index 0deffcc8d..6218c6ffc 100644 --- a/Sources/WalletConnectUtils/KeyedDatabase.swift +++ b/Sources/WalletConnectUtils/KeyedDatabase.swift @@ -42,6 +42,15 @@ public class KeyedDatabase where Element: DatabaseObject { return index[key]?[id] } + public func find(id: String) -> (key: String, element: Element)? { + guard + let value = index.first(where: { $0.value[id] != nil }), + let element = value.value[id] + else { return nil } + + return (value.key, element) + } + @discardableResult public func set(elements: [Element], for key: String) -> Bool { var map = index[key] ?? [:] diff --git a/Sources/Web3Inbox/PushClientProxy/PushClientProxy.swift b/Sources/Web3Inbox/PushClientProxy/PushClientProxy.swift index 2a84f4bd4..ff8c3e47d 100644 --- a/Sources/Web3Inbox/PushClientProxy/PushClientProxy.swift +++ b/Sources/Web3Inbox/PushClientProxy/PushClientProxy.swift @@ -16,6 +16,8 @@ final class PushClientProxy { guard let event = PushWebViewEvent(rawValue: request.method) else { throw Errors.unregisteredMethod } + // TODO: Handle register event + switch event { case .approve: let params = try parse(ApproveRequest.self, params: request.params) diff --git a/Sources/Web3Inbox/PushClientProxy/PushClientRequestSubscriber.swift b/Sources/Web3Inbox/PushClientProxy/PushClientRequestSubscriber.swift index 7cb07c45a..921322154 100644 --- a/Sources/Web3Inbox/PushClientProxy/PushClientRequestSubscriber.swift +++ b/Sources/Web3Inbox/PushClientProxy/PushClientRequestSubscriber.swift @@ -31,15 +31,12 @@ final class PushClientRequestSubscriber { handle(event: .pushDelete, params: record) }.store(in: &publishers) - client.subscriptionPublisher.sink { [unowned self] record in - switch record { - case .success(let subscription): - handle(event: .pushSubscription, params: subscription) - case .failure: - //TODO - handle error - break + client.newSubscriptionPublisher.sink { [unowned self] subscription in + handle(event: .pushSubscription, params: subscription) + }.store(in: &publishers) - } + client.deleteSubscriptionPublisher.sink { [unowned self] topic in + handle(event: .pushDelete, params: topic) }.store(in: &publishers) client.updateSubscriptionPublisher.sink { [unowned self] record in diff --git a/Sources/Web3Wallet/Web3WalletClient.swift b/Sources/Web3Wallet/Web3WalletClient.swift index 0f117f0a8..757b15e7f 100644 --- a/Sources/Web3Wallet/Web3WalletClient.swift +++ b/Sources/Web3Wallet/Web3WalletClient.swift @@ -87,8 +87,8 @@ public class Web3WalletClient { /// - Parameters: /// - proposalId: Session Proposal id /// - namespaces: namespaces for given session, needs to contain at least required namespaces proposed by dApp. - public func approve(proposalId: String, namespaces: [String: SessionNamespace]) async throws { - try await signClient.approve(proposalId: proposalId, namespaces: namespaces) + public func approve(proposalId: String, namespaces: [String: SessionNamespace], sessionProperties: [String: String]? = nil) async throws { + try await signClient.approve(proposalId: proposalId, namespaces: namespaces, sessionProperties: sessionProperties) } /// For the wallet to reject a session proposal. diff --git a/Tests/RelayerTests/RelayClientTests.swift b/Tests/RelayerTests/RelayClientTests.swift index 7a6841a84..5905e6e70 100644 --- a/Tests/RelayerTests/RelayClientTests.swift +++ b/Tests/RelayerTests/RelayClientTests.swift @@ -15,7 +15,8 @@ final class RelayClientTests: XCTestCase { dispatcher = DispatcherMock() let logger = ConsoleLogger() let clientIdStorage = ClientIdStorageMock() - sut = RelayClient(dispatcher: dispatcher, logger: logger, keyValueStorage: RuntimeKeyValueStorage(), clientIdStorage: clientIdStorage) + let rpcHistory = RPCHistoryFactory.createForRelay(keyValueStorage: RuntimeKeyValueStorage()) + sut = RelayClient(dispatcher: dispatcher, logger: logger, rpcHistory: rpcHistory, clientIdStorage: clientIdStorage) } override func tearDown() { diff --git a/Tests/WalletConnectModalTests/Mocks/ModalSheetInteractorMock.swift b/Tests/WalletConnectModalTests/Mocks/ModalSheetInteractorMock.swift index 738eb9d35..5d8607e5c 100644 --- a/Tests/WalletConnectModalTests/Mocks/ModalSheetInteractorMock.swift +++ b/Tests/WalletConnectModalTests/Mocks/ModalSheetInteractorMock.swift @@ -28,7 +28,7 @@ final class ModalSheetInteractorMock: ModalSheetInteractor { } var sessionSettlePublisher: AnyPublisher { - Result.Publisher(Session(topic: "", pairingTopic: "", peer: .stub(), namespaces: [:], expiryDate: Date())) + Result.Publisher(Session(topic: "", pairingTopic: "", peer: .stub(), requiredNamespaces: [:], namespaces: [:], sessionProperties: nil, expiryDate: Date())) .eraseToAnyPublisher() } diff --git a/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift b/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift index af452092d..5dd2e2d58 100644 --- a/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift +++ b/Tests/WalletConnectSignTests/AutoNamespacesValidationTests.swift @@ -799,4 +799,204 @@ final class AutoNamespacesValidationTests: XCTestCase { ) ) } + + func testAutoNamespacesSameChainEmptyOptinalEvents() { + let accounts = [ + Account(blockchain: Blockchain("eip155:1")!, address: "0x57f48fAFeC1d76B27e3f29b8d277b6218CDE6092")! + ] + let requiredNamespaces = [ + "eip155": ProposalNamespace( + chains: [Blockchain("eip155:1")!], + methods: ["personal_sign", "eth_sendTransaction"], + events: ["chainChanged", "accountsChanged"]) + ] + let optionalNamespaces = [ + "eip155": ProposalNamespace( + chains: [Blockchain("eip155:1")!], + methods: ["eth_signTypedData_v4", "eth_signTransaction", "eth_signTypedData", "eth_sign"], + events: [] + ) + ] + let sessionProposal = Session.Proposal( + id: "", + pairingTopic: "", + proposer: AppMetadata(name: "", description: "", url: "", icons: []), + requiredNamespaces: requiredNamespaces, + optionalNamespaces: optionalNamespaces, + sessionProperties: nil, + proposal: SessionProposal(relays: [], proposer: Participant(publicKey: "", metadata: AppMetadata(name: "", description: "", url: "", icons: [])), requiredNamespaces: [:], optionalNamespaces: [:], sessionProperties: [:]) + ) + let sessionNamespaces = try! AutoNamespaces.build( + sessionProposal: sessionProposal, + chains: [Blockchain("eip155:1")!], + methods: ["personal_sign", "eth_sendTransaction", "eth_signTypedData_v4", "eth_signTransaction", "eth_signTypedData", "eth_sign"], + events: ["chainChanged", "accountsChanged"], + accounts: accounts + ) + let expectedNamespaces: [String: SessionNamespace] = [ + "eip155": SessionNamespace( + chains: [Blockchain("eip155:1")!], + accounts: Set([ + Account(blockchain: Blockchain("eip155:1")!, address: "0x57f48fAFeC1d76B27e3f29b8d277b6218CDE6092")! + ]), + methods: ["eth_signTypedData_v4", "eth_signTransaction", "eth_signTypedData", "eth_sign", "personal_sign", "eth_sendTransaction"], + events: ["chainChanged", "accountsChanged"] + ) + ] + XCTAssertEqual(sessionNamespaces, expectedNamespaces) + } + + func testAutoNamespacesSameChainEmptyRequiredEvents() { + let accounts = [ + Account(blockchain: Blockchain("eip155:1")!, address: "0x57f48fAFeC1d76B27e3f29b8d277b6218CDE6092")! + ] + let requiredNamespaces = [ + "eip155": ProposalNamespace( + chains: [Blockchain("eip155:1")!], + methods: ["personal_sign", "eth_sendTransaction"], + events: [] + ) + ] + let optionalNamespaces = [ + "eip155": ProposalNamespace( + chains: [Blockchain("eip155:1")!], + methods: ["eth_signTypedData_v4", "eth_signTransaction", "eth_signTypedData", "eth_sign"], + events: ["chainChanged", "accountsChanged"] + ) + ] + let sessionProposal = Session.Proposal( + id: "", + pairingTopic: "", + proposer: AppMetadata(name: "", description: "", url: "", icons: []), + requiredNamespaces: requiredNamespaces, + optionalNamespaces: optionalNamespaces, + sessionProperties: nil, + proposal: SessionProposal(relays: [], proposer: Participant(publicKey: "", metadata: AppMetadata(name: "", description: "", url: "", icons: [])), requiredNamespaces: [:], optionalNamespaces: [:], sessionProperties: [:]) + ) + let sessionNamespaces = try! AutoNamespaces.build( + sessionProposal: sessionProposal, + chains: [Blockchain("eip155:1")!], + methods: ["personal_sign", "eth_sendTransaction", "eth_signTypedData_v4", "eth_signTransaction", "eth_signTypedData", "eth_sign"], + events: ["chainChanged", "accountsChanged"], + accounts: accounts + ) + let expectedNamespaces: [String: SessionNamespace] = [ + "eip155": SessionNamespace( + chains: [Blockchain("eip155:1")!], + accounts: Set([ + Account(blockchain: Blockchain("eip155:1")!, address: "0x57f48fAFeC1d76B27e3f29b8d277b6218CDE6092")! + ]), + methods: ["eth_signTypedData_v4", "eth_signTransaction", "eth_signTypedData", "eth_sign", "personal_sign", "eth_sendTransaction"], + events: ["chainChanged", "accountsChanged"] + ) + ] + XCTAssertEqual(sessionNamespaces, expectedNamespaces) + } + + func testAutoNamespacesSameChainEmptyEvents() { + let accounts = [ + Account(blockchain: Blockchain("eip155:1")!, address: "0x57f48fAFeC1d76B27e3f29b8d277b6218CDE6092")! + ] + let requiredNamespaces = [ + "eip155": ProposalNamespace( + chains: [Blockchain("eip155:1")!], + methods: ["personal_sign", "eth_sendTransaction"], + events: [] + ) + ] + let optionalNamespaces = [ + "eip155": ProposalNamespace( + chains: [Blockchain("eip155:1")!], + methods: ["eth_signTypedData_v4", "eth_signTransaction", "eth_signTypedData", "eth_sign"], + events: [] + ) + ] + let sessionProposal = Session.Proposal( + id: "", + pairingTopic: "", + proposer: AppMetadata(name: "", description: "", url: "", icons: []), + requiredNamespaces: requiredNamespaces, + optionalNamespaces: optionalNamespaces, + sessionProperties: nil, + proposal: SessionProposal(relays: [], proposer: Participant(publicKey: "", metadata: AppMetadata(name: "", description: "", url: "", icons: [])), requiredNamespaces: [:], optionalNamespaces: [:], sessionProperties: [:]) + ) + let sessionNamespaces = try! AutoNamespaces.build( + sessionProposal: sessionProposal, + chains: [Blockchain("eip155:1")!], + methods: ["personal_sign", "eth_sendTransaction", "eth_signTypedData_v4", "eth_signTransaction", "eth_signTypedData", "eth_sign"], + events: ["chainChanged", "accountsChanged"], + accounts: accounts + ) + let expectedNamespaces: [String: SessionNamespace] = [ + "eip155": SessionNamespace( + chains: [Blockchain("eip155:1")!], + accounts: Set([ + Account(blockchain: Blockchain("eip155:1")!, address: "0x57f48fAFeC1d76B27e3f29b8d277b6218CDE6092")! + ]), + methods: ["eth_signTypedData_v4", "eth_signTransaction", "eth_signTypedData", "eth_sign", "personal_sign", "eth_sendTransaction"], + events: [] + ) + ] + XCTAssertEqual(sessionNamespaces, expectedNamespaces) + } + + func testAutoNamespacesDifferentChainEmptyOptinalEvents() { + let accounts = [ + Account(blockchain: Blockchain("eip155:1")!, address: "0x57f48fAFeC1d76B27e3f29b8d277b6218CDE6092")!, + Account(blockchain: Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!, address: "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")! + ] + let requiredNamespaces = [ + "eip155": ProposalNamespace( + chains: [Blockchain("eip155:1")!], + methods: ["personal_sign", "eth_sendTransaction"], + events: ["chainChanged", "accountsChanged"]) + ] + let optionalNamespaces = [ + "eip155": ProposalNamespace( + chains: [Blockchain("eip155:1")!], + methods: ["eth_signTypedData_v4", "eth_signTransaction", "eth_signTypedData", "eth_sign"], + events: [] + ), + "solana": ProposalNamespace( + chains: [Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!], + methods: ["solana_signMessage"], + events: [] + ) + ] + let sessionProposal = Session.Proposal( + id: "", + pairingTopic: "", + proposer: AppMetadata(name: "", description: "", url: "", icons: []), + requiredNamespaces: requiredNamespaces, + optionalNamespaces: optionalNamespaces, + sessionProperties: nil, + proposal: SessionProposal(relays: [], proposer: Participant(publicKey: "", metadata: AppMetadata(name: "", description: "", url: "", icons: [])), requiredNamespaces: [:], optionalNamespaces: [:], sessionProperties: [:]) + ) + let sessionNamespaces = try! AutoNamespaces.build( + sessionProposal: sessionProposal, + chains: [Blockchain("eip155:1")!, Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!], + methods: ["personal_sign", "eth_sendTransaction", "eth_signTypedData_v4", "eth_signTransaction", "eth_signTypedData", "eth_sign", "solana_signMessage", "solana_signMessage"], + events: ["chainChanged", "accountsChanged"], + accounts: accounts + ) + let expectedNamespaces: [String: SessionNamespace] = [ + "eip155": SessionNamespace( + chains: [Blockchain("eip155:1")!], + accounts: Set([ + Account(blockchain: Blockchain("eip155:1")!, address: "0x57f48fAFeC1d76B27e3f29b8d277b6218CDE6092")! + ]), + methods: ["eth_signTypedData_v4", "eth_signTransaction", "eth_signTypedData", "eth_sign", "personal_sign", "eth_sendTransaction"], + events: ["chainChanged", "accountsChanged"] + ), + "solana": SessionNamespace( + chains: [Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!], + accounts: Set([ + Account(blockchain: Blockchain("solana:4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!, address: "4sGjMW1sUnHzSxGspuhpqLDx6wiyjNtZ")!, + ]), + methods: ["solana_signMessage"], + events: [] + ) + ] + XCTAssertEqual(sessionNamespaces, expectedNamespaces) + } } diff --git a/Tests/WalletConnectSignTests/Stub/Session+Stub.swift b/Tests/WalletConnectSignTests/Stub/Session+Stub.swift index e60b778ec..37fcfdef9 100644 --- a/Tests/WalletConnectSignTests/Stub/Session+Stub.swift +++ b/Tests/WalletConnectSignTests/Stub/Session+Stub.swift @@ -9,6 +9,7 @@ extension WCSession { expiryDate: Date = Date.distantFuture, selfPrivateKey: AgreementPrivateKey = AgreementPrivateKey(), namespaces: [String: SessionNamespace] = [:], + sessionProperties: [String: String] = [:], requiredNamespaces: [String: ProposalNamespace] = [:], acknowledged: Bool = true, timestamp: Date = Date() @@ -25,6 +26,7 @@ extension WCSession { selfParticipant: Participant.stub(publicKey: selfKey), peerParticipant: Participant.stub(publicKey: peerKey), namespaces: namespaces, + sessionProperties: sessionProperties, requiredNamespaces: requiredNamespaces, events: [], accounts: Account.stubSet(), @@ -45,6 +47,7 @@ extension SessionType.SettleParams { relay: RelayProtocolOptions.stub(), controller: Participant.stub(), namespaces: SessionNamespace.stubDictionary(), + sessionProperties: nil, expiry: Int64(Date.distantFuture.timeIntervalSince1970)) } } diff --git a/Tests/Web3WalletTests/Mocks/AuthClientMock.swift b/Tests/Web3WalletTests/Mocks/AuthClientMock.swift index 535ace60a..302d40a53 100644 --- a/Tests/Web3WalletTests/Mocks/AuthClientMock.swift +++ b/Tests/Web3WalletTests/Mocks/AuthClientMock.swift @@ -22,6 +22,7 @@ final class AuthClientMock: AuthClientProtocol { return AuthRequest( id: .left(""), + topic: "", payload: AuthPayload(requestParams: requestParams, iat: "") ) } diff --git a/Tests/Web3WalletTests/Mocks/SignClientMock.swift b/Tests/Web3WalletTests/Mocks/SignClientMock.swift index fec7b51c4..721af0e46 100644 --- a/Tests/Web3WalletTests/Mocks/SignClientMock.swift +++ b/Tests/Web3WalletTests/Mocks/SignClientMock.swift @@ -4,7 +4,6 @@ import Combine @testable import WalletConnectSign final class SignClientMock: SignClientProtocol { - var approveCalled = false var rejectCalled = false var updateCalled = false @@ -41,7 +40,7 @@ final class SignClientMock: SignClientProtocol { } var sessionsPublisher: AnyPublisher<[WalletConnectSign.Session], Never> { - return Result.Publisher([WalletConnectSign.Session(topic: "", pairingTopic: "", peer: metadata, namespaces: [:], expiryDate: Date())]) + return Result.Publisher([WalletConnectSign.Session(topic: "", pairingTopic: "", peer: metadata, requiredNamespaces: [:], namespaces: [:], sessionProperties: nil, expiryDate: Date())]) .eraseToAnyPublisher() } @@ -51,7 +50,7 @@ final class SignClientMock: SignClientProtocol { } var sessionSettlePublisher: AnyPublisher { - return Result.Publisher(Session(topic: "", pairingTopic: "", peer: metadata, namespaces: [:], expiryDate: Date())) + return Result.Publisher(Session(topic: "", pairingTopic: "", peer: metadata, requiredNamespaces: [:], namespaces: [:], sessionProperties: nil, expiryDate: Date())) .eraseToAnyPublisher() } @@ -80,7 +79,7 @@ final class SignClientMock: SignClientProtocol { .eraseToAnyPublisher() } - func approve(proposalId: String, namespaces: [String : WalletConnectSign.SessionNamespace]) async throws { + func approve(proposalId: String, namespaces: [String : WalletConnectSign.SessionNamespace], sessionProperties: [String : String]? = nil) async throws { approveCalled = true } @@ -113,7 +112,7 @@ final class SignClientMock: SignClientProtocol { } func getSessions() -> [WalletConnectSign.Session] { - return [WalletConnectSign.Session(topic: "", pairingTopic: "", peer: metadata, namespaces: [:], expiryDate: Date())] + return [WalletConnectSign.Session(topic: "", pairingTopic: "", peer: metadata, requiredNamespaces: [:], namespaces: [:], sessionProperties: nil, expiryDate: Date())] } func getPendingRequests(topic: String?) -> [WalletConnectSign.Request] { diff --git a/WalletConnectSwiftV2.podspec b/WalletConnectSwiftV2.podspec index 6b3b759a6..c2f80eb8f 100644 --- a/WalletConnectSwiftV2.podspec +++ b/WalletConnectSwiftV2.podspec @@ -75,6 +75,14 @@ Pod::Spec.new do |spec| spec.default_subspecs = 'WalletConnect' spec.subspec 'WalletConnect' do |ss| + ss.source_files = 'Sources/Web3Wallet/**/*.{h,m,swift}' + ss.dependency 'WalletConnectSwiftV2/WalletConnectSign' + ss.dependency 'WalletConnectSwiftV2/WalletConnectAuth' + ss.dependency 'WalletConnectSwiftV2/WalletConnectEcho' + ss.dependency 'WalletConnectSwiftV2/WalletConnectVerify' + end + + spec.subspec 'WalletConnectSign' do |ss| ss.source_files = 'Sources/WalletConnectSign/**/*.{h,m,swift}' ss.dependency 'WalletConnectSwiftV2/WalletConnectPairing' ss.dependency 'WalletConnectSwiftV2/WalletConnectVerify' @@ -86,14 +94,6 @@ Pod::Spec.new do |spec| ss.dependency 'WalletConnectSwiftV2/WalletConnectSigner' ss.dependency 'WalletConnectSwiftV2/WalletConnectVerify' end - - spec.subspec 'Web3Wallet' do |ss| - ss.source_files = 'Sources/Web3Wallet/**/*.{h,m,swift}' - ss.dependency 'WalletConnectSwiftV2/WalletConnect' - ss.dependency 'WalletConnectSwiftV2/WalletConnectAuth' - ss.dependency 'WalletConnectSwiftV2/WalletConnectEcho' - ss.dependency 'WalletConnectSwiftV2/WalletConnectVerify' - end spec.subspec 'WalletConnectVerify' do |ss| ss.source_files = 'Sources/WalletConnectVerify/**/*.{h,m,swift}'