diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c4822fd3..ef5f81953 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,7 +69,7 @@ jobs: - name: Run integration tests if: matrix.type == 'integration-tests' shell: bash - run: make integration_tests RELAY_HOST=relay.walletconnect.com PROJECT_ID=${{ secrets.PROJECT_ID }} CAST_HOST=cast.walletconnect.com GM_DAPP_PROJECT_ID=${{ secrets.GM_DAPP_PROJECT_ID }} GM_DAPP_PROJECT_SECRET=${{ secrets.GM_DAPP_PROJECT_SECRET }} JS_CLIENT_API_HOST=test-automation-api.walletconnect.com + run: make integration_tests RELAY_HOST=relay.walletconnect.com PROJECT_ID=${{ secrets.PROJECT_ID }} CAST_HOST=notify.walletconnect.com GM_DAPP_PROJECT_ID=${{ secrets.GM_DAPP_PROJECT_ID }} GM_DAPP_PROJECT_SECRET=${{ secrets.GM_DAPP_PROJECT_SECRET }} JS_CLIENT_API_HOST=test-automation-api.walletconnect.com # Relay Integration tests - name: Run Relay integration tests diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/NotifyTests.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/NotifyTests.xcscheme new file mode 100644 index 000000000..b94c9e014 --- /dev/null +++ b/.swiftpm/xcode/xcshareddata/xcschemes/NotifyTests.xcscheme @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectPush.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectNotify.xcscheme similarity index 55% rename from Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectPush.xcscheme rename to .swiftpm/xcode/xcshareddata/xcschemes/WalletConnectNotify.xcscheme index c779cb9d1..9b01e10d1 100644 --- a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectPush.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectNotify.xcscheme @@ -1,7 +1,7 @@ + LastUpgradeVersion = "1430" + version = "1.7"> @@ -14,24 +14,10 @@ buildForAnalyzing = "YES"> - - - - + BlueprintIdentifier = "WalletConnectNotify" + BuildableName = "WalletConnectNotify" + BlueprintName = "WalletConnectNotify" + ReferencedContainer = "container:"> @@ -40,19 +26,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> - - - - - - + shouldUseLaunchSchemeArgsEnv = "YES" + shouldAutocreateTestPlan = "YES"> + BlueprintIdentifier = "WalletConnectNotify" + BuildableName = "WalletConnectNotify" + BlueprintName = "WalletConnectNotify" + ReferencedContainer = "container:"> diff --git a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectEcho.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectPush.xcscheme similarity index 76% rename from Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectEcho.xcscheme rename to .swiftpm/xcode/xcshareddata/xcschemes/WalletConnectPush.xcscheme index 341890963..43b6f9bc1 100644 --- a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnectEcho.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnectPush.xcscheme @@ -1,7 +1,7 @@ + LastUpgradeVersion = "1430" + version = "1.7"> @@ -14,10 +14,10 @@ buildForAnalyzing = "YES"> + BlueprintIdentifier = "WalletConnectPush" + BuildableName = "WalletConnectPush" + BlueprintName = "WalletConnectPush" + ReferencedContainer = "container:"> @@ -26,9 +26,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "YES"> - - + shouldUseLaunchSchemeArgsEnv = "YES" + shouldAutocreateTestPlan = "YES"> + BlueprintIdentifier = "WalletConnectPush" + BuildableName = "WalletConnectPush" + BlueprintName = "WalletConnectPush" + ReferencedContainer = "container:"> diff --git a/Configuration.xcconfig b/Configuration.xcconfig index a0bd4e19c..27c07ad29 100644 --- a/Configuration.xcconfig +++ b/Configuration.xcconfig @@ -14,4 +14,4 @@ RELAY_HOST = relay.walletconnect.com // WALLETAPP_SENTRY_DSN = WALLETAPP_SENTRY_DSN -CAST_HOST = cast.walletconnect.com +CAST_HOST = notify.walletconnect.com diff --git a/Example/DApp/Info.plist b/Example/DApp/Info.plist index ec622dae1..92cddbe74 100644 --- a/Example/DApp/Info.plist +++ b/Example/DApp/Info.plist @@ -2,6 +2,15 @@ + LSApplicationQueriesSchemes + + metamask + trust + safe + zerion + rainbow + spot + CFBundleVersion 7 CFBundleShortVersionString diff --git a/Example/DApp/Sign/Accounts/AccountsViewController.swift b/Example/DApp/Sign/Accounts/AccountsViewController.swift index ad29010ae..631a0725a 100644 --- a/Example/DApp/Sign/Accounts/AccountsViewController.swift +++ b/Example/DApp/Sign/Accounts/AccountsViewController.swift @@ -1,6 +1,6 @@ import UIKit import WalletConnectSign -import WalletConnectPush +import WalletConnectNotify import Combine struct AccountDetails { @@ -14,7 +14,7 @@ final class AccountsViewController: UIViewController, UITableViewDataSource, UIT let session: Session var accountsDetails: [AccountDetails] = [] var onDisconnect: (() -> Void)? - var pushSubscription: PushSubscription? + var notifySubscription: NotifySubscription? private var publishers = [AnyCancellable]() private let accountsView: AccountsView = { @@ -54,21 +54,7 @@ final class AccountsViewController: UIViewController, UITableViewDataSource, UIT } } } - - func proposePushSubscription() { - let account = session.namespaces.values.first!.accounts.first! - - Task(priority: .high){ try! await Push.dapp.propose(account: account, topic: session.pairingTopic)} - Push.dapp.proposalResponsePublisher.sink { result in - switch result { - case .success(let subscription): - self.pushSubscription = subscription - case .failure(let error): - print(error) - } - }.store(in: &publishers) - } - + @objc private func disconnect() { Task { diff --git a/Example/DApp/Sign/SignCoordinator.swift b/Example/DApp/Sign/SignCoordinator.swift index 55344a902..763cd2463 100644 --- a/Example/DApp/Sign/SignCoordinator.swift +++ b/Example/DApp/Sign/SignCoordinator.swift @@ -45,13 +45,6 @@ final class SignCoordinator { presentResponse(for: response) }.store(in: &publishers) - Sign.instance.sessionSettlePublisher - .receive(on: DispatchQueue.main) - .sink { [unowned self] session in - let vc = showAccountsScreen(session) - vc.proposePushSubscription() - }.store(in: &publishers) - if let session = Sign.instance.getSessions().first { _ = showAccountsScreen(session) } else { diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index 92c706fdd..a0a35346e 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -39,7 +39,7 @@ 8487A9442A836C2A0003D5AF /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 8487A9432A836C2A0003D5AF /* Sentry */; }; 8487A9462A836C3F0003D5AF /* Sentry in Frameworks */ = {isa = PBXBuildFile; productRef = 8487A9452A836C3F0003D5AF /* Sentry */; }; 8487A9482A83AD680003D5AF /* LoggingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8487A9472A83AD680003D5AF /* LoggingService.swift */; }; - 849D7A93292E2169006A2BD4 /* PushTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849D7A92292E2169006A2BD4 /* PushTests.swift */; }; + 849D7A93292E2169006A2BD4 /* NotifyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849D7A92292E2169006A2BD4 /* NotifyTests.swift */; }; 84A6E3C32A386BBC008A0571 /* Publisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84A6E3C22A386BBC008A0571 /* Publisher.swift */; }; 84AA01DB28CF0CD7005D48D8 /* XCTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84AA01DA28CF0CD7005D48D8 /* XCTest.swift */; }; 84B8154E2991099000FAD54E /* BuildConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84B8154D2991099000FAD54E /* BuildConfiguration.swift */; }; @@ -68,14 +68,6 @@ 84DDB4ED28ABB663003D66ED /* WalletConnectAuth in Frameworks */ = {isa = PBXBuildFile; productRef = 84DDB4EC28ABB663003D66ED /* WalletConnectAuth */; }; 84E6B84A29787A8000428BAF /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E6B84929787A8000428BAF /* NotificationService.swift */; }; 84E6B84E29787A8000428BAF /* PNDecryptionService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 84E6B84729787A8000428BAF /* PNDecryptionService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 84E6B85429787AAE00428BAF /* WalletConnectPush in Frameworks */ = {isa = PBXBuildFile; productRef = 84E6B85329787AAE00428BAF /* WalletConnectPush */; }; - 84E6B8582981624F00428BAF /* PushRequestModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E6B8572981624F00428BAF /* PushRequestModule.swift */; }; - 84E6B85B298162EF00428BAF /* PushRequestPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E6B85A298162EF00428BAF /* PushRequestPresenter.swift */; }; - 84E6B85D298162F700428BAF /* PushRequestRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E6B85C298162F700428BAF /* PushRequestRouter.swift */; }; - 84E6B85F2981630000428BAF /* PushRequestInteractor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E6B85E2981630000428BAF /* PushRequestInteractor.swift */; }; - 84E6B8612981630C00428BAF /* PushRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E6B8602981630C00428BAF /* PushRequestView.swift */; }; - 84E6B86329816A7900428BAF /* WalletConnectPush in Frameworks */ = {isa = PBXBuildFile; productRef = 84E6B86229816A7900428BAF /* WalletConnectPush */; }; - 84E6B8652981720400428BAF /* WalletConnectPush in Frameworks */ = {isa = PBXBuildFile; productRef = 84E6B8642981720400428BAF /* WalletConnectPush */; }; 84FE684628ACDB4700C893FF /* RequestParams.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84FE684528ACDB4700C893FF /* RequestParams.swift */; }; A507BE1A29E8032E0038EF70 /* EIP55Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A507BE1929E8032E0038EF70 /* EIP55Tests.swift */; }; A50C036528AAD32200FE72D3 /* ClientDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A50C036428AAD32200FE72D3 /* ClientDelegate.swift */; }; @@ -182,6 +174,10 @@ A5A8E47E293A1CFE00FEB97D /* DefaultSignerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59CF4F5292F83D50031A42F /* DefaultSignerFactory.swift */; }; A5A8E47F293A1D0000FEB97D /* DefaultSocketFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5629AEF2877F73000094373 /* DefaultSocketFactory.swift */; }; A5A8E480293A1D0000FEB97D /* DefaultSignerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59CF4F5292F83D50031A42F /* DefaultSignerFactory.swift */; }; + A5B6C0F12A6EAB0800927332 /* WalletConnectNotify in Frameworks */ = {isa = PBXBuildFile; productRef = A5B6C0F02A6EAB0800927332 /* WalletConnectNotify */; }; + A5B6C0F32A6EAB1700927332 /* WalletConnectNotify in Frameworks */ = {isa = PBXBuildFile; productRef = A5B6C0F22A6EAB1700927332 /* WalletConnectNotify */; }; + A5B6C0F52A6EAB2800927332 /* WalletConnectNotify in Frameworks */ = {isa = PBXBuildFile; productRef = A5B6C0F42A6EAB2800927332 /* WalletConnectNotify */; }; + A5B6C0F72A6EAB3200927332 /* WalletConnectNotify in Frameworks */ = {isa = PBXBuildFile; productRef = A5B6C0F62A6EAB3200927332 /* WalletConnectNotify */; }; A5BB7F9F28B69B7100707FC6 /* SignCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BB7F9E28B69B7100707FC6 /* SignCoordinator.swift */; }; A5BB7FA128B69F3400707FC6 /* AuthCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5BB7FA028B69F3400707FC6 /* AuthCoordinator.swift */; }; A5BB7FA328B6A50400707FC6 /* WalletConnectAuth in Frameworks */ = {isa = PBXBuildFile; productRef = A5BB7FA228B6A50400707FC6 /* WalletConnectAuth */; }; @@ -395,9 +391,8 @@ 8487A9472A83AD680003D5AF /* LoggingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LoggingService.swift; sourceTree = ""; }; 849A4F18298281E300E61ACE /* WalletAppRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = WalletAppRelease.entitlements; sourceTree = ""; }; 849A4F19298281F100E61ACE /* PNDecryptionServiceRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = PNDecryptionServiceRelease.entitlements; sourceTree = ""; }; - 849D7A92292E2169006A2BD4 /* PushTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushTests.swift; sourceTree = ""; }; + 849D7A92292E2169006A2BD4 /* NotifyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotifyTests.swift; sourceTree = ""; }; 84A6E3C22A386BBC008A0571 /* Publisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Publisher.swift; sourceTree = ""; }; - 84A6E3C42A38A5A3008A0571 /* NotifyTests.xctestplan */ = {isa = PBXFileReference; lastKnownFileType = text; name = NotifyTests.xctestplan; path = ../NotifyTests.xctestplan; sourceTree = ""; }; 84AA01DA28CF0CD7005D48D8 /* XCTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XCTest.swift; sourceTree = ""; }; 84B8154D2991099000FAD54E /* BuildConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildConfiguration.swift; sourceTree = ""; }; 84B8154F2991217900FAD54E /* PushMessagesModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushMessagesModule.swift; sourceTree = ""; }; @@ -428,11 +423,6 @@ 84E6B84729787A8000428BAF /* PNDecryptionService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = PNDecryptionService.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 84E6B84929787A8000428BAF /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = ""; }; 84E6B84B29787A8000428BAF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 84E6B8572981624F00428BAF /* PushRequestModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushRequestModule.swift; sourceTree = ""; }; - 84E6B85A298162EF00428BAF /* PushRequestPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushRequestPresenter.swift; sourceTree = ""; }; - 84E6B85C298162F700428BAF /* PushRequestRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushRequestRouter.swift; sourceTree = ""; }; - 84E6B85E2981630000428BAF /* PushRequestInteractor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushRequestInteractor.swift; sourceTree = ""; }; - 84E6B8602981630C00428BAF /* PushRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushRequestView.swift; sourceTree = ""; }; 84F568C1279582D200D0A289 /* Signer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Signer.swift; sourceTree = ""; }; 84F568C32795832A00D0A289 /* EthereumTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EthereumTransaction.swift; sourceTree = ""; }; 84FE684528ACDB4700C893FF /* RequestParams.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestParams.swift; sourceTree = ""; }; @@ -650,9 +640,9 @@ files = ( 8448F1D427E4726F0000B866 /* WalletConnect in Frameworks */, CF25F28B2A432488009C7E49 /* WalletConnectModal in Frameworks */, + A5B6C0F12A6EAB0800927332 /* WalletConnectNotify in Frameworks */, A54195A52934E83F0035AD19 /* Web3 in Frameworks */, 8487A9442A836C2A0003D5AF /* Sentry in Frameworks */, - 84E6B8652981720400428BAF /* WalletConnectPush in Frameworks */, A5D85228286333E300DAF5C3 /* Starscream in Frameworks */, A573C53929EC365000E3CBFD /* HDWalletKit in Frameworks */, A5BB7FA328B6A50400707FC6 /* WalletConnectAuth in Frameworks */, @@ -663,7 +653,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 84E6B86329816A7900428BAF /* WalletConnectPush in Frameworks */, + A5B6C0F72A6EAB3200927332 /* WalletConnectNotify in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -697,10 +687,10 @@ A561C80529DFCD4500DF540D /* WalletConnectSync in Frameworks */, A5E03DF52864651200888481 /* Starscream in Frameworks */, A50DF19D2A25084A0036EA6C /* WalletConnectHistory in Frameworks */, - 847CF3AF28E3141700F1D760 /* WalletConnectPush in Frameworks */, A5C8BE85292FE20B006CC85C /* Web3 in Frameworks */, 84DDB4ED28ABB663003D66ED /* WalletConnectAuth in Frameworks */, C5DD5BE1294E09E3008FD3A4 /* Web3Wallet in Frameworks */, + A5B6C0F32A6EAB1700927332 /* WalletConnectNotify in Frameworks */, A573C53B29EC365800E3CBFD /* HDWalletKit in Frameworks */, A5E03E01286466EA00888481 /* WalletConnectChat in Frameworks */, ); @@ -717,8 +707,8 @@ C5B2F7052970573D000DBA0E /* SolanaSwift in Frameworks */, 8487A9462A836C3F0003D5AF /* Sentry in Frameworks */, C55D349929630D440004314A /* Web3Wallet in Frameworks */, - 84E6B85429787AAE00428BAF /* WalletConnectPush in Frameworks */, C56EE255293F569A004840D1 /* Starscream in Frameworks */, + A5B6C0F52A6EAB2800927332 /* WalletConnectNotify in Frameworks */, C56EE27B293F56F8004840D1 /* WalletConnectAuth in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -739,7 +729,6 @@ CFF161B82A69719F00004342 /* WalletConnect-Package.xctestplan */, CF79389D29EDD9DC00441B4F /* RelayIntegrationTests.xctestplan */, 845AA7D929BA1EBA00F33739 /* IntegrationTests.xctestplan */, - 84A6E3C42A38A5A3008A0571 /* NotifyTests.xctestplan */, 845AA7DC29BB424800F33739 /* SmokeTests.xctestplan */, 8487A92E2A7BD2F30003D5AF /* XPlatformProtocolTests.xctestplan */, A5A8E479293A1C4400FEB97D /* Shared */, @@ -872,7 +861,7 @@ 849D7A91292E2115006A2BD4 /* Push */ = { isa = PBXGroup; children = ( - 849D7A92292E2169006A2BD4 /* PushTests.swift */, + 849D7A92292E2169006A2BD4 /* NotifyTests.swift */, 84A6E3C22A386BBC008A0571 /* Publisher.swift */, ); path = Push; @@ -988,18 +977,6 @@ path = PNDecryptionService; sourceTree = ""; }; - 84E6B8592981625A00428BAF /* PushRequest */ = { - isa = PBXGroup; - children = ( - 84E6B8572981624F00428BAF /* PushRequestModule.swift */, - 84E6B85A298162EF00428BAF /* PushRequestPresenter.swift */, - 84E6B85C298162F700428BAF /* PushRequestRouter.swift */, - 84E6B85E2981630000428BAF /* PushRequestInteractor.swift */, - 84E6B8602981630C00428BAF /* PushRequestView.swift */, - ); - path = PushRequest; - sourceTree = ""; - }; A50F3944288005A700064555 /* Types */ = { isa = PBXGroup; children = ( @@ -1583,7 +1560,6 @@ C5F32A2A2954812900A6476E /* ConnectionDetails */, C56EE236293F566A004840D1 /* Scan */, C56EE22A293F5668004840D1 /* Wallet */, - 84E6B8592981625A00428BAF /* PushRequest */, 847BD1E9298A807000076C90 /* Notifications */, 84B815592991217F00FAD54E /* PushMessages */, ); @@ -1843,10 +1819,10 @@ A5D85227286333E300DAF5C3 /* Starscream */, A5BB7FA228B6A50400707FC6 /* WalletConnectAuth */, A54195A42934E83F0035AD19 /* Web3 */, - 84E6B8642981720400428BAF /* WalletConnectPush */, A573C53829EC365000E3CBFD /* HDWalletKit */, CF25F28A2A432488009C7E49 /* WalletConnectModal */, 8487A9432A836C2A0003D5AF /* Sentry */, + A5B6C0F02A6EAB0800927332 /* WalletConnectNotify */, ); productName = DApp; productReference = 84CE641C27981DED00142511 /* DApp.app */; @@ -1866,7 +1842,7 @@ ); name = PNDecryptionService; packageProductDependencies = ( - 84E6B86229816A7900428BAF /* WalletConnectPush */, + A5B6C0F62A6EAB3200927332 /* WalletConnectNotify */, ); productName = PNDecryptionService; productReference = 84E6B84729787A8000428BAF /* PNDecryptionService.appex */; @@ -1935,12 +1911,12 @@ A5E03DFE2864662500888481 /* WalletConnect */, A5E03E00286466EA00888481 /* WalletConnectChat */, 84DDB4EC28ABB663003D66ED /* WalletConnectAuth */, - 847CF3AE28E3141700F1D760 /* WalletConnectPush */, A5C8BE84292FE20B006CC85C /* Web3 */, C5DD5BE0294E09E3008FD3A4 /* Web3Wallet */, A561C80429DFCD4500DF540D /* WalletConnectSync */, A573C53A29EC365800E3CBFD /* HDWalletKit */, A50DF19C2A25084A0036EA6C /* WalletConnectHistory */, + A5B6C0F22A6EAB1700927332 /* WalletConnectNotify */, ); productName = IntegrationTests; productReference = A5E03DED286464DB00888481 /* IntegrationTests.xctest */; @@ -1968,10 +1944,10 @@ C5133A77294125CC00A8314C /* Web3 */, C55D349829630D440004314A /* Web3Wallet */, C5B2F7042970573D000DBA0E /* SolanaSwift */, - 84E6B85329787AAE00428BAF /* WalletConnectPush */, 84536D7329EEBCF0008EA8DB /* Web3Inbox */, A573C53C29EC366500E3CBFD /* HDWalletKit */, 8487A9452A836C3F0003D5AF /* Sentry */, + A5B6C0F42A6EAB2800927332 /* WalletConnectNotify */, ); productName = ChatWallet; productReference = C56EE21B293F55ED004840D1 /* WalletApp.app */; @@ -2289,7 +2265,7 @@ A59CF4F6292F83D50031A42F /* DefaultSignerFactory.swift in Sources */, 847F08012A25DBFF00B2A5A4 /* XPlatformW3WTests.swift in Sources */, A5E03E03286466F400888481 /* ChatTests.swift in Sources */, - 849D7A93292E2169006A2BD4 /* PushTests.swift in Sources */, + 849D7A93292E2169006A2BD4 /* NotifyTests.swift in Sources */, 845B8D8C2934B36C0084A966 /* Account.swift in Sources */, 84D2A66628A4F51E0088AE09 /* AuthTests.swift in Sources */, 84FE684628ACDB4700C893FF /* RequestParams.swift in Sources */, @@ -2316,9 +2292,7 @@ files = ( C5D4603A29687A5700302C7E /* DefaultSocketFactory.swift in Sources */, C53AA4362941251C008EA57C /* DefaultSignerFactory.swift in Sources */, - 84E6B8582981624F00428BAF /* PushRequestModule.swift in Sources */, C55D3480295DD7140004314A /* AuthRequestPresenter.swift in Sources */, - 84E6B85B298162EF00428BAF /* PushRequestPresenter.swift in Sources */, A51811A02A52E83100A52B15 /* SettingsPresenter.swift in Sources */, 847BD1DA2989492500076C90 /* MainRouter.swift in Sources */, C5F32A2E2954814A00A6476E /* ConnectionDetailsRouter.swift in Sources */, @@ -2368,8 +2342,6 @@ A51811A12A52E83100A52B15 /* SettingsRouter.swift in Sources */, C56EE279293F56D7004840D1 /* Color.swift in Sources */, 847BD1E6298A806800076C90 /* NotificationsRouter.swift in Sources */, - 84E6B8612981630C00428BAF /* PushRequestView.swift in Sources */, - 84E6B85F2981630000428BAF /* PushRequestInteractor.swift in Sources */, C55D3483295DD7140004314A /* AuthRequestView.swift in Sources */, C56EE243293F566D004840D1 /* ScanView.swift in Sources */, 84310D05298BC980000C15B6 /* MainInteractor.swift in Sources */, @@ -2406,7 +2378,6 @@ C56EE274293F56D7004840D1 /* SceneViewController.swift in Sources */, 847BD1E5298A806800076C90 /* NotificationsPresenter.swift in Sources */, C55D3496295DFA750004314A /* WelcomeInteractor.swift in Sources */, - 84E6B85D298162F700428BAF /* PushRequestRouter.swift in Sources */, C5B2F6FC297055B0000DBA0E /* SOLSigner.swift in Sources */, A518119F2A52E83100A52B15 /* SettingsModule.swift in Sources */, 8487A9482A83AD680003D5AF /* LoggingService.swift in Sources */, @@ -3194,10 +3165,6 @@ isa = XCSwiftPackageProductDependency; productName = Web3Inbox; }; - 847CF3AE28E3141700F1D760 /* WalletConnectPush */ = { - isa = XCSwiftPackageProductDependency; - productName = WalletConnectPush; - }; 8487A9432A836C2A0003D5AF /* Sentry */ = { isa = XCSwiftPackageProductDependency; package = 8487A9422A836C2A0003D5AF /* XCRemoteSwiftPackageReference "sentry-cocoa" */; @@ -3212,18 +3179,6 @@ isa = XCSwiftPackageProductDependency; productName = WalletConnectAuth; }; - 84E6B85329787AAE00428BAF /* WalletConnectPush */ = { - isa = XCSwiftPackageProductDependency; - productName = WalletConnectPush; - }; - 84E6B86229816A7900428BAF /* WalletConnectPush */ = { - isa = XCSwiftPackageProductDependency; - productName = WalletConnectPush; - }; - 84E6B8642981720400428BAF /* WalletConnectPush */ = { - isa = XCSwiftPackageProductDependency; - productName = WalletConnectPush; - }; A50DF19C2A25084A0036EA6C /* WalletConnectHistory */ = { isa = XCSwiftPackageProductDependency; productName = WalletConnectHistory; @@ -3284,6 +3239,22 @@ package = A5AE354528A1A2AC0059AE8A /* XCRemoteSwiftPackageReference "Web3" */; productName = Web3; }; + A5B6C0F02A6EAB0800927332 /* WalletConnectNotify */ = { + isa = XCSwiftPackageProductDependency; + productName = WalletConnectNotify; + }; + A5B6C0F22A6EAB1700927332 /* WalletConnectNotify */ = { + isa = XCSwiftPackageProductDependency; + productName = WalletConnectNotify; + }; + A5B6C0F42A6EAB2800927332 /* WalletConnectNotify */ = { + isa = XCSwiftPackageProductDependency; + productName = WalletConnectNotify; + }; + A5B6C0F62A6EAB3200927332 /* WalletConnectNotify */ = { + isa = XCSwiftPackageProductDependency; + productName = WalletConnectNotify; + }; A5BB7FA228B6A50400707FC6 /* WalletConnectAuth */ = { isa = XCSwiftPackageProductDependency; productName = WalletConnectAuth; diff --git a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnect.xcscheme b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnect.xcscheme index aa58da974..4f51ebe1b 100644 --- a/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnect.xcscheme +++ b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/WalletConnect.xcscheme @@ -288,6 +288,16 @@ ReferencedContainer = "container:.."> + + + + (PairingClient, AuthClient) { - let logger = ConsoleLogger(suffix: prefix, loggingLevel: .debug) + let logger = ConsoleLogger(prefix: prefix, loggingLevel: .debug) let keyValueStorage = RuntimeKeyValueStorage() let keychain = KeychainStorageMock() let relayClient = RelayClientFactory.create( @@ -207,9 +207,3 @@ final class AuthTests: XCTestCase { wait(for: [responseExpectation], timeout: InputConfig.defaultTimeout) } } - -private struct IATProviderMock: IATProvider { - var iat: String { - return "2022-10-10T23:03:35.700Z" - } -} diff --git a/Example/IntegrationTests/Auth/ENS/ENSResolverTests.swift b/Example/IntegrationTests/Auth/ENS/ENSResolverTests.swift index 8ea3661e8..6cced3613 100644 --- a/Example/IntegrationTests/Auth/ENS/ENSResolverTests.swift +++ b/Example/IntegrationTests/Auth/ENS/ENSResolverTests.swift @@ -7,16 +7,16 @@ class ENSResolverTests: XCTestCase { private let account = Account("eip155:1:0xD02D090F8f99B61D65d8e8876Ea86c2720aB27BC")! private let ens = "web3.eth" -// Note: - removed until RPC server fix -// func testResolveEns() async throws { -// let resolver = ENSResolverFactory(crypto: DefaultCryptoProvider()).create(projectId: InputConfig.projectId) -// let resolved = try await resolver.resolveEns(account: account) -// XCTAssertEqual(resolved, ens) -// } -// -// func testResolveAddress() async throws { -// let resolver = ENSResolverFactory(crypto: DefaultCryptoProvider()).create(projectId: InputConfig.projectId) -// let resolved = try await resolver.resolveAddress(ens: ens, blockchain: account.blockchain) -// XCTAssertEqual(resolved, account) -// } + // Note: - removed until RPC server fix + // func testResolveEns() async throws { + // let resolver = ENSResolverFactory(crypto: DefaultCryptoProvider()).create(projectId: InputConfig.projectId) + // let resolved = try await resolver.resolveEns(account: account) + // XCTAssertEqual(resolved, ens) + // } + // + // func testResolveAddress() async throws { + // let resolver = ENSResolverFactory(crypto: DefaultCryptoProvider()).create(projectId: InputConfig.projectId) + // let resolved = try await resolver.resolveAddress(ens: ens, blockchain: account.blockchain) + // XCTAssertEqual(resolved, account) + // } } diff --git a/Example/IntegrationTests/Chat/ChatTests.swift b/Example/IntegrationTests/Chat/ChatTests.swift index fb71cbd2b..54d2381c8 100644 --- a/Example/IntegrationTests/Chat/ChatTests.swift +++ b/Example/IntegrationTests/Chat/ChatTests.swift @@ -48,7 +48,7 @@ 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 logger = ConsoleLogger(prefix: prefix, loggingLevel: .debug) let keyValueStorage = RuntimeKeyValueStorage() let keychain = KeychainStorageMock() let relayClient = RelayClientFactory.create( diff --git a/Example/IntegrationTests/Chat/RegistryTests.swift b/Example/IntegrationTests/Chat/RegistryTests.swift index 074251e63..a6de83f9e 100644 --- a/Example/IntegrationTests/Chat/RegistryTests.swift +++ b/Example/IntegrationTests/Chat/RegistryTests.swift @@ -32,27 +32,27 @@ final class RegistryTests: XCTestCase { signer = MessageSignerFactory(signerFactory: DefaultSignerFactory()).create(projectId: InputConfig.projectId) } - func testRegisterIdentityAndInviteKey() async throws { - let publicKey = try await sut.registerIdentity(account: account, onSign: onSign) - - let iss = DIDKey(rawData: Data(hex: publicKey)).did(variant: .ED25519) - let resolvedAccount = try await sut.resolveIdentity(iss: iss) - XCTAssertEqual(resolvedAccount, account) - - let recovered = try storage.getIdentityKey(for: account).publicKey.hexRepresentation - XCTAssertEqual(publicKey, recovered) - - let inviteKey = try await sut.registerInvite(account: account) - - let recoveredKey = try storage.getInviteKey(for: account) - XCTAssertEqual(inviteKey, recoveredKey) - - let resolvedKey = try await sut.resolveInvite(account: account) - XCTAssertEqual(inviteKey.did, resolvedKey) - - _ = try await sut.goPrivate(account: account) - try await sut.unregister(account: account, onSign: onSign) - } +// func testRegisterIdentityAndInviteKey() async throws { +// let publicKey = try await sut.registerIdentity(account: account, onSign: onSign) +// +// let iss = DIDKey(rawData: Data(hex: publicKey)).did(variant: .ED25519) +// let resolvedAccount = try await sut.resolveIdentity(iss: iss) +// XCTAssertEqual(resolvedAccount, account) +// +// let recovered = try storage.getIdentityKey(for: account).publicKey.hexRepresentation +// XCTAssertEqual(publicKey, recovered) +// +// let inviteKey = try await sut.registerInvite(account: account) +// +// let recoveredKey = try storage.getInviteKey(for: account) +// XCTAssertEqual(inviteKey, recoveredKey) +// +// let resolvedKey = try await sut.resolveInvite(account: account) +// XCTAssertEqual(inviteKey.did, resolvedKey) +// +// _ = try await sut.goPrivate(account: account) +// try await sut.unregister(account: account, onSign: onSign) +// } } private extension RegistryTests { diff --git a/Example/IntegrationTests/History/HistoryTests.swift b/Example/IntegrationTests/History/HistoryTests.swift index 8667a9224..40d52f5c0 100644 --- a/Example/IntegrationTests/History/HistoryTests.swift +++ b/Example/IntegrationTests/History/HistoryTests.swift @@ -30,7 +30,7 @@ final class HistoryTests: XCTestCase { keyValueStorage: RuntimeKeyValueStorage(), keychainStorage: keychain, socketFactory: DefaultSocketFactory(), - logger: ConsoleLogger(suffix: prefix + " [Relay]", loggingLevel: .debug)) + logger: ConsoleLogger(prefix: prefix + " [Relay]", loggingLevel: .debug)) } private func makeHistoryClient(keychain: KeychainStorageProtocol) -> HistoryNetworkService { diff --git a/Example/IntegrationTests/Pairing/PairingTests.swift b/Example/IntegrationTests/Pairing/PairingTests.swift index 24cdcaf24..6d8005a6c 100644 --- a/Example/IntegrationTests/Pairing/PairingTests.swift +++ b/Example/IntegrationTests/Pairing/PairingTests.swift @@ -1,12 +1,12 @@ import Foundation import XCTest -import WalletConnectUtils +@testable import WalletConnectUtils @testable import WalletConnectKMS import WalletConnectRelay import Combine import WalletConnectNetworking -import WalletConnectEcho -@testable import WalletConnectPush +import WalletConnectPush +@testable import Auth @testable import WalletConnectPairing @testable import WalletConnectSync @testable import WalletConnectHistory @@ -16,20 +16,20 @@ final class PairingTests: XCTestCase { var appPairingClient: PairingClient! var walletPairingClient: PairingClient! - var appPushClient: DappPushClient! - var walletPushClient: WalletPushClient! + var appAuthClient: AuthClient! + var walletAuthClient: AuthClient! var pairingStorage: PairingStorage! private var publishers = [AnyCancellable]() - func makeClientDependencies(prefix: String) -> (PairingClient, NetworkInteracting, SyncClient, KeychainStorageProtocol, KeyValueStorage) { + func makeClientDependencies(prefix: String) -> (PairingClient, NetworkingInteractor, KeychainStorageProtocol, KeyValueStorage) { let keychain = KeychainStorageMock() let keyValueStorage = RuntimeKeyValueStorage() - let relayLogger = ConsoleLogger(suffix: prefix + " [Relay]", loggingLevel: .debug) - let pairingLogger = ConsoleLogger(suffix: prefix + " [Pairing]", loggingLevel: .debug) - let networkingLogger = ConsoleLogger(suffix: prefix + " [Networking]", loggingLevel: .debug) + let relayLogger = ConsoleLogger(prefix: prefix + " [Relay]", loggingLevel: .debug) + let pairingLogger = ConsoleLogger(prefix: prefix + " [Pairing]", loggingLevel: .debug) + let networkingLogger = ConsoleLogger(prefix: prefix + " [Networking]", loggingLevel: .debug) let relayClient = RelayClientFactory.create( relayHost: InputConfig.relayHost, @@ -50,59 +50,55 @@ final class PairingTests: XCTestCase { keyValueStorage: keyValueStorage, 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, syncClient, keychain, keyValueStorage) + return (pairingClient, networkingClient, keychain, keyValueStorage) } func makeDappClients() { let prefix = "🤖 Dapp: " - let (pairingClient, networkingInteractor, syncClient, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix) - let pushLogger = ConsoleLogger(suffix: prefix + " [Push]", loggingLevel: .debug) + let (pairingClient, networkingInteractor, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix) + let notifyLogger = ConsoleLogger(prefix: prefix + " [Notify]", loggingLevel: .debug) appPairingClient = pairingClient - appPushClient = DappPushClientFactory.create(metadata: AppMetadata(name: name, description: "", url: "", icons: [""]), - logger: pushLogger, - keyValueStorage: keyValueStorage, - keychainStorage: keychain, - groupKeychainStorage: KeychainStorageMock(), - networkInteractor: networkingInteractor, - syncClient: syncClient) + + appAuthClient = AuthClientFactory.create( + metadata: AppMetadata(name: name, description: "", url: "", icons: [""]), + projectId: InputConfig.projectId, + crypto: DefaultCryptoProvider(), + logger: notifyLogger, + keyValueStorage: keyValueStorage, + keychainStorage: keychain, + networkingClient: networkingInteractor, + pairingRegisterer: pairingClient, + iatProvider: IATProviderMock()) } func makeWalletClients() { let prefix = "đŸļ Wallet: " - let (pairingClient, networkingInteractor, syncClient, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix) - let pushLogger = ConsoleLogger(suffix: prefix + " [Push]", loggingLevel: .debug) + let (pairingClient, networkingInteractor, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix) + let notifyLogger = ConsoleLogger(prefix: prefix + " [Notify]", loggingLevel: .debug) walletPairingClient = pairingClient - let echoClient = EchoClientFactory.create(projectId: "", - echoHost: "echo.walletconnect.com", - keychainStorage: keychain, - environment: .sandbox) - let keyserverURL = URL(string: "https://keys.walletconnect.com")! let historyClient = HistoryClientFactory.create( historyUrl: "https://history.walletconnect.com", relayUrl: "wss://relay.walletconnect.com", keychain: keychain ) - walletPushClient = WalletPushClientFactory.create(keyserverURL: keyserverURL, - logger: pushLogger, - keyValueStorage: keyValueStorage, - keychainStorage: keychain, - groupKeychainStorage: KeychainStorageMock(), - networkInteractor: networkingInteractor, - pairingRegisterer: pairingClient, - echoClient: echoClient, - syncClient: syncClient, - historyClient: historyClient) + appAuthClient = AuthClientFactory.create( + metadata: AppMetadata(name: name, description: "", url: "", icons: [""]), + projectId: InputConfig.projectId, + crypto: DefaultCryptoProvider(), + logger: notifyLogger, + keyValueStorage: keyValueStorage, + keychainStorage: keychain, + networkingClient: networkingInteractor, + pairingRegisterer: pairingClient, + iatProvider: IATProviderMock()) } func makeWalletPairingClient() { let prefix = "đŸļ Wallet: " - let (pairingClient, _, _, _, _) = makeClientDependencies(prefix: prefix) + let (pairingClient, _, _, _) = makeClientDependencies(prefix: prefix) walletPairingClient = pairingClient } @@ -110,23 +106,6 @@ final class PairingTests: XCTestCase { makeDappClients() } - func testProposePushOnPairing() async { - makeWalletClients() - let expectation = expectation(description: "propose push on pairing") - - walletPushClient.requestPublisher.sink { _ in - expectation.fulfill() - }.store(in: &publishers) - - let uri = try! await appPairingClient.create() - - try! await walletPairingClient.pair(uri: uri) - - try! await appPushClient.propose(account: Account.stub(), topic: uri.topic) - - wait(for: [expectation], timeout: InputConfig.defaultTimeout) - } - func testPing() async { let expectation = expectation(description: "expects ping response") makeWalletClients() @@ -145,8 +124,8 @@ final class PairingTests: XCTestCase { makeWalletPairingClient() let expectation = expectation(description: "wallet responds unsupported method for unregistered method") - appPushClient.proposalResponsePublisher.sink { (response) in - XCTAssertEqual(response, .failure(PushError(code: 10001)!)) + appAuthClient.authResponsePublisher.sink { (_, response) in + XCTAssertEqual(response, .failure(AuthError(code: 10001)!)) expectation.fulfill() }.store(in: &publishers) @@ -154,7 +133,7 @@ final class PairingTests: XCTestCase { try! await walletPairingClient.pair(uri: uri) - try! await appPushClient.propose(account: Account.stub(), topic: uri.topic) + try! await appAuthClient.request(RequestParams.stub(), topic: uri.topic) wait(for: [expectation], timeout: InputConfig.defaultTimeout) } diff --git a/Example/IntegrationTests/Push/NotifyTests.swift b/Example/IntegrationTests/Push/NotifyTests.swift new file mode 100644 index 000000000..10dc8e061 --- /dev/null +++ b/Example/IntegrationTests/Push/NotifyTests.swift @@ -0,0 +1,184 @@ +import Foundation +import XCTest +import WalletConnectUtils +import Web3 +@testable import WalletConnectKMS +import WalletConnectRelay +import Combine +import WalletConnectNetworking +import WalletConnectPush +@testable import WalletConnectNotify +@testable import WalletConnectPairing +@testable import WalletConnectSync +@testable import WalletConnectHistory +import WalletConnectIdentity +import WalletConnectSigner + +final class NotifyTests: XCTestCase { + + var walletPairingClient: PairingClient! + + var walletNotifyClient: NotifyClient! + + let gmDappUrl = "https://notify.gm.walletconnect.com/" + + 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, SyncClient, KeychainStorageProtocol, KeyValueStorage) { + let keychain = KeychainStorageMock() + let keyValueStorage = RuntimeKeyValueStorage() + + let relayLogger = ConsoleLogger(prefix: prefix + " [Relay]", loggingLevel: .debug) + let pairingLogger = ConsoleLogger(prefix: prefix + " [Pairing]", loggingLevel: .debug) + let networkingLogger = ConsoleLogger(prefix: prefix + " [Networking]", loggingLevel: .debug) + + let relayClient = RelayClientFactory.create( + relayHost: InputConfig.relayHost, + projectId: InputConfig.projectId, + keyValueStorage: keyValueStorage, + keychainStorage: keychain, + socketFactory: DefaultSocketFactory(), + logger: relayLogger) + + let networkingClient = NetworkingClientFactory.create( + relayClient: relayClient, + logger: networkingLogger, + keychainStorage: keychain, + keyValueStorage: keyValueStorage) + + let pairingClient = PairingClientFactory.create( + logger: pairingLogger, + keyValueStorage: keyValueStorage, + 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, syncClient, keychain, keyValueStorage) + } + + func makeWalletClients() { + let prefix = "đŸĻ‹ Wallet: " + let (pairingClient, networkingInteractor, syncClient, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix) + let notifyLogger = ConsoleLogger(prefix: prefix + " [Notify]", loggingLevel: .debug) + walletPairingClient = pairingClient + let pushClient = PushClientFactory.create(projectId: "", + pushHost: "echo.walletconnect.com", + keychainStorage: keychain, + environment: .sandbox) + let keyserverURL = URL(string: "https://keys.walletconnect.com")! + let historyClient = HistoryClientFactory.create( + historyUrl: "https://history.walletconnect.com", + relayUrl: "wss://relay.walletconnect.com", + keychain: keychain + ) + walletNotifyClient = NotifyClientFactory.create(keyserverURL: keyserverURL, + logger: notifyLogger, + keyValueStorage: keyValueStorage, + keychainStorage: keychain, + groupKeychainStorage: KeychainStorageMock(), + networkInteractor: networkingInteractor, + pairingRegisterer: pairingClient, + pushClient: pushClient, + syncClient: syncClient, + historyClient: historyClient, + crypto: DefaultCryptoProvider()) + } + + override func setUp() { + makeWalletClients() + } + + func testWalletCreatesSubscription() async { + let expectation = expectation(description: "expects to create notify subscription") + let metadata = AppMetadata(name: "GM Dapp", description: "", url: gmDappUrl, icons: []) + try! await walletNotifyClient.register(account: account, onSign: sign) + try! await walletNotifyClient.subscribe(metadata: metadata, account: account, onSign: sign) + walletNotifyClient.subscriptionsPublisher + .first() + .sink { [unowned self] subscriptions in + XCTAssertNotNil(subscriptions.first) + Task { try! await walletNotifyClient.deleteSubscription(topic: subscriptions.first!.topic) } + expectation.fulfill() + }.store(in: &publishers) + wait(for: [expectation], timeout: InputConfig.defaultTimeout) + } + + func testWalletCreatesAndUpdatesSubscription() async { + let expectation = expectation(description: "expects to create and update notify subscription") + let metadata = AppMetadata(name: "GM Dapp", description: "", url: gmDappUrl, icons: []) + let updateScope: Set = ["alerts"] + try! await walletNotifyClient.register(account: account, onSign: sign) + try! await walletNotifyClient.subscribe(metadata: metadata, account: account, onSign: sign) + walletNotifyClient.subscriptionsPublisher + .first() + .sink { [unowned self] subscriptions in + sleep(1) + Task { try! await walletNotifyClient.update(topic: subscriptions.first!.topic, scope: updateScope) } + } + .store(in: &publishers) + + walletNotifyClient.updateSubscriptionPublisher + .sink { [unowned self] result in + guard case .success(let subscription) = result else { XCTFail(); return } + let updatedScope = Set(subscription.scope.filter{ $0.value.enabled == true }.keys) + XCTAssertEqual(updatedScope, updateScope) + Task { try! await walletNotifyClient.deleteSubscription(topic: subscription.topic) } + expectation.fulfill() + }.store(in: &publishers) + + wait(for: [expectation], timeout: InputConfig.defaultTimeout) + } + +// func testNotifyServerSubscribeAndNotifies() async throws { +// let subscribeExpectation = expectation(description: "creates notify subscription") +// let messageExpectation = expectation(description: "receives a notify message") +// let notifyMessage = NotifyMessage.stub() +// +// let metadata = AppMetadata(name: "GM Dapp", description: "", url: gmDappUrl, icons: []) +// try! await walletNotifyClient.register(account: account, onSign: sign) +// try! await walletNotifyClient.subscribe(metadata: metadata, account: account, onSign: sign) +// var subscription: NotifySubscription! +// walletNotifyClient.subscriptionsPublisher +// .first() +// .sink { subscriptions in +// XCTAssertNotNil(subscriptions.first) +// subscribeExpectation.fulfill() +// subscription = subscriptions.first! +// let notifier = Publisher() +// sleep(5) +// Task(priority: .high) { try await notifier.notify(topic: subscriptions.first!.topic, account: subscriptions.first!.account, message: notifyMessage) } +// }.store(in: &publishers) +// walletNotifyClient.notifyMessagePublisher +// .sink { notifyMessageRecord in +// XCTAssertEqual(notifyMessage, notifyMessageRecord.message) +// messageExpectation.fulfill() +// }.store(in: &publishers) +// +// wait(for: [subscribeExpectation, messageExpectation], timeout: InputConfig.defaultTimeout) +// try await walletNotifyClient.deleteSubscription(topic: subscription.topic) +// } + +} + + +private extension NotifyTests { + func sign(_ message: String) -> SigningResult { + 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/Push/Publisher.swift b/Example/IntegrationTests/Push/Publisher.swift index 50667f1ce..5ee993759 100644 --- a/Example/IntegrationTests/Push/Publisher.swift +++ b/Example/IntegrationTests/Push/Publisher.swift @@ -1,8 +1,8 @@ -@testable import WalletConnectPush +@testable import WalletConnectNotify import Foundation class Publisher { - func notify(topic: String, account: Account, message: PushMessage) async throws { + func notify(topic: String, account: Account, message: NotifyMessage) async throws { let url = URL(string: "https://\(InputConfig.castHost)/\(InputConfig.gmDappProjectId)/notify")! var request = URLRequest(url: url) let notifyRequestPayload = NotifyRequest(notification: message, accounts: [account]) @@ -19,6 +19,6 @@ class Publisher { } struct NotifyRequest: Codable { - let notification: PushMessage + let notification: NotifyMessage let accounts: [Account] } diff --git a/Example/IntegrationTests/Push/PushTests.swift b/Example/IntegrationTests/Push/PushTests.swift deleted file mode 100644 index a58c1ed54..000000000 --- a/Example/IntegrationTests/Push/PushTests.swift +++ /dev/null @@ -1,272 +0,0 @@ -import Foundation -import XCTest -import WalletConnectUtils -import Web3 -@testable import WalletConnectKMS -import WalletConnectRelay -import Combine -import WalletConnectNetworking -import WalletConnectEcho -@testable import WalletConnectPush -@testable import WalletConnectPairing -@testable import WalletConnectSync -@testable import WalletConnectHistory -import WalletConnectIdentity -import WalletConnectSigner - -final class PushTests: XCTestCase { - - var dappPairingClient: PairingClient! - var walletPairingClient: PairingClient! - - var dappPushClient: DappPushClient! - var walletPushClient: WalletPushClient! - - 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, SyncClient, KeychainStorageProtocol, KeyValueStorage) { - let keychain = KeychainStorageMock() - let keyValueStorage = RuntimeKeyValueStorage() - - let relayLogger = ConsoleLogger(suffix: prefix + " [Relay]", loggingLevel: .debug) - let pairingLogger = ConsoleLogger(suffix: prefix + " [Pairing]", loggingLevel: .debug) - let networkingLogger = ConsoleLogger(suffix: prefix + " [Networking]", loggingLevel: .debug) - - let relayClient = RelayClientFactory.create( - relayHost: InputConfig.relayHost, - projectId: InputConfig.projectId, - keyValueStorage: keyValueStorage, - keychainStorage: keychain, - socketFactory: DefaultSocketFactory(), - logger: relayLogger) - - let networkingClient = NetworkingClientFactory.create( - relayClient: relayClient, - logger: networkingLogger, - keychainStorage: keychain, - keyValueStorage: keyValueStorage) - - let pairingClient = PairingClientFactory.create( - logger: pairingLogger, - keyValueStorage: keyValueStorage, - 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, syncClient, keychain, keyValueStorage) - } - - func makeDappClients() { - let prefix = "đŸĻ„ Dapp: " - 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, - groupKeychainStorage: KeychainStorageMock(), - networkInteractor: networkingInteractor, - syncClient: syncClient) - } - - func makeWalletClients() { - let prefix = "đŸĻ‹ Wallet: " - let (pairingClient, networkingInteractor, syncClient, keychain, keyValueStorage) = makeClientDependencies(prefix: prefix) - let pushLogger = ConsoleLogger(suffix: prefix + " [Push]", loggingLevel: .debug) - walletPairingClient = pairingClient - let echoClient = EchoClientFactory.create(projectId: "", - echoHost: "echo.walletconnect.com", - keychainStorage: keychain, - environment: .sandbox) - let keyserverURL = URL(string: "https://keys.walletconnect.com")! - let historyClient = HistoryClientFactory.create( - historyUrl: "https://history.walletconnect.com", - relayUrl: "wss://relay.walletconnect.com", - keychain: keychain - ) - walletPushClient = WalletPushClientFactory.create(keyserverURL: keyserverURL, - logger: pushLogger, - keyValueStorage: keyValueStorage, - keychainStorage: keychain, - groupKeychainStorage: KeychainStorageMock(), - networkInteractor: networkingInteractor, - pairingRegisterer: pairingClient, - echoClient: echoClient, - syncClient: syncClient, - historyClient: historyClient) - } - - override func setUp() { - makeDappClients() - makeWalletClients() - } - - func testPushPropose() async { - let expectation = expectation(description: "expects dapp to receive error response") - - let uri = try! await dappPairingClient.create() - try! await walletPairingClient.pair(uri: uri) - 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) } - }.store(in: &publishers) - - dappPushClient.proposalResponsePublisher.sink { (result) in - guard case .success = result else { - XCTFail() - return - } - expectation.fulfill() - }.store(in: &publishers) - wait(for: [expectation], timeout: InputConfig.defaultTimeout) - - } - - func testWalletRejectsPushPropose() async { - let expectation = expectation(description: "expects dapp to receive error response") - - let uri = try! await dappPairingClient.create() - try! await walletPairingClient.pair(uri: uri) - 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) } - }.store(in: &publishers) - - dappPushClient.proposalResponsePublisher.sink { (result) in - guard case .failure = result else { - XCTFail() - return - } - expectation.fulfill() - }.store(in: &publishers) - - wait(for: [expectation], timeout: InputConfig.defaultTimeout) - } - - 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.enableSync(account: account, onSign: sign) - try! await walletPushClient.subscribe(metadata: metadata, account: account, onSign: sign) - walletPushClient.subscriptionsPublisher - .first() - .sink { [unowned self] subscriptions in - XCTAssertNotNil(subscriptions.first) - Task { try! await walletPushClient.deleteSubscription(topic: subscriptions.first!.topic) } - expectation.fulfill() - }.store(in: &publishers) - 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 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 - Task(priority: .high) { try! await walletPushClient.approve(id: id, onSign: sign) } - }.store(in: &publishers) - - dappPushClient.proposalResponsePublisher.sink { [unowned self] (result) in - guard case .success(let pushSubscription) = result else { - XCTFail() - return - } - subscriptionTopic = pushSubscription.topic - sleep(1) - Task(priority: .userInitiated) { try! await walletPushClient.deleteSubscription(topic: pushSubscription.topic)} - }.store(in: &publishers) - - dappPushClient.deleteSubscriptionPublisher.sink { topic in - XCTAssertEqual(subscriptionTopic, topic) - expectation.fulfill() - }.store(in: &publishers) - wait(for: [expectation], timeout: InputConfig.defaultTimeout) - } - - func testWalletCreatesAndUpdatesSubscription() async { - 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.enableSync(account: account, onSign: sign) - try! await walletPushClient.subscribe(metadata: metadata, account: account, onSign: sign) - walletPushClient.subscriptionsPublisher - .first() - .sink { [unowned self] subscriptions in - sleep(1) - Task { try! await walletPushClient.update(topic: subscriptions.first!.topic, scope: updateScope) } - } - .store(in: &publishers) - - walletPushClient.updateSubscriptionPublisher - .sink { [unowned self] result in - guard case .success(let subscription) = result else { XCTFail(); return } - let updatedScope = Set(subscription.scope.filter{ $0.value.enabled == true }.keys) - XCTAssertEqual(updatedScope, updateScope) - Task { try! await walletPushClient.deleteSubscription(topic: subscription.topic) } - expectation.fulfill() - }.store(in: &publishers) - - wait(for: [expectation], timeout: InputConfig.defaultTimeout) - } - - func testNotifyServerSubscribeAndNotifies() async throws { - let subscribeExpectation = expectation(description: "creates push subscription") - let messageExpectation = expectation(description: "receives a push message") - let pushMessage = PushMessage.stub() - - let metadata = AppMetadata(name: "GM Dapp", description: "", url: "https://gm-dapp-xi.vercel.app/", icons: []) - try! await walletPushClient.enableSync(account: account, onSign: sign) - try! await walletPushClient.subscribe(metadata: metadata, account: account, onSign: sign) - var subscription: PushSubscription! - walletPushClient.subscriptionsPublisher - .first() - .sink { subscriptions in - XCTAssertNotNil(subscriptions.first) - subscribeExpectation.fulfill() - subscription = subscriptions.first! - let notifier = Publisher() - sleep(1) - Task(priority: .high) { try await notifier.notify(topic: subscriptions.first!.topic, account: subscriptions.first!.account, message: pushMessage) } - }.store(in: &publishers) - walletPushClient.pushMessagePublisher - .sink { pushMessageRecord in - XCTAssertEqual(pushMessage, pushMessageRecord.message) - messageExpectation.fulfill() - }.store(in: &publishers) - - wait(for: [subscribeExpectation, messageExpectation], timeout: InputConfig.defaultTimeout) - try await walletPushClient.deleteSubscription(topic: subscription.topic) - } - -} - - -private extension PushTests { - func sign(_ message: String) -> SigningResult { - 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 96383d0fc..d28ae7c11 100644 --- a/Example/IntegrationTests/Sign/SignClientTests.swift +++ b/Example/IntegrationTests/Sign/SignClientTests.swift @@ -12,7 +12,7 @@ final class SignClientTests: XCTestCase { var wallet: ClientDelegate! static private func makeClientDelegate(name: String) -> ClientDelegate { - let logger = ConsoleLogger(suffix: name, loggingLevel: .debug) + let logger = ConsoleLogger(prefix: name, loggingLevel: .debug) let keychain = KeychainStorageMock() let keyValueStorage = RuntimeKeyValueStorage() let relayClient = RelayClientFactory.create( diff --git a/Example/IntegrationTests/Stubs/PushMessage.swift b/Example/IntegrationTests/Stubs/PushMessage.swift index 5355a3aeb..1ad6880ee 100644 --- a/Example/IntegrationTests/Stubs/PushMessage.swift +++ b/Example/IntegrationTests/Stubs/PushMessage.swift @@ -1,9 +1,9 @@ import Foundation -import WalletConnectPush +import WalletConnectNotify -extension PushMessage { - static func stub() -> PushMessage { - return PushMessage( +extension NotifyMessage { + static func stub() -> NotifyMessage { + return NotifyMessage( title: "swift_test", body: "gm_hourly", icon: "https://images.unsplash.com/photo-1581224463294-908316338239?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=250&q=80", diff --git a/Example/IntegrationTests/Sync/SyncTests.swift b/Example/IntegrationTests/Sync/SyncTests.swift index 3f6464da4..adcfdc532 100644 --- a/Example/IntegrationTests/Sync/SyncTests.swift +++ b/Example/IntegrationTests/Sync/SyncTests.swift @@ -56,7 +56,7 @@ final class SyncTests: XCTestCase { let keychain = KeychainStorageMock() let kms = KeyManagementService(keychain: keychain) let derivationService = SyncDerivationService(syncStorage: syncSignatureStore, bip44: DefaultBIP44Provider(), kms: kms) - let logger = ConsoleLogger(suffix: suffix, loggingLevel: .debug) + let logger = ConsoleLogger(prefix: suffix, loggingLevel: .debug) let relayClient = RelayClientFactory.create( relayHost: InputConfig.relayHost, projectId: InputConfig.projectId, @@ -88,8 +88,10 @@ final class SyncTests: XCTestCase { func testSync() async throws { let setExpectation = expectation(description: "syncSetTest") let delExpectation = expectation(description: "syncDelTest") + let uptExpectation = expectation(description: "syncUptTest") let object = TestObject(id: "id-1", value: "value-1") + let updated = TestObject(id: "id-1", value: "value-2") syncStore1.syncUpdatePublisher.sink { (_, _, update) in switch update { @@ -97,6 +99,8 @@ final class SyncTests: XCTestCase { XCTFail() case .delete: delExpectation.fulfill() + case .update: + XCTFail() } }.store(in: &publishers) @@ -106,6 +110,8 @@ final class SyncTests: XCTestCase { setExpectation.fulfill() case .delete: XCTFail() + case .update: + uptExpectation.fulfill() } }.store(in: &publishers) @@ -123,6 +129,15 @@ final class SyncTests: XCTestCase { XCTAssertEqual(try syncStore1.getAll(for: account), [object]) XCTAssertEqual(try syncStore2.getAll(for: account), [object]) + // Testing SyncStore `update` + + try await syncStore1.set(object: updated, for: account) + + wait(for: [uptExpectation], timeout: InputConfig.defaultTimeout) + + XCTAssertEqual(try syncStore1.getAll(for: account), [updated]) + XCTAssertEqual(try syncStore2.getAll(for: account), [updated]) + // Testing SyncStore `delete` try await syncStore2.delete(id: object.id, for: account) @@ -140,5 +155,6 @@ final class SyncTests: XCTestCase { try await client.register(account: account, signature: signature) try await client.create(account: account, store: storeName) + try await client.subscribe(account: account, store: storeName) } } diff --git a/Example/IntegrationTests/XPlatform/Web3Wallet/XPlatformW3WTests.swift b/Example/IntegrationTests/XPlatform/Web3Wallet/XPlatformW3WTests.swift index 614134db1..e08391021 100644 --- a/Example/IntegrationTests/XPlatform/Web3Wallet/XPlatformW3WTests.swift +++ b/Example/IntegrationTests/XPlatform/Web3Wallet/XPlatformW3WTests.swift @@ -4,7 +4,7 @@ import Combine @testable import Web3Wallet @testable import Auth @testable import WalletConnectSign -@testable import WalletConnectEcho +@testable import WalletConnectPush final class XPlatformW3WTests: XCTestCase { var w3wClient: Web3WalletClient! @@ -20,12 +20,12 @@ final class XPlatformW3WTests: XCTestCase { let keychain = KeychainStorageMock() let keyValueStorage = RuntimeKeyValueStorage() - let relayLogger = ConsoleLogger(suffix: "🚄" + " [Relay]", loggingLevel: .debug) - let pairingLogger = ConsoleLogger(suffix: "👩‍❤ī¸â€đŸ’‹â€đŸ‘Š" + " [Pairing]", loggingLevel: .debug) - let networkingLogger = ConsoleLogger(suffix: "🕸ī¸" + " [Networking]", loggingLevel: .debug) - let authLogger = ConsoleLogger(suffix: "đŸĒĒ", loggingLevel: .debug) + let relayLogger = ConsoleLogger(prefix: "🚄" + " [Relay]", loggingLevel: .debug) + let pairingLogger = ConsoleLogger(prefix: "👩‍❤ī¸â€đŸ’‹â€đŸ‘Š" + " [Pairing]", loggingLevel: .debug) + let networkingLogger = ConsoleLogger(prefix: "🕸ī¸" + " [Networking]", loggingLevel: .debug) + let authLogger = ConsoleLogger(prefix: "đŸĒĒ", loggingLevel: .debug) - let signLogger = ConsoleLogger(suffix: "✍đŸŋ", loggingLevel: .debug) + let signLogger = ConsoleLogger(prefix: "✍đŸŋ", loggingLevel: .debug) let relayClient = RelayClientFactory.create( relayHost: InputConfig.relayHost, @@ -72,7 +72,7 @@ final class XPlatformW3WTests: XCTestCase { authClient: authClient, signClient: signClient, pairingClient: pairingClient, - echoClient: EchoClientMock()) + pushClient: PushClientMock()) } func testSessionSettle() async throws { diff --git a/Example/PNDecryptionService/NotificationService.swift b/Example/PNDecryptionService/NotificationService.swift index 1be5f2335..abf6473c2 100644 --- a/Example/PNDecryptionService/NotificationService.swift +++ b/Example/PNDecryptionService/NotificationService.swift @@ -1,5 +1,5 @@ import UserNotifications -import WalletConnectPush +import WalletConnectNotify import os class NotificationService: UNNotificationServiceExtension { @@ -13,9 +13,9 @@ class NotificationService: UNNotificationServiceExtension { if let bestAttemptContent = bestAttemptContent { let topic = bestAttemptContent.userInfo["topic"] as! String let ciphertext = bestAttemptContent.userInfo["blob"] as! String - NSLog("echo decryption, topic=%@", topic) + NSLog("Push decryption, topic=%@", topic) do { - let service = PushDecryptionService() + let service = NotifyDecryptionService() let pushMessage = try service.decryptMessage(topic: topic, ciphertext: ciphertext) bestAttemptContent.title = pushMessage.title bestAttemptContent.body = pushMessage.body @@ -23,7 +23,7 @@ class NotificationService: UNNotificationServiceExtension { return } catch { - NSLog("echo decryption, error=%@", error.localizedDescription) + NSLog("Push decryption, error=%@", error.localizedDescription) bestAttemptContent.title = "" bestAttemptContent.body = "content not set" } diff --git a/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift b/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift index 639a5d85d..9dc595c95 100644 --- a/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift +++ b/Example/RelayIntegrationTests/RelayClientEndToEndTests.swift @@ -43,7 +43,7 @@ final class RelayClientEndToEndTests: XCTestCase { ) let socket = WebSocket(url: urlFactory.create(fallback: false)) let webSocketFactory = WebSocketFactoryMock(webSocket: socket) - let logger = ConsoleLogger(suffix: prefix, loggingLevel: .debug) + let logger = ConsoleLogger(prefix: prefix, loggingLevel: .debug) let dispatcher = Dispatcher( socketFactory: webSocketFactory, relayUrlFactory: urlFactory, diff --git a/Example/Shared/DefaultCryptoProvider.swift b/Example/Shared/DefaultCryptoProvider.swift index 4905855f7..3baf2b7e9 100644 --- a/Example/Shared/DefaultCryptoProvider.swift +++ b/Example/Shared/DefaultCryptoProvider.swift @@ -22,20 +22,4 @@ struct DefaultCryptoProvider: CryptoProvider { return Data(hash) } - public func derive(entropy: Data, path: [WalletConnectSigner.DerivationPath]) -> Data { - let mnemonic = Mnemonic.create(entropy: entropy) - let seed = Mnemonic.createSeed(mnemonic: mnemonic) - let privateKey = PrivateKey(seed: seed, coin: .bitcoin) - - let derived = path.reduce(privateKey) { result, path in - switch path { - case .hardened(let index): - return result.derived(at: .hardened(index)) - case .notHardened(let index): - return result.derived(at: .notHardened(index)) - } - } - - return derived.raw - } } diff --git a/Example/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxViewController.swift b/Example/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxViewController.swift index 4a7a87c8f..c6a2d32d6 100644 --- a/Example/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxViewController.swift +++ b/Example/Showcase/Classes/PresentationLayer/Web3Inbox/Web3InboxViewController.swift @@ -18,7 +18,7 @@ final class Web3InboxViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() - Web3Inbox.configure(account: importAccount.account, bip44: DefaultBIP44Provider(), config: [.pushEnabled: false], environment: .sandbox, onSign: onSing) + Web3Inbox.configure(account: importAccount.account, bip44: DefaultBIP44Provider(), config: [.notifyEnabled: false], environment: .sandbox, crypto: DefaultCryptoProvider(), onSign: onSing) edgesForExtendedLayout = [] navigationItem.title = "Web3Inbox SDK" diff --git a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift index 5693f1dd0..58054aa20 100644 --- a/Example/WalletApp/ApplicationLayer/ConfigurationService.swift +++ b/Example/WalletApp/ApplicationLayer/ConfigurationService.swift @@ -7,6 +7,7 @@ final class ConfigurationService { func configure(importAccount: ImportAccount) { Networking.configure(projectId: InputConfig.projectId, socketFactory: DefaultSocketFactory()) + Networking.instance.setLogging(level: .debug) let metadata = AppMetadata( name: "Example Wallet", @@ -22,9 +23,13 @@ final class ConfigurationService { bip44: DefaultBIP44Provider(), config: [.chatEnabled: false, .settingsEnabled: false], environment: BuildConfiguration.shared.apnsEnvironment, + crypto: DefaultCryptoProvider(), onSign: importAccount.onSign ) + if let clientId = try? Networking.interactor.getClientId() { + LoggingService.instance.setUpUser(account: importAccount.account.absoluteString, clientId: clientId) + } LoggingService.instance.startLogging() } } diff --git a/Example/WalletApp/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift b/Example/WalletApp/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift index aa0c8f745..cea617574 100644 --- a/Example/WalletApp/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift +++ b/Example/WalletApp/ApplicationLayer/Configurator/ThirdPartyConfigurator.swift @@ -8,7 +8,7 @@ struct ThirdPartyConfigurator: Configurator { } func configureLogging() { - guard let sentryDsn = InputConfig.sentryDsn else { return } + guard let sentryDsn = InputConfig.sentryDsn, !sentryDsn.isEmpty else { return } SentrySDK.start { options in options.dsn = "https://\(sentryDsn)" // Set tracesSampleRate to 1.0 to capture 100% of transactions for performance monitoring. diff --git a/Example/WalletApp/ApplicationLayer/LoggingService.swift b/Example/WalletApp/ApplicationLayer/LoggingService.swift index d44badb82..30c0b484b 100644 --- a/Example/WalletApp/ApplicationLayer/LoggingService.swift +++ b/Example/WalletApp/ApplicationLayer/LoggingService.swift @@ -13,6 +13,13 @@ final class LoggingService { private var isLogging = false private let queue = DispatchQueue(label: "com.walletApp.loggingService") + func setUpUser(account: String, clientId: String) { + let user = User() + user.userId = clientId + user.data = ["account": account] + SentrySDK.setUser(user) + } + func startLogging() { queue.sync { guard isLogging == false else { return } diff --git a/Example/WalletApp/ApplicationLayer/PushRegisterer.swift b/Example/WalletApp/ApplicationLayer/PushRegisterer.swift index 622c4de26..b3f02f00d 100644 --- a/Example/WalletApp/ApplicationLayer/PushRegisterer.swift +++ b/Example/WalletApp/ApplicationLayer/PushRegisterer.swift @@ -1,5 +1,5 @@ - -import WalletConnectPush +import WalletConnectNotify +import Combine import UIKit class PushRegisterer { diff --git a/Example/WalletApp/Common/BuildConfiguration.swift b/Example/WalletApp/Common/BuildConfiguration.swift index f17d08e73..4e70cc13f 100644 --- a/Example/WalletApp/Common/BuildConfiguration.swift +++ b/Example/WalletApp/Common/BuildConfiguration.swift @@ -1,5 +1,5 @@ import Foundation -import WalletConnectPush +import WalletConnectNotify class BuildConfiguration { enum Environment: String { diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainInteractor.swift index 93f699efe..ecae96621 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainInteractor.swift @@ -2,13 +2,10 @@ import Foundation import Combine import Web3Wallet -import WalletConnectPush +import WalletConnectNotify final class MainInteractor { - var pushRequestPublisher: AnyPublisher<(id: RPCID, account: Account, metadata: AppMetadata), Never> { - return Push.wallet.requestPublisher - } - + var sessionProposalPublisher: AnyPublisher<(proposal: Session.Proposal, context: VerifyContext?), Never> { return Web3Wallet.instance.sessionProposalPublisher } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift index 9c51f9869..7167284c2 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainPresenter.swift @@ -39,12 +39,6 @@ extension MainPresenter { configurationService.configure(importAccount: importAccount) pushRegisterer.registerForPushNotifications() - interactor.pushRequestPublisher - .receive(on: DispatchQueue.main) - .sink { [unowned self] request in - router.present(pushRequest: request) - }.store(in: &disposeBag) - interactor.sessionProposalPublisher .receive(on: DispatchQueue.main) .sink { [unowned self] session in diff --git a/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift index 11f5e2a6f..7d176926f 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Main/MainRouter.swift @@ -1,7 +1,7 @@ import UIKit import Web3Wallet -import WalletConnectPush +import WalletConnectNotify final class MainRouter { weak var viewController: UIViewController! @@ -31,11 +31,6 @@ final class MainRouter { return SettingsModule.create(app: app) .wrapToNavigationController() } - - func present(pushRequest: PushRequest) { -// PushRequestModule.create(app: app, pushRequest: pushRequest) -// .presentFullScreen(from: viewController, transparentBackground: true) - } func present(proposal: Session.Proposal, importAccount: ImportAccount, context: VerifyContext?) { SessionProposalModule.create(app: app, importAccount: importAccount, proposal: proposal, context: context) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift index caf9826ee..e727c94e3 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/Models/SubscriptionsViewModel.swift @@ -1,8 +1,8 @@ import Foundation -import WalletConnectPush +import WalletConnectNotify struct SubscriptionsViewModel: Identifiable { - let subscription: WalletConnectPush.PushSubscription + let subscription: NotifySubscription var id: String { return subscription.topic diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsInteractor.swift index f07d922f7..84797d283 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsInteractor.swift @@ -1,20 +1,20 @@ -import WalletConnectPush +import WalletConnectNotify import Combine final class NotificationsInteractor { - var subscriptionsPublisher: AnyPublisher<[PushSubscription], Never> { - return Push.wallet.subscriptionsPublisher + var subscriptionsPublisher: AnyPublisher<[NotifySubscription], Never> { + return Notify.instance.subscriptionsPublisher } - func getSubscriptions() -> [PushSubscription] { - let subs = Push.wallet.getActiveSubscriptions() + func getSubscriptions() -> [NotifySubscription] { + let subs = Notify.instance.getActiveSubscriptions() return subs } - func removeSubscription(_ subscription: PushSubscription) async { + func removeSubscription(_ subscription: NotifySubscription) async { do { - try await Push.wallet.deleteSubscription(topic: subscription.topic) + try await Notify.instance.deleteSubscription(topic: subscription.topic) } catch { print(error) } diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsPresenter.swift index ba6761a50..0aa9d878d 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsPresenter.swift @@ -54,8 +54,8 @@ private extension NotificationsPresenter { } interactor.subscriptionsPublisher .receive(on: DispatchQueue.main) - .sink { [weak self] pushSubscriptions in - self?.subscriptions = pushSubscriptions + .sink { [weak self] notifySubscriptions in + self?.subscriptions = notifySubscriptions .map { SubscriptionsViewModel(subscription: $0) } } .store(in: &disposeBag) diff --git a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsRouter.swift index 1441d32ba..eebcfd7b3 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsRouter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Notifications/NotificationsRouter.swift @@ -1,5 +1,5 @@ import UIKit -import WalletConnectPush +import WalletConnectNotify final class NotificationsRouter { @@ -11,7 +11,7 @@ final class NotificationsRouter { self.app = app } - func presentNotifications(subscription: WalletConnectPush.PushSubscription) { + func presentNotifications(subscription: NotifySubscription) { PushMessagesModule.create(app: app, subscription: subscription) .push(from: viewController) } diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/Models/PushMessageViewModel.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/Models/PushMessageViewModel.swift index 35a61e1d6..cfb200823 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/Models/PushMessageViewModel.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/Models/PushMessageViewModel.swift @@ -1,10 +1,9 @@ - import Foundation -import WalletConnectPush +import WalletConnectNotify struct PushMessageViewModel: Identifiable { - let pushMessageRecord: WalletConnectPush.PushMessageRecord + let pushMessageRecord: NotifyMessageRecord var id: String { return pushMessageRecord.id diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesInteractor.swift index b2fa4a51f..e198f8383 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesInteractor.swift @@ -1,23 +1,23 @@ -import WalletConnectPush +import WalletConnectNotify import Combine final class PushMessagesInteractor { - let subscription: PushSubscription + let subscription: NotifySubscription - init(subscription: PushSubscription) { + init(subscription: NotifySubscription) { self.subscription = subscription } - var pushMessagePublisher: AnyPublisher { - return Push.wallet.pushMessagePublisher + var notifyMessagePublisher: AnyPublisher { + return Notify.instance.notifyMessagePublisher } - func getPushMessages() -> [PushMessageRecord] { - return Push.wallet.getMessageHistory(topic: subscription.topic) + func getPushMessages() -> [NotifyMessageRecord] { + return Notify.instance.getMessageHistory(topic: subscription.topic) } func deletePushMessage(id: String) { - Push.wallet.deletePushMessage(id: id) + Notify.instance.deleteNotifyMessage(id: id) } } diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesModule.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesModule.swift index e7b6cc382..447c08ce3 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesModule.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesModule.swift @@ -1,10 +1,10 @@ import SwiftUI -import WalletConnectPush +import WalletConnectNotify final class PushMessagesModule { @discardableResult - static func create(app: Application, subscription: PushSubscription) -> UIViewController { + static func create(app: Application, subscription: NotifySubscription) -> UIViewController { let router = PushMessagesRouter(app: app) let interactor = PushMessagesInteractor(subscription: subscription) let presenter = PushMessagesPresenter(interactor: interactor, router: router) diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift index 1abf8a408..1c725aec6 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/PushMessages/PushMessagesPresenter.swift @@ -1,6 +1,6 @@ import UIKit import Combine -import WalletConnectPush +import WalletConnectNotify final class PushMessagesPresenter: ObservableObject { @@ -50,7 +50,7 @@ private extension PushMessagesPresenter { PushMessageViewModel(pushMessageRecord: pushMessageRecord) } - interactor.pushMessagePublisher + interactor.notifyMessagePublisher .receive(on: DispatchQueue.main) .sink { [weak self] newPushMessage in let newMessageViewModel = PushMessageViewModel(pushMessageRecord: newPushMessage) diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift deleted file mode 100644 index 21610770c..000000000 --- a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestInteractor.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Foundation -import WalletConnectPush - -final class PushRequestInteractor { - func approve(pushRequest: PushRequest, importAccount: ImportAccount) async throws { - try await Push.wallet.approve(id: pushRequest.id, onSign: importAccount.onSign) - } - - func reject(pushRequest: PushRequest) async throws { - try await Push.wallet.reject(id: pushRequest.id) - } -} diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestModule.swift b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestModule.swift deleted file mode 100644 index ffb464f0f..000000000 --- a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestModule.swift +++ /dev/null @@ -1,17 +0,0 @@ -import SwiftUI -import WalletConnectPush - -final class PushRequestModule { - @discardableResult - static func create(app: Application, pushRequest: PushRequest, importAccount: ImportAccount) -> UIViewController { - let router = PushRequestRouter(app: app) - let interactor = PushRequestInteractor() - let presenter = PushRequestPresenter(interactor: interactor, router: router, pushRequest: pushRequest, importAccount: importAccount) - let view = PushRequestView().environmentObject(presenter) - let viewController = SceneViewController(viewModel: presenter, content: view) - - router.viewController = viewController - - return viewController - } -} diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestPresenter.swift b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestPresenter.swift deleted file mode 100644 index 19e2dadd3..000000000 --- a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestPresenter.swift +++ /dev/null @@ -1,54 +0,0 @@ -import UIKit -import Combine -import WalletConnectPush - -final class PushRequestPresenter: ObservableObject { - private let interactor: PushRequestInteractor - private let router: PushRequestRouter - private let importAccount: ImportAccount - - let pushRequest: PushRequest - - var message: String { - return String(describing: pushRequest.account) - } - - private var disposeBag = Set() - - init( - interactor: PushRequestInteractor, - router: PushRequestRouter, - pushRequest: PushRequest, - importAccount: ImportAccount - ) { - defer { setupInitialState() } - self.interactor = interactor - self.router = router - self.pushRequest = pushRequest - self.importAccount = importAccount - } - - @MainActor - func onApprove() async throws { - try await interactor.approve(pushRequest: pushRequest, importAccount: importAccount) - router.dismiss() - } - - @MainActor - func onReject() async throws { - try await interactor.reject(pushRequest: pushRequest) - router.dismiss() - } -} - -// MARK: - Private functions -private extension PushRequestPresenter { - func setupInitialState() { - - } -} - -// MARK: - SceneViewModel -extension PushRequestPresenter: SceneViewModel { - -} diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestRouter.swift b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestRouter.swift deleted file mode 100644 index 6ac5f730c..000000000 --- a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestRouter.swift +++ /dev/null @@ -1,15 +0,0 @@ -import UIKit - -final class PushRequestRouter { - weak var viewController: UIViewController! - - private let app: Application - - init(app: Application) { - self.app = app - } - - func dismiss() { - viewController.dismiss() - } -} diff --git a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestView.swift b/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestView.swift deleted file mode 100644 index 62a21e17f..000000000 --- a/Example/WalletApp/PresentationLayer/Wallet/PushRequest/PushRequestView.swift +++ /dev/null @@ -1,127 +0,0 @@ -import SwiftUI - -struct PushRequestView: View { - @EnvironmentObject var presenter: PushRequestPresenter - - @State var text = "" - - var body: some View { - ZStack { - Color.black.opacity(0.6) - - VStack { - Spacer() - - VStack(spacing: 0) { - Image("header") - .resizable() - .scaledToFit() - - Text("would you like to send notifications") - .foregroundColor(.grey8) - .font(.system(size: 22, weight: .bold, design: .rounded)) - .padding(.top, 10) - - pushRequestView() - - HStack(spacing: 20) { - Button { - Task(priority: .userInitiated) { try await - presenter.onReject() - } - } label: { - Text("Decline") - .frame(maxWidth: .infinity) - .foregroundColor(.white) - .font(.system(size: 20, weight: .semibold, design: .rounded)) - .padding(.vertical, 11) - .background( - LinearGradient( - gradient: Gradient(colors: [ - .foregroundNegative, - .lightForegroundNegative - ]), - startPoint: .top, endPoint: .bottom) - ) - .cornerRadius(20) - } - .shadow(color: .white.opacity(0.25), radius: 8, y: 2) - - Button { - Task(priority: .userInitiated) { try await - presenter.onApprove() - } - } label: { - Text("Allow") - .frame(maxWidth: .infinity) - .foregroundColor(.white) - .font(.system(size: 20, weight: .semibold, design: .rounded)) - .padding(.vertical, 11) - .background( - LinearGradient( - gradient: Gradient(colors: [ - .foregroundPositive, - .lightForegroundPositive - ]), - startPoint: .top, endPoint: .bottom) - ) - .cornerRadius(20) - } - .shadow(color: .white.opacity(0.25), radius: 8, y: 2) - } - .padding(.top, 25) - } - .padding(20) - .background(.ultraThinMaterial) - .cornerRadius(34) - .padding(.horizontal, 10) - - Spacer() - } - } - .edgesIgnoringSafeArea(.all) - } - - private func pushRequestView() -> some View { - VStack { - VStack(alignment: .leading) { - Text("Notifications") - .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) { - ScrollView { - Text(presenter.message) - .foregroundColor(.grey50) - .font(.system(size: 13, weight: .semibold, design: .rounded)) - } - .padding(.horizontal, 18) - .padding(.vertical, 10) - .frame(height: 250) - } - .background(Color.whiteBackground) - .cornerRadius(20, corners: .allCorners) - .padding(.horizontal, 5) - .padding(.bottom, 5) - - } - .background(.thinMaterial) - .cornerRadius(25, corners: .allCorners) - } - .padding(.top, 30) - } -} - -#if DEBUG -struct PushRequestView_Previews: PreviewProvider { - static var previews: some View { - PushRequestView() - } -} -#endif diff --git a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletInteractor.swift b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletInteractor.swift index 1860ad808..3a32be3f8 100644 --- a/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletInteractor.swift +++ b/Example/WalletApp/PresentationLayer/Wallet/Wallet/WalletInteractor.swift @@ -1,7 +1,7 @@ import Combine import Web3Wallet -import WalletConnectPush +import WalletConnectNotify final class WalletInteractor { var sessionsPublisher: AnyPublisher<[Session], Never> { diff --git a/Package.swift b/Package.swift index cbbb29165..dc111f0dd 100644 --- a/Package.swift +++ b/Package.swift @@ -25,12 +25,12 @@ let package = Package( .library( name: "WalletConnectPairing", targets: ["WalletConnectPairing"]), + .library( + name: "WalletConnectNotify", + targets: ["WalletConnectNotify"]), .library( name: "WalletConnectPush", targets: ["WalletConnectPush"]), - .library( - name: "WalletConnectEcho", - targets: ["WalletConnectEcho"]), .library( name: "WalletConnectRouter", targets: ["WalletConnectRouter"]), @@ -73,16 +73,16 @@ let package = Package( path: "Sources/Auth"), .target( name: "Web3Wallet", - dependencies: ["Auth", "WalletConnectSign", "WalletConnectEcho", "WalletConnectVerify"], + dependencies: ["Auth", "WalletConnectSign", "WalletConnectPush", "WalletConnectVerify"], path: "Sources/Web3Wallet"), .target( - name: "WalletConnectPush", - dependencies: ["WalletConnectPairing", "WalletConnectEcho", "WalletConnectIdentity", "WalletConnectSync", "WalletConnectHistory"], - path: "Sources/WalletConnectPush"), + name: "WalletConnectNotify", + dependencies: ["WalletConnectPairing", "WalletConnectPush", "WalletConnectIdentity", "WalletConnectSync", "WalletConnectHistory"], + path: "Sources/WalletConnectNotify"), .target( - name: "WalletConnectEcho", + name: "WalletConnectPush", dependencies: ["WalletConnectNetworking", "WalletConnectJWT"], - path: "Sources/WalletConnectEcho"), + path: "Sources/WalletConnectPush"), .target( name: "WalletConnectRelay", dependencies: ["WalletConnectJWT"], @@ -100,7 +100,7 @@ let package = Package( dependencies: ["HTTPClient", "WalletConnectRelay"]), .target( name: "Web3Inbox", - dependencies: ["WalletConnectChat", "WalletConnectPush"]), + dependencies: ["WalletConnectChat", "WalletConnectNotify"]), .target( name: "WalletConnectSigner", dependencies: ["WalletConnectNetworking"]), @@ -157,7 +157,7 @@ let package = Package( dependencies: ["WalletConnectChat", "WalletConnectUtils", "TestingUtils"]), .testTarget( name: "NotifyTests", - dependencies: ["WalletConnectPush", "TestingUtils"]), + dependencies: ["WalletConnectNotify", "TestingUtils"]), .testTarget( name: "AuthTests", dependencies: ["Auth", "WalletConnectUtils", "TestingUtils", "WalletConnectVerify"]), diff --git a/Sources/Auth/Types/Errors/AuthError.swift b/Sources/Auth/Types/Errors/AuthError.swift index 7f9256ebd..cf4accb16 100644 --- a/Sources/Auth/Types/Errors/AuthError.swift +++ b/Sources/Auth/Types/Errors/AuthError.swift @@ -2,6 +2,7 @@ import Foundation /// Authentication error public enum AuthError: Codable, Equatable, Error { + case methodUnsupported case userDisconnected case userRejeted case malformedResponseParams @@ -14,6 +15,8 @@ extension AuthError: Reason { init?(code: Int) { switch code { + case Self.methodUnsupported.code: + self = .methodUnsupported case Self.userRejeted.code: self = .userRejeted case Self.malformedResponseParams.code: @@ -31,6 +34,8 @@ extension AuthError: Reason { public var code: Int { switch self { + case .methodUnsupported: + return 10001 case .userDisconnected: return 6000 case .userRejeted: @@ -48,6 +53,8 @@ extension AuthError: Reason { public var message: String { switch self { + case .methodUnsupported: + return "Method Unsupported" case .userRejeted: return "Auth request rejected by user" case .malformedResponseParams: diff --git a/Sources/Chat/ChatClientFactory.swift b/Sources/Chat/ChatClientFactory.swift index 558b3f7b2..d8f336381 100644 --- a/Sources/Chat/ChatClientFactory.swift +++ b/Sources/Chat/ChatClientFactory.swift @@ -28,7 +28,7 @@ public struct ChatClientFactory { historyClient: HistoryClient ) -> ChatClient { let kms = KeyManagementService(keychain: keychain) - let serializer = Serializer(kms: kms) + let serializer = Serializer(kms: kms, logger: logger) let historyService = HistoryService(historyClient: historyClient, seiralizer: serializer) let messageStore = KeyedDatabase(storage: storage, identifier: ChatStorageIdentifiers.messages.rawValue) let receivedInviteStore = KeyedDatabase(storage: storage, identifier: ChatStorageIdentifiers.receivedInvites.rawValue) diff --git a/Sources/Chat/Storage/ChatStorage.swift b/Sources/Chat/Storage/ChatStorage.swift index 400536e9f..67ff84402 100644 --- a/Sources/Chat/Storage/ChatStorage.swift +++ b/Sources/Chat/Storage/ChatStorage.swift @@ -102,10 +102,15 @@ final class ChatStorage { // MARK: - Configuration func initializeStores(for account: Account) async throws { - try await sentInviteStore.initialize(for: account) - try await threadStore.initialize(for: account) - try await inviteKeyStore.initialize(for: account) - try await receivedInviteStatusStore.initialize(for: account) + try await sentInviteStore.create(for: account) + try await threadStore.create(for: account) + try await inviteKeyStore.create(for: account) + try await receivedInviteStatusStore.create(for: account) + + try await sentInviteStore.subscribe(for: account) + try await threadStore.subscribe(for: account) + try await inviteKeyStore.subscribe(for: account) + try await receivedInviteStatusStore.subscribe(for: account) } func initializeDelegates() async throws { @@ -132,9 +137,9 @@ final class ChatStorage { receivedInvitesPublisherSubject.send(getReceivedInvites(account: account)) } - try sentInviteStore.setupSubscriptions(account: account) - try threadStore.setupSubscriptions(account: account) - try inviteKeyStore.setupSubscriptions(account: account) + try sentInviteStore.setupDatabaseSubscriptions(account: account) + try threadStore.setupDatabaseSubscriptions(account: account) + try inviteKeyStore.setupDatabaseSubscriptions(account: account) } // MARK: - Invites @@ -280,7 +285,7 @@ private extension ChatStorage { func setupSyncSubscriptions() { sentInviteStore.syncUpdatePublisher.sink { [unowned self] topic, account, update in switch update { - case .set(let object): + case .set(let object), .update(let object): self.sentInviteStoreDelegate.onUpdate(object) case .delete(let object): self.sentInviteStoreDelegate.onDelete(object) @@ -289,7 +294,7 @@ private extension ChatStorage { threadStore.syncUpdatePublisher.sink { [unowned self] topic, account, update in switch update { - case .set(let object): + case .set(let object), .update(let object): self.threadStoreDelegate.onUpdate(object, storage: self) case .delete(let object): self.threadStoreDelegate.onDelete(object) @@ -298,7 +303,7 @@ private extension ChatStorage { inviteKeyStore.syncUpdatePublisher.sink { [unowned self] topic, account, update in switch update { - case .set(let object): + case .set(let object), .update(let object): self.inviteKeyDelegate.onUpdate(object, account: account) case .delete(let object): self.inviteKeyDelegate.onDelete(object) @@ -307,7 +312,7 @@ private extension ChatStorage { receivedInviteStatusStore.syncUpdatePublisher.sink { [unowned self] topic, account, update in switch update { - case .set(let object): + case .set(let object), .update(let object): self.receiviedInviteStatusDelegate.onUpdate(object, storage: self, account: account) case .delete(let object): self.receiviedInviteStatusDelegate.onDelete(object) diff --git a/Sources/Chat/Types/Payloads/AcceptPayload.swift b/Sources/Chat/Types/Payloads/AcceptPayload.swift index 52c403c31..be06d1aa6 100644 --- a/Sources/Chat/Types/Payloads/AcceptPayload.swift +++ b/Sources/Chat/Types/Payloads/AcceptPayload.swift @@ -10,7 +10,11 @@ struct AcceptPayload: JWTClaimsCodable { let aud: String // proposer/inviter blockchain account (did:pkh) let sub: String // public key sent by the responder/invitee - let act: String // description of action intent + let act: String? // description of action intent + + static var action: String? { + return "invite_approval" + } } struct Wrapper: JWTWrapper { @@ -49,7 +53,7 @@ struct AcceptPayload: JWTClaimsCodable { ksu: keyserver.absoluteString, aud: inviterAccount.did, sub: inviteePublicKey.did(variant: .X25519), - act: "invite_approval" + act: Claims.action ) } } diff --git a/Sources/Chat/Types/Payloads/InvitePayload.swift b/Sources/Chat/Types/Payloads/InvitePayload.swift index f33bea807..bd6fdef67 100644 --- a/Sources/Chat/Types/Payloads/InvitePayload.swift +++ b/Sources/Chat/Types/Payloads/InvitePayload.swift @@ -23,7 +23,11 @@ struct InvitePayload: JWTClaimsCodable { let aud: String // responder/invitee blockchain account (did:pkh) let sub: String // opening message included in the invite let pke: String // proposer/inviter public key (did:key) - let act: String // description of action intent + let act: String? // description of action intent + + static var action: String? { + return "invite_proposal" + } } let keyserver: URL @@ -54,7 +58,7 @@ struct InvitePayload: JWTClaimsCodable { aud: inviteeAccount.did, sub: message, pke: inviterPublicKey.did(variant: .X25519), - act: "invite_proposal" + act: Claims.action ) } } diff --git a/Sources/Chat/Types/Payloads/MessagePayload.swift b/Sources/Chat/Types/Payloads/MessagePayload.swift index bb0632d5d..445612dc9 100644 --- a/Sources/Chat/Types/Payloads/MessagePayload.swift +++ b/Sources/Chat/Types/Payloads/MessagePayload.swift @@ -10,10 +10,14 @@ struct MessagePayload: JWTClaimsCodable { let aud: String // recipient blockchain account (did:pkh) let sub: String // message sent by the author account - let act: String // description of action intent + let act: String? // description of action intent // TODO: Media not implemented // public let xma: Media? + + static var action: String? { + return "chat_message" + } } struct Wrapper: JWTWrapper { @@ -52,7 +56,7 @@ struct MessagePayload: JWTClaimsCodable { ksu: keyserver.absoluteString, aud: DIDPKH(account: recipientAccount).string, sub: message, - act: "chat_message" + act: Claims.action ) } } diff --git a/Sources/Chat/Types/Payloads/ReceiptPayload.swift b/Sources/Chat/Types/Payloads/ReceiptPayload.swift index c5206922e..90362b305 100644 --- a/Sources/Chat/Types/Payloads/ReceiptPayload.swift +++ b/Sources/Chat/Types/Payloads/ReceiptPayload.swift @@ -10,7 +10,11 @@ struct ReceiptPayload: JWTClaimsCodable { let sub: String // hash of the message received let aud: String // sender blockchain account (did:pkh) - let act: String // description of action intent + let act: String? // description of action intent + + static var action: String? { + return "chat_receipt" + } } struct Wrapper: JWTWrapper { @@ -49,7 +53,7 @@ struct ReceiptPayload: JWTClaimsCodable { ksu: keyserver.absoluteString, sub: messageHash, aud: DIDPKH(account: senderAccount).string, - act: "chat_receipt" + act: Claims.action ) } } diff --git a/Sources/WalletConnectEcho/Echo.swift b/Sources/WalletConnectEcho/Echo.swift deleted file mode 100644 index 702033cb5..000000000 --- a/Sources/WalletConnectEcho/Echo.swift +++ /dev/null @@ -1,28 +0,0 @@ -import Foundation - -public class Echo { - static public let echoHost = "echo.walletconnect.com" - public static var instance: EchoClient = { - guard let config = Echo.config else { - fatalError("Error - you must call Echo.configure(_:) before accessing the shared instance.") - } - - return EchoClientFactory.create( - projectId: Networking.projectId, - echoHost: config.echoHost, - environment: config.environment) - }() - - private static var config: Config? - - private init() { } - - /// Echo instance config method - /// - Parameter clientId: https://github.com/WalletConnect/walletconnect-docs/blob/main/docs/specs/clients/core/relay/relay-client-auth.md#overview - static public func configure( - echoHost: String = echoHost, - environment: APNSEnvironment - ) { - Echo.config = Echo.Config(echoHost: echoHost, environment: environment) - } -} diff --git a/Sources/WalletConnectEcho/EchoImports.swift b/Sources/WalletConnectEcho/EchoImports.swift deleted file mode 100644 index 463cb7c23..000000000 --- a/Sources/WalletConnectEcho/EchoImports.swift +++ /dev/null @@ -1,4 +0,0 @@ -#if !CocoaPods -@_exported import WalletConnectNetworking -@_exported import WalletConnectJWT -#endif diff --git a/Sources/WalletConnectHistory/HistoryClientFactory.swift b/Sources/WalletConnectHistory/HistoryClientFactory.swift index c328ff4eb..5168430a3 100644 --- a/Sources/WalletConnectHistory/HistoryClientFactory.swift +++ b/Sources/WalletConnectHistory/HistoryClientFactory.swift @@ -14,7 +14,7 @@ class HistoryClientFactory { static func create(historyUrl: String, relayUrl: String, keychain: KeychainStorageProtocol) -> HistoryClient { let clientIdStorage = ClientIdStorage(keychain: keychain) let kms = KeyManagementService(keychain: keychain) - let serializer = Serializer(kms: kms) + let serializer = Serializer(kms: kms, logger: ConsoleLogger(prefix: "🔐", loggingLevel: .off)) let historyNetworkService = HistoryNetworkService(clientIdStorage: clientIdStorage) return HistoryClient( historyUrl: historyUrl, diff --git a/Sources/WalletConnectIdentity/IdentityService.swift b/Sources/WalletConnectIdentity/IdentityService.swift index 8a1a984a8..5f7fc0431 100644 --- a/Sources/WalletConnectIdentity/IdentityService.swift +++ b/Sources/WalletConnectIdentity/IdentityService.swift @@ -48,7 +48,7 @@ actor IdentityService { let inviteKey = try kms.createX25519KeyPair() let invitePublicKey = DIDKey(rawData: inviteKey.rawRepresentation) - let idAuth = try makeIDAuth(account: account, issuer: invitePublicKey, kind: .registerInvite) + let idAuth = try makeIDAuth(account: account, issuer: invitePublicKey, claims: RegisterInviteClaims.self) try await networkService.registerInvite(idAuth: idAuth) return try storage.saveInviteKey(inviteKey, for: account) @@ -57,7 +57,7 @@ actor IdentityService { func unregister(account: Account, onSign: SigningCallback) async throws { let identityKey = try storage.getIdentityKey(for: account) let identityPublicKey = DIDKey(rawData: identityKey.publicKey.rawRepresentation) - let idAuth = try makeIDAuth(account: account, issuer: identityPublicKey, kind: .unregisterIdentity) + let idAuth = try makeIDAuth(account: account, issuer: identityPublicKey, claims: UnregisterIdentityClaims.self) try await networkService.removeIdentity(idAuth: idAuth) try storage.removeIdentityKey(for: account) } @@ -65,7 +65,7 @@ actor IdentityService { func goPrivate(account: Account) async throws -> AgreementPublicKey { let inviteKey = try storage.getInviteKey(for: account) let invitePublicKey = DIDKey(rawData: inviteKey.rawRepresentation) - let idAuth = try makeIDAuth(account: account, issuer: invitePublicKey, kind: .unregisterInvite) + let idAuth = try makeIDAuth(account: account, issuer: invitePublicKey, claims: UnregisterInviteClaims.self) try await networkService.removeInvite(idAuth: idAuth) try storage.removeInviteKey(for: account) @@ -113,11 +113,10 @@ private extension IdentityService { } } - func makeIDAuth(account: Account, issuer: DIDKey, kind: IDAuthPayload.Kind) throws -> String { + func makeIDAuth(account: Account, issuer: DIDKey, claims: Claims.Type) throws -> String { let identityKey = try storage.getIdentityKey(for: account) - let payload = IDAuthPayload( - kind: kind, + let payload = IDAuthPayload( keyserver: keyserverURL, account: account, invitePublicKey: issuer diff --git a/Sources/WalletConnectIdentity/Types/IDAuthClaims.swift b/Sources/WalletConnectIdentity/Types/IDAuthClaims.swift new file mode 100644 index 000000000..55c47a7df --- /dev/null +++ b/Sources/WalletConnectIdentity/Types/IDAuthClaims.swift @@ -0,0 +1,13 @@ +import Foundation + +protocol IDAuthClaims: JWTClaims { + var iss: String { get } + var sub: String { get } + var aud: String { get } + var iat: UInt64 { get } + var exp: UInt64 { get } + var pkh: String { get } + var act: String? { get } + + init(iss: String, sub: String, aud: String, iat: UInt64, exp: UInt64, pkh: String, act: String?) +} diff --git a/Sources/WalletConnectIdentity/Types/IDAuthPayload.swift b/Sources/WalletConnectIdentity/Types/IDAuthPayload.swift index f8dcc7226..6b418538f 100644 --- a/Sources/WalletConnectIdentity/Types/IDAuthPayload.swift +++ b/Sources/WalletConnectIdentity/Types/IDAuthPayload.swift @@ -1,52 +1,26 @@ import Foundation -struct IDAuthPayload: JWTClaimsCodable { +struct IDAuthPayload: JWTClaimsCodable { enum Errors: Error { case undefinedKind } - enum Kind: String { - case registerInvite = "register_invite" - case unregisterInvite = "unregister_invite" - case unregisterIdentity = "unregister_identity" - - init(rawValue: String) throws { - guard let kind = Kind(rawValue: rawValue) else { - throw Errors.undefinedKind - } - self = kind - } - } - struct Wrapper: JWTWrapper { let jwtString: String } - struct Claims: JWTClaims { - let iss: String - let sub: String - let aud: String - let iat: UInt64 - let exp: UInt64 - let pkh: String - let act: String - } - - let kind: Kind let keyserver: URL let account: Account let invitePublicKey: DIDKey - init(kind: Kind, keyserver: URL, account: Account, invitePublicKey: DIDKey) { - self.kind = kind + init(keyserver: URL, account: Account, invitePublicKey: DIDKey) { self.keyserver = keyserver self.account = account self.invitePublicKey = invitePublicKey } init(claims: Claims) throws { - self.kind = try Kind(rawValue: claims.act) self.keyserver = try claims.aud.asURL() self.account = try Account(DIDPKHString: claims.pkh) self.invitePublicKey = try DIDKey(did: claims.sub) @@ -60,7 +34,7 @@ struct IDAuthPayload: JWTClaimsCodable { iat: defaultIatMilliseconds(), exp: expiry(days: 30), pkh: account.did, - act: kind.rawValue + act: Claims.action ) } } diff --git a/Sources/WalletConnectIdentity/Types/RegisterInviteClaims.swift b/Sources/WalletConnectIdentity/Types/RegisterInviteClaims.swift new file mode 100644 index 000000000..2a32ea8c3 --- /dev/null +++ b/Sources/WalletConnectIdentity/Types/RegisterInviteClaims.swift @@ -0,0 +1,15 @@ +import Foundation + +struct RegisterInviteClaims: IDAuthClaims { + let iss: String + let sub: String + let aud: String + let iat: UInt64 + let exp: UInt64 + let pkh: String + let act: String? + + static var action: String? { + return "register_invite" + } +} diff --git a/Sources/WalletConnectIdentity/Types/UnregisterIdentityClaims.swift b/Sources/WalletConnectIdentity/Types/UnregisterIdentityClaims.swift new file mode 100644 index 000000000..881c988ba --- /dev/null +++ b/Sources/WalletConnectIdentity/Types/UnregisterIdentityClaims.swift @@ -0,0 +1,15 @@ +import Foundation + +struct UnregisterIdentityClaims: IDAuthClaims { + let iss: String + let sub: String + let aud: String + let iat: UInt64 + let exp: UInt64 + let pkh: String + let act: String? + + static var action: String? { + return "unregister_identity" + } +} diff --git a/Sources/WalletConnectIdentity/Types/UnregisterInviteClaims.swift b/Sources/WalletConnectIdentity/Types/UnregisterInviteClaims.swift new file mode 100644 index 000000000..9b3452051 --- /dev/null +++ b/Sources/WalletConnectIdentity/Types/UnregisterInviteClaims.swift @@ -0,0 +1,15 @@ +import Foundation + +struct UnregisterInviteClaims: IDAuthClaims { + let iss: String + let sub: String + let aud: String + let iat: UInt64 + let exp: UInt64 + let pkh: String + let act: String? + + static var action: String? { + return "unregister_invite" + } +} diff --git a/Sources/WalletConnectJWT/JWTDecodable.swift b/Sources/WalletConnectJWT/JWTDecodable.swift index 59b1871b1..2f96ae072 100644 --- a/Sources/WalletConnectJWT/JWTDecodable.swift +++ b/Sources/WalletConnectJWT/JWTDecodable.swift @@ -10,6 +10,9 @@ public protocol JWTClaims: JWTEncodable { var iss: String { get } var iat: UInt64 { get } var exp: UInt64 { get } + var act: String? { get } + + static var action: String? { get } } public protocol JWTClaimsCodable { @@ -32,6 +35,9 @@ extension JWTClaimsCodable { guard try JWTValidator(jwtString: wrapper.jwtString).isValid(publicKey: signingPublicKey) else { throw JWTError.signatureVerificationFailed } + guard Claims.action == jwt.claims.act + else { throw JWTError.actMismatch } + return (try Self.init(claims: jwt.claims), jwt.claims) } diff --git a/Sources/WalletConnectJWT/JWTError.swift b/Sources/WalletConnectJWT/JWTError.swift index 0d84e2b5f..90cc40e14 100644 --- a/Sources/WalletConnectJWT/JWTError.swift +++ b/Sources/WalletConnectJWT/JWTError.swift @@ -7,4 +7,5 @@ enum JWTError: Error { case noSignature case invalidJWTString case signatureVerificationFailed + case actMismatch } diff --git a/Sources/WalletConnectKMS/Serialiser/Serializer.swift b/Sources/WalletConnectKMS/Serialiser/Serializer.swift index dcd8f984e..7a3dcf739 100644 --- a/Sources/WalletConnectKMS/Serialiser/Serializer.swift +++ b/Sources/WalletConnectKMS/Serialiser/Serializer.swift @@ -1,23 +1,43 @@ import Foundation +import Combine public class Serializer: Serializing { - enum Errors: String, Error { - case symmetricKeyForTopicNotFound + enum Errors: Error, CustomStringConvertible { + case symmetricKeyForTopicNotFound(String) case publicKeyForTopicNotFound + + var description: String { + switch self { + case .symmetricKeyForTopicNotFound(let topic): + return "Error: Symmetric key for topic '\(topic)' was not found." + case .publicKeyForTopicNotFound: + return "Error: Public key for topic was not found." + } + } } private let kms: KeyManagementServiceProtocol private let codec: Codec + private let logger: ConsoleLogging + public var logsPublisher: AnyPublisher { + logger.logsPublisher.eraseToAnyPublisher() + } - init(kms: KeyManagementServiceProtocol, codec: Codec = ChaChaPolyCodec()) { + init(kms: KeyManagementServiceProtocol, codec: Codec = ChaChaPolyCodec(), logger: ConsoleLogging) { self.kms = kms self.codec = codec + self.logger = logger } - public init(kms: KeyManagementServiceProtocol) { + public init(kms: KeyManagementServiceProtocol, logger: ConsoleLogging) { self.kms = kms self.codec = ChaChaPolyCodec() + self.logger = logger + } + + public func setLogging(level: LoggingLevel) { + logger.setLogging(level: level) } /// Encrypts and serializes an object @@ -29,7 +49,9 @@ public class Serializer: Serializing { public func serialize(topic: String, encodable: Encodable, envelopeType: Envelope.EnvelopeType) throws -> String { let messageJson = try encodable.json() guard let symmetricKey = kms.getSymmetricKeyRepresentable(for: topic) else { - throw Errors.symmetricKeyForTopicNotFound + let error = Errors.symmetricKeyForTopicNotFound(topic) + logger.error("\(error)") + throw error } let sealbox = try codec.encode(plaintext: messageJson, symmetricKey: symmetricKey) return Envelope(type: envelopeType, sealbox: sealbox).serialised() @@ -53,15 +75,29 @@ public class Serializer: Serializing { private func handleType0Envelope(_ topic: String, _ envelope: Envelope) throws -> (T, Data) { if let symmetricKey = kms.getSymmetricKeyRepresentable(for: topic) { - return try decode(sealbox: envelope.sealbox, symmetricKey: symmetricKey) + do { + let decoded: (T, Data) = try decode(sealbox: envelope.sealbox, symmetricKey: symmetricKey) + logger.debug("Decoded: \(decoded.0)") + return decoded + } + catch { + logger.error("\(error)") + throw error + } } else { - throw Errors.symmetricKeyForTopicNotFound + let error = Errors.symmetricKeyForTopicNotFound(topic) + logger.error("\(error)") + throw error } } private func handleType1Envelope(_ topic: String, peerPubKey: Data, sealbox: Data) throws -> (T, String, Data) { guard let selfPubKey = kms.getPublicKey(for: topic) - else { throw Errors.publicKeyForTopicNotFound } + else { + let error = Errors.publicKeyForTopicNotFound + logger.error("\(error)") + throw error + } let agreementKeys = try kms.performKeyAgreement(selfPublicKey: selfPubKey, peerPublicKey: peerPubKey.toHexString()) let decodedType: (object: T, data: Data) = try decode(sealbox: sealbox, symmetricKey: agreementKeys.sharedKey.rawRepresentation) @@ -71,7 +107,13 @@ public class Serializer: Serializing { } private func decode(sealbox: Data, symmetricKey: Data) throws -> (T, Data) { - let decryptedData = try codec.decode(sealbox: sealbox, symmetricKey: symmetricKey) - return (try JSONDecoder().decode(T.self, from: decryptedData), decryptedData) + do { + let decryptedData = try codec.decode(sealbox: sealbox, symmetricKey: symmetricKey) + let decodedType = try JSONDecoder().decode(T.self, from: decryptedData) + return (decodedType, decryptedData) + } catch { + logger.error("Failed to decode with error: \(error)") + throw error + } } } diff --git a/Sources/WalletConnectKMS/Serialiser/Serializing.swift b/Sources/WalletConnectKMS/Serialiser/Serializing.swift index 5982ce660..c9fc25cd2 100644 --- a/Sources/WalletConnectKMS/Serialiser/Serializing.swift +++ b/Sources/WalletConnectKMS/Serialiser/Serializing.swift @@ -1,6 +1,9 @@ import Foundation +import Combine public protocol Serializing { + var logsPublisher: AnyPublisher {get} + func setLogging(level: LoggingLevel) func serialize(topic: String, encodable: Encodable, envelopeType: Envelope.EnvelopeType) throws -> String /// - derivedTopic: topic derived from symmetric key as a result of key exchange if peers has sent envelope(type1) prefixed with it's public key func deserialize(topic: String, encodedEnvelope: String) throws -> (T, derivedTopic: String?, decryptedPayload: Data) diff --git a/Sources/WalletConnectModal/Modal/ModalViewModel.swift b/Sources/WalletConnectModal/Modal/ModalViewModel.swift index 33b26c141..5c274468a 100644 --- a/Sources/WalletConnectModal/Modal/ModalViewModel.swift +++ b/Sources/WalletConnectModal/Modal/ModalViewModel.swift @@ -38,6 +38,7 @@ final class ModalViewModel: ObservableObject { var isShown: Binding let interactor: ModalSheetInteractor let uiApplicationWrapper: UIApplicationWrapper + let recentWalletStorage: RecentWalletsStorage @Published private(set) var destinationStack: [Destination] = [.welcome] @Published private(set) var uri: String? @@ -52,11 +53,9 @@ final class ModalViewModel: ObservableObject { } var filteredWallets: [Listing] { - if searchTerm.isEmpty { return sortByRecent(wallets) } - - return sortByRecent( - wallets.filter { $0.name.lowercased().contains(searchTerm.lowercased()) } - ) + wallets + .sortByRecent() + .filter(searchTerm: searchTerm) } private var disposeBag = Set() @@ -65,11 +64,13 @@ final class ModalViewModel: ObservableObject { init( isShown: Binding, interactor: ModalSheetInteractor, - uiApplicationWrapper: UIApplicationWrapper = .live + uiApplicationWrapper: UIApplicationWrapper = .live, + recentWalletStorage: RecentWalletsStorage = RecentWalletsStorage() ) { self.isShown = isShown self.interactor = interactor self.uiApplicationWrapper = uiApplicationWrapper + self.recentWalletStorage = recentWalletStorage interactor.sessionSettlePublisher .receive(on: DispatchQueue.main) @@ -82,7 +83,7 @@ final class ModalViewModel: ObservableObject { interactor.sessionRejectionPublisher .receive(on: DispatchQueue.main) - .sink { (proposal, reason) in + .sink { _, reason in print(reason) self.toast = Toast(style: .error, message: reason.message) @@ -124,7 +125,10 @@ final class ModalViewModel: ObservableObject { func onBackButton() { guard destinationStack.count != 1 else { return } - _ = destinationStack.popLast() + + withAnimation { + _ = destinationStack.popLast() + } if destinationStack.last?.hasSearch == false { searchTerm = "" @@ -132,7 +136,6 @@ final class ModalViewModel: ObservableObject { } func onCopyButton() { - guard let uri else { toast = Toast(style: .error, message: "No uri found") return @@ -164,79 +167,103 @@ final class ModalViewModel: ObservableObject { // Small deliberate delay to ensure animations execute properly try await Task.sleep(nanoseconds: 500_000_000) - withAnimation { - self.wallets = wallets.sorted { - guard let lhs = $0.order else { - return false - } - - guard let rhs = $1.order else { - return true - } - - return lhs < rhs - } - - loadRecentWallets() - } + loadRecentWallets() + checkWhetherInstalled(wallets: wallets) + + self.wallets = wallets + .sortByOrder() + .sortByInstalled() } catch { toast = Toast(style: .error, message: error.localizedDescription) } } } -// MARK: - Recent Wallets +// MARK: - Sorting and filtering -private extension ModalViewModel { - - func sortByRecent(_ input: [Listing]) -> [Listing] { - input.sorted { lhs, rhs in - guard let lhsLastTimeUsed = lhs.lastTimeUsed else { +private extension Array where Element: Listing { + func sortByOrder() -> [Listing] { + sorted { + guard let lhs = $0.order else { return false } - guard let rhsLastTimeUsed = rhs.lastTimeUsed else { + guard let rhs = $1.order else { return true } - return lhsLastTimeUsed > rhsLastTimeUsed + return lhs < rhs } } - func loadRecentWallets() { - RecentWalletsStorage().recentWallets.forEach { wallet in - - guard let lastTimeUsed = wallet.lastTimeUsed else { - return + func sortByInstalled() -> [Listing] { + sorted { lhs, rhs in + if lhs.installed, !rhs.installed { + return true } - // Consider Recent only for 3 days - if abs(lastTimeUsed.timeIntervalSinceNow) > (24 * 60 * 60 * 3) { - return + if !lhs.installed, rhs.installed { + return false } - setLastTimeUsed(wallet.id, date: lastTimeUsed) + return false } } - func saveRecentWallets() { - RecentWalletsStorage().recentWallets = Array(wallets.filter { - $0.lastTimeUsed != nil - }.prefix(5)) + func sortByRecent() -> [Listing] { + sorted { lhs, rhs in + guard let lhsLastTimeUsed = lhs.lastTimeUsed else { + return false + } + + guard let rhsLastTimeUsed = rhs.lastTimeUsed else { + return true + } + + return lhsLastTimeUsed > rhsLastTimeUsed + } } - func setLastTimeUsed(_ walletId: String, date: Date = Date()) { - guard let index = wallets.firstIndex(where: { - $0.id == walletId - }) else { + func filter(searchTerm: String) -> [Listing] { + if searchTerm.isEmpty { return self } + + return filter { + $0.name.lowercased().contains(searchTerm.lowercased()) + } + } +} + +// MARK: - Recent & Installed Wallets + +private extension ModalViewModel { + func checkWhetherInstalled(wallets: [Listing]) { + guard let schemes = Bundle.main.object(forInfoDictionaryKey: "LSApplicationQueriesSchemes") as? [String] else { return } - var copy = wallets[index] - copy.lastTimeUsed = date - wallets[index] = copy - - saveRecentWallets() + wallets.forEach { + if + let walletScheme = $0.mobile.native, + !walletScheme.isEmpty, + schemes.contains(walletScheme.replacingOccurrences(of: "://", with: "")) + { + $0.installed = uiApplicationWrapper.canOpenURL(URL(string: walletScheme)!) + } + } + } + + func loadRecentWallets() { + recentWalletStorage.recentWallets.forEach { wallet in + guard let lastTimeUsed = wallet.lastTimeUsed else { return } + setLastTimeUsed(wallet.id, date: lastTimeUsed) + } + } + + func setLastTimeUsed(_ id: String, date: Date = Date()) { + wallets.first { + $0.id == id + }?.lastTimeUsed = date + recentWalletStorage.recentWallets = wallets } } @@ -248,7 +275,6 @@ protocol WalletDeeplinkHandler { } extension ModalViewModel: WalletDeeplinkHandler { - func openAppstore(wallet: Listing) { guard let storeLinkString = wallet.app.ios, @@ -277,7 +303,7 @@ extension ModalViewModel: WalletDeeplinkHandler { if !success { self.toast = Toast(style: .error, message: DeeplinkErrors.failedToOpen.localizedDescription) } - } + } } else { throw DeeplinkErrors.noWalletLinkFound } diff --git a/Sources/WalletConnectModal/Modal/RecentWalletStorage.swift b/Sources/WalletConnectModal/Modal/RecentWalletStorage.swift index 04487edce..00ccd5929 100644 --- a/Sources/WalletConnectModal/Modal/RecentWalletStorage.swift +++ b/Sources/WalletConnectModal/Modal/RecentWalletStorage.swift @@ -9,23 +9,44 @@ final class RecentWalletsStorage { var recentWallets: [Listing] { get { - guard - let data = defaults.data(forKey: "recentWallets"), - let wallets = try? JSONDecoder().decode([Listing].self, from: data) - else { - return [] - } - - return wallets + loadRecentWallets() } set { - guard - let walletsData = try? JSONEncoder().encode(newValue) - else { - return + saveRecentWallets(newValue) + } + } + + func loadRecentWallets() -> [Listing] { + guard + let data = defaults.data(forKey: "recentWallets"), + let wallets = try? JSONDecoder().decode([Listing].self, from: data) + else { + return [] + } + + return wallets.filter { listing in + guard let lastTimeUsed = listing.lastTimeUsed else { + assertionFailure("Shouldn't happen we stored wallet without `lastTimeUsed`") + return false } - defaults.set(walletsData, forKey: "recentWallets") + // Consider Recent only for 3 days + return abs(lastTimeUsed.timeIntervalSinceNow) > (24 * 60 * 60 * 3) + } + } + + func saveRecentWallets(_ listings: [Listing]) { + + let subset = Array(listings.filter { + $0.lastTimeUsed != nil + }.prefix(5)) + + guard + let walletsData = try? JSONEncoder().encode(subset) + else { + return } + + defaults.set(walletsData, forKey: "recentWallets") } } diff --git a/Sources/WalletConnectModal/Modal/Screens/WalletList.swift b/Sources/WalletConnectModal/Modal/Screens/WalletList.swift index 8a76bcd83..7ea02d286 100644 --- a/Sources/WalletConnectModal/Modal/Screens/WalletList.swift +++ b/Sources/WalletConnectModal/Modal/Screens/WalletList.swift @@ -35,6 +35,7 @@ struct WalletList: View { case .viewAll: viewAll() .frame(minHeight: 250) + .animation(nil) default: EmptyView() } @@ -173,8 +174,8 @@ struct WalletList: View { .foregroundColor(.foreground1) .multilineTextAlignment(.center) - Text("RECENT") - .opacity(wallet.lastTimeUsed != nil ? 1 : 0) + Text(wallet.lastTimeUsed != nil ? "RECENT" : "INSTALLED") + .opacity(wallet.lastTimeUsed != nil || wallet.installed ? 1 : 0) .font(.system(size: 10)) .foregroundColor(.foreground3) .padding(.horizontal, 12) diff --git a/Sources/WalletConnectModal/Networking/Explorer/ListingsResponse.swift b/Sources/WalletConnectModal/Networking/Explorer/ListingsResponse.swift index 3eef522e8..0ddd4446c 100644 --- a/Sources/WalletConnectModal/Networking/Explorer/ListingsResponse.swift +++ b/Sources/WalletConnectModal/Networking/Explorer/ListingsResponse.swift @@ -4,7 +4,40 @@ struct ListingsResponse: Codable { let listings: [String: Listing] } -struct Listing: Codable, Hashable, Identifiable { +class Listing: Codable, Hashable, Identifiable { + init( + id: String, + name: String, + homepage: String, + order: Int? = nil, + imageId: String, + app: Listing.App, + mobile: Listing.Links, + desktop: Listing.Links, + lastTimeUsed: Date? = nil, + installed: Bool = false + ) { + self.id = id + self.name = name + self.homepage = homepage + self.order = order + self.imageId = imageId + self.app = app + self.mobile = mobile + self.desktop = desktop + self.lastTimeUsed = lastTimeUsed + self.installed = installed + } + + func hash(into hasher: inout Hasher) { + hasher.combine(id) + hasher.combine(name) + } + + static func == (lhs: Listing, rhs: Listing) -> Bool { + lhs.id == rhs.id && lhs.name == rhs.name + } + let id: String let name: String let homepage: String @@ -13,7 +46,9 @@ struct Listing: Codable, Hashable, Identifiable { let app: App let mobile: Links let desktop: Links + var lastTimeUsed: Date? + var installed: Bool = false private enum CodingKeys: String, CodingKey { case id diff --git a/Sources/WalletConnectNetworking/NetworkingClient.swift b/Sources/WalletConnectNetworking/NetworkingClient.swift index 6c38700e4..e7c7fcd76 100644 --- a/Sources/WalletConnectNetworking/NetworkingClient.swift +++ b/Sources/WalletConnectNetworking/NetworkingClient.swift @@ -4,6 +4,7 @@ import Combine public protocol NetworkingClient { var socketConnectionStatusPublisher: AnyPublisher { get } var logsPublisher: AnyPublisher {get} + func setLogging(level: LoggingLevel) func connect() throws func disconnect(closeCode: URLSessionWebSocketTask.CloseCode) throws } diff --git a/Sources/WalletConnectNetworking/NetworkingClientFactory.swift b/Sources/WalletConnectNetworking/NetworkingClientFactory.swift index 6b5e39a2c..4470087d1 100644 --- a/Sources/WalletConnectNetworking/NetworkingClientFactory.swift +++ b/Sources/WalletConnectNetworking/NetworkingClientFactory.swift @@ -3,7 +3,7 @@ import Foundation public struct NetworkingClientFactory { public static func create(relayClient: RelayClient) -> NetworkingInteractor { - let logger = ConsoleLogger(loggingLevel: .debug) + let logger = ConsoleLogger(prefix: "🕸ī¸", loggingLevel: .off) let keyValueStorage = UserDefaults.standard let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk") return NetworkingClientFactory.create(relayClient: relayClient, logger: logger, keychainStorage: keychainStorage, keyValueStorage: keyValueStorage) @@ -12,7 +12,7 @@ public struct NetworkingClientFactory { public static func create(relayClient: RelayClient, logger: ConsoleLogging, keychainStorage: KeychainStorageProtocol, keyValueStorage: KeyValueStorage) -> NetworkingInteractor { let kms = KeyManagementService(keychain: keychainStorage) - let serializer = Serializer(kms: kms) + let serializer = Serializer(kms: kms, logger: ConsoleLogger(prefix: "🔐", loggingLevel: .off)) let rpcHistory = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage) diff --git a/Sources/WalletConnectNetworking/NetworkingInteractor.swift b/Sources/WalletConnectNetworking/NetworkingInteractor.swift index 76135c8fa..a07f0b81b 100644 --- a/Sources/WalletConnectNetworking/NetworkingInteractor.swift +++ b/Sources/WalletConnectNetworking/NetworkingInteractor.swift @@ -20,7 +20,9 @@ public class NetworkingInteractor: NetworkInteracting { } public var logsPublisher: AnyPublisher { - logger.logsPublisher.eraseToAnyPublisher() + logger.logsPublisher + .merge(with: serializer.logsPublisher) + .eraseToAnyPublisher() } public var networkConnectionStatusPublisher: AnyPublisher @@ -51,6 +53,13 @@ public class NetworkingInteractor: NetworkInteracting { }.store(in: &publishers) } + public func setLogging(level: LoggingLevel) { + logger.setLogging(level: level) + serializer.setLogging(level: level) + relayClient.setLogging(level: level) + } + + public func subscribe(topic: String) async throws { try await relayClient.subscribe(topic: topic) } diff --git a/Sources/WalletConnectNotify/Client/Common/DeleteNotifySubscriptionService.swift b/Sources/WalletConnectNotify/Client/Common/DeleteNotifySubscriptionService.swift new file mode 100644 index 000000000..cef34a689 --- /dev/null +++ b/Sources/WalletConnectNotify/Client/Common/DeleteNotifySubscriptionService.swift @@ -0,0 +1,74 @@ +import Foundation + +class DeleteNotifySubscriptionService { + enum Errors: Error { + case notifySubscriptionNotFound + } + private let keyserver: URL + private let networkingInteractor: NetworkInteracting + private let identityClient: IdentityClient + private let webDidResolver: WebDidResolver + private let kms: KeyManagementServiceProtocol + private let logger: ConsoleLogging + private let notifyStorage: NotifyStorage + + init( + keyserver: URL, + networkingInteractor: NetworkInteracting, + identityClient: IdentityClient, + webDidResolver: WebDidResolver, + kms: KeyManagementServiceProtocol, + logger: ConsoleLogging, + notifyStorage: NotifyStorage + ) { + self.keyserver = keyserver + self.networkingInteractor = networkingInteractor + self.identityClient = identityClient + self.webDidResolver = webDidResolver + self.kms = kms + self.logger = logger + self.notifyStorage = notifyStorage + } + + func delete(topic: String) async throws { + logger.debug("Will delete notify subscription") + + guard let subscription = notifyStorage.getSubscription(topic: topic) + else { throw Errors.notifySubscriptionNotFound} + + try await notifyStorage.deleteSubscription(topic: topic) + notifyStorage.deleteMessages(topic: topic) + + let protocolMethod = NotifyDeleteProtocolMethod() + let dappPubKey = try await webDidResolver.resolvePublicKey(dappUrl: subscription.metadata.url) + + let wrapper = try createJWTWrapper( + dappPubKey: DIDKey(rawData: dappPubKey.rawRepresentation), + reason: NotifyDeleteParams.userDisconnected.message, + app: subscription.metadata.url, + account: subscription.account + ) + + let request = RPCRequest(method: protocolMethod.method, params: wrapper) + try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) + + try await notifyStorage.deleteSubscription(topic: topic) + + networkingInteractor.unsubscribe(topic: topic) + + logger.debug("Subscription removed, topic: \(topic)") + + kms.deleteSymmetricKey(for: topic) + } +} + +private extension DeleteNotifySubscriptionService { + + func createJWTWrapper(dappPubKey: DIDKey, reason: String, app: String, account: Account) throws -> NotifyDeletePayload.Wrapper { + let jwtPayload = NotifyDeletePayload(keyserver: keyserver, dappPubKey: dappPubKey, reason: reason, app: app) + return try identityClient.signAndCreateWrapper( + payload: jwtPayload, + account: account + ) + } +} diff --git a/Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionSubscriber.swift b/Sources/WalletConnectNotify/Client/Common/DeleteNotifySubscriptionSubscriber.swift similarity index 60% rename from Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionSubscriber.swift rename to Sources/WalletConnectNotify/Client/Common/DeleteNotifySubscriptionSubscriber.swift index 7ec0f6f3a..435ece85f 100644 --- a/Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionSubscriber.swift +++ b/Sources/WalletConnectNotify/Client/Common/DeleteNotifySubscriptionSubscriber.swift @@ -1,36 +1,34 @@ import Foundation import Combine -class DeletePushSubscriptionSubscriber { +class DeleteNotifySubscriptionSubscriber { private let networkingInteractor: NetworkInteracting private let kms: KeyManagementServiceProtocol private let logger: ConsoleLogging private var publishers = [AnyCancellable]() - private let pushStorage: PushStorage + private let notifyStorage: NotifyStorage init(networkingInteractor: NetworkInteracting, kms: KeyManagementServiceProtocol, logger: ConsoleLogging, - pushStorage: PushStorage + notifyStorage: NotifyStorage ) { self.networkingInteractor = networkingInteractor self.kms = kms self.logger = logger - self.pushStorage = pushStorage + self.notifyStorage = notifyStorage subscribeForDeleteSubscription() } private func subscribeForDeleteSubscription() { - let protocolMethod = PushDeleteProtocolMethod() + let protocolMethod = NotifyDeleteProtocolMethod() networkingInteractor.requestSubscription(on: protocolMethod) - .sink { [unowned self] (payload: RequestSubscriptionPayload) in + .sink { [unowned self] (payload: RequestSubscriptionPayload) in + + guard let (_, _) = try? NotifyDeleteResponsePayload.decodeAndVerify(from: payload.request) + else { fatalError() /* TODO: Handle error */ } + logger.debug("Peer deleted subscription") - let topic = payload.topic - networkingInteractor.unsubscribe(topic: topic) - Task(priority: .high) { - try await pushStorage.deleteSubscription(topic: topic) - } - kms.deleteSymmetricKey(for: topic) }.store(in: &publishers) } } diff --git a/Sources/WalletConnectPush/Client/Common/PushDecryptionService.swift b/Sources/WalletConnectNotify/Client/Common/NotifyDecryptionService.swift similarity index 59% rename from Sources/WalletConnectPush/Client/Common/PushDecryptionService.swift rename to Sources/WalletConnectNotify/Client/Common/NotifyDecryptionService.swift index 995ff4e4a..5fe116af5 100644 --- a/Sources/WalletConnectPush/Client/Common/PushDecryptionService.swift +++ b/Sources/WalletConnectNotify/Client/Common/NotifyDecryptionService.swift @@ -1,8 +1,8 @@ import Foundation -public class PushDecryptionService { +public class NotifyDecryptionService { enum Errors: Error { - case malformedPushMessage + case malformedNotifyMessage } private let serializer: Serializing @@ -13,12 +13,14 @@ public class PushDecryptionService { public init() { let keychainStorage = GroupKeychainStorage(serviceIdentifier: "group.com.walletconnect.sdk") let kms = KeyManagementService(keychain: keychainStorage) - self.serializer = Serializer(kms: kms) + self.serializer = Serializer(kms: kms, logger: ConsoleLogger(prefix: "🔐", loggingLevel: .off)) } - public func decryptMessage(topic: String, ciphertext: String) throws -> PushMessage { + public func decryptMessage(topic: String, ciphertext: String) throws -> NotifyMessage { let (rpcRequest, _, _): (RPCRequest, String?, Data) = try serializer.deserialize(topic: topic, encodedEnvelope: ciphertext) - guard let params = rpcRequest.params else { throw Errors.malformedPushMessage } - return try params.get(PushMessage.self) + guard let params = rpcRequest.params else { throw Errors.malformedNotifyMessage } + let wrapper = try params.get(NotifyMessagePayload.Wrapper.self) + let (messagePayload, _) = try NotifyMessagePayload.decodeAndVerify(from: wrapper) + return messagePayload.message } } diff --git a/Sources/WalletConnectPush/Client/Common/PushResubscribeService.swift b/Sources/WalletConnectNotify/Client/Common/NotifyResubscribeService.swift similarity index 68% rename from Sources/WalletConnectPush/Client/Common/PushResubscribeService.swift rename to Sources/WalletConnectNotify/Client/Common/NotifyResubscribeService.swift index c19769f4d..4d48f7a7c 100644 --- a/Sources/WalletConnectPush/Client/Common/PushResubscribeService.swift +++ b/Sources/WalletConnectNotify/Client/Common/NotifyResubscribeService.swift @@ -1,16 +1,16 @@ import Foundation import Combine -final class PushResubscribeService { +final class NotifyResubscribeService { private var publishers = Set() private let networkInteractor: NetworkInteracting - private let pushStorage: PushStorage + private let notifyStorage: NotifyStorage - init(networkInteractor: NetworkInteracting, pushStorage: PushStorage) { + init(networkInteractor: NetworkInteracting, notifyStorage: NotifyStorage) { self.networkInteractor = networkInteractor - self.pushStorage = pushStorage + self.notifyStorage = notifyStorage setUpResubscription() } @@ -18,7 +18,7 @@ final class PushResubscribeService { networkInteractor.socketConnectionStatusPublisher .sink { [unowned self] status in guard status == .connected else { return } - let topics = pushStorage.getSubscriptions().map{$0.topic} + let topics = notifyStorage.getSubscriptions().map{$0.topic} Task(priority: .high) { try await networkInteractor.batchSubscribe(topics: topics) } diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift new file mode 100644 index 000000000..00f499a57 --- /dev/null +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClient.swift @@ -0,0 +1,123 @@ +import Foundation +import Combine + +public class NotifyClient { + + private var publishers = Set() + + /// publishes new subscriptions + public var newSubscriptionPublisher: AnyPublisher { + return notifyStorage.newSubscriptionPublisher + } + + public var subscriptionErrorPublisher: AnyPublisher { + return notifySubscribeResponseSubscriber.subscriptionErrorPublisher + } + + public var deleteSubscriptionPublisher: AnyPublisher { + return notifyStorage.deleteSubscriptionPublisher + } + + public var subscriptionsPublisher: AnyPublisher<[NotifySubscription], Never> { + return notifyStorage.subscriptionsPublisher + } + + public var notifyMessagePublisher: AnyPublisher { + notifyMessageSubscriber.notifyMessagePublisher + } + + public var updateSubscriptionPublisher: AnyPublisher, Never> { + return notifyUpdateResponseSubscriber.updateSubscriptionPublisher + } + + private let deleteNotifySubscriptionService: DeleteNotifySubscriptionService + private let notifySubscribeRequester: NotifySubscribeRequester + + public let logger: ConsoleLogging + + private let pushClient: PushClient + private let notifyStorage: NotifyStorage + private let notifySyncService: NotifySyncService + private let notifyMessageSubscriber: NotifyMessageSubscriber + private let resubscribeService: NotifyResubscribeService + private let notifySubscribeResponseSubscriber: NotifySubscribeResponseSubscriber + private let deleteNotifySubscriptionSubscriber: DeleteNotifySubscriptionSubscriber + private let notifyUpdateRequester: NotifyUpdateRequester + private let notifyUpdateResponseSubscriber: NotifyUpdateResponseSubscriber + private let subscriptionsAutoUpdater: SubscriptionsAutoUpdater + + init(logger: ConsoleLogging, + kms: KeyManagementServiceProtocol, + pushClient: PushClient, + notifyMessageSubscriber: NotifyMessageSubscriber, + notifyStorage: NotifyStorage, + notifySyncService: NotifySyncService, + deleteNotifySubscriptionService: DeleteNotifySubscriptionService, + resubscribeService: NotifyResubscribeService, + notifySubscribeRequester: NotifySubscribeRequester, + notifySubscribeResponseSubscriber: NotifySubscribeResponseSubscriber, + deleteNotifySubscriptionSubscriber: DeleteNotifySubscriptionSubscriber, + notifyUpdateRequester: NotifyUpdateRequester, + notifyUpdateResponseSubscriber: NotifyUpdateResponseSubscriber, + subscriptionsAutoUpdater: SubscriptionsAutoUpdater + ) { + self.logger = logger + self.pushClient = pushClient + self.notifyMessageSubscriber = notifyMessageSubscriber + self.notifyStorage = notifyStorage + self.notifySyncService = notifySyncService + self.deleteNotifySubscriptionService = deleteNotifySubscriptionService + self.resubscribeService = resubscribeService + self.notifySubscribeRequester = notifySubscribeRequester + self.notifySubscribeResponseSubscriber = notifySubscribeResponseSubscriber + self.deleteNotifySubscriptionSubscriber = deleteNotifySubscriptionSubscriber + self.notifyUpdateRequester = notifyUpdateRequester + self.notifyUpdateResponseSubscriber = notifyUpdateResponseSubscriber + self.subscriptionsAutoUpdater = subscriptionsAutoUpdater + } + + public func register(account: Account, onSign: @escaping SigningCallback) async throws { + try await notifySyncService.registerSyncIfNeeded(account: account, onSign: onSign) + try await notifySyncService.registerIdentity(account: account, onSign: onSign) + try await notifyStorage.initialize(account: account) + try await notifyStorage.subscribe(account: account) + try await notifySyncService.fetchHistoryIfNeeded(account: account) + } + + public func subscribe(metadata: AppMetadata, account: Account, onSign: @escaping SigningCallback) async throws { + try await notifySubscribeRequester.subscribe(metadata: metadata, account: account, onSign: onSign) + } + + public func update(topic: String, scope: Set) async throws { + try await notifyUpdateRequester.update(topic: topic, scope: scope) + } + + public func getActiveSubscriptions() -> [NotifySubscription] { + return notifyStorage.getSubscriptions() + } + + public func getMessageHistory(topic: String) -> [NotifyMessageRecord] { + notifyStorage.getMessages(topic: topic) + } + + public func deleteSubscription(topic: String) async throws { + try await deleteNotifySubscriptionService.delete(topic: topic) + } + + public func deleteNotifyMessage(id: String) { + notifyStorage.deleteMessage(id: id) + } + + public func register(deviceToken: Data) async throws { + try await pushClient.register(deviceToken: deviceToken) + } +} + +#if targetEnvironment(simulator) +extension NotifyClient { + public func register(deviceToken: String) async throws { + try await pushClient.register(deviceToken: deviceToken) + } +} +#endif + diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift new file mode 100644 index 000000000..2e15e4266 --- /dev/null +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyClientFactory.swift @@ -0,0 +1,85 @@ +import Foundation + +public struct NotifyClientFactory { + + public static func create(networkInteractor: NetworkInteracting, pairingRegisterer: PairingRegisterer, pushClient: PushClient, syncClient: SyncClient, historyClient: HistoryClient, crypto: CryptoProvider) -> NotifyClient { + let logger = ConsoleLogger(prefix: "🔔",loggingLevel: .debug) + let keyValueStorage = UserDefaults.standard + let keyserverURL = URL(string: "https://keys.walletconnect.com")! + let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk") + let groupKeychainService = GroupKeychainStorage(serviceIdentifier: "group.com.walletconnect.sdk") + + return NotifyClientFactory.create( + keyserverURL: keyserverURL, + logger: logger, + keyValueStorage: keyValueStorage, + keychainStorage: keychainStorage, + groupKeychainStorage: groupKeychainService, + networkInteractor: networkInteractor, + pairingRegisterer: pairingRegisterer, + pushClient: pushClient, + syncClient: syncClient, + historyClient: historyClient, + crypto: crypto + ) + } + + static func create( + keyserverURL: URL, + logger: ConsoleLogging, + keyValueStorage: KeyValueStorage, + keychainStorage: KeychainStorageProtocol, + groupKeychainStorage: KeychainStorageProtocol, + networkInteractor: NetworkInteracting, + pairingRegisterer: PairingRegisterer, + pushClient: PushClient, + syncClient: SyncClient, + historyClient: HistoryClient, + crypto: CryptoProvider + ) -> NotifyClient { + let kms = KeyManagementService(keychain: keychainStorage) + let subscriptionStore: SyncStore = SyncStoreFactory.create(name: NotifyStorageIdntifiers.notifySubscription, syncClient: syncClient, storage: keyValueStorage) + let subscriptionStoreDelegate = NotifySubscriptionStoreDelegate(networkingInteractor: networkInteractor, kms: kms, groupKeychainStorage: groupKeychainStorage) + let messagesStore = KeyedDatabase(storage: keyValueStorage, identifier: NotifyStorageIdntifiers.notifyMessagesRecords) + let notifyStorage = NotifyStorage(subscriptionStore: subscriptionStore, messagesStore: messagesStore, subscriptionStoreDelegate: subscriptionStoreDelegate) + let coldStartStore = CodableStore(defaults: keyValueStorage, identifier: NotifyStorageIdntifiers.coldStartStore) + let identityClient = IdentityClientFactory.create(keyserver: keyserverURL, keychain: keychainStorage, logger: logger) + let notifySyncService = NotifySyncService(syncClient: syncClient, logger: logger, historyClient: historyClient, identityClient: identityClient, subscriptionsStore: subscriptionStore, messagesStore: messagesStore, networkingInteractor: networkInteractor, kms: kms, coldStartStore: coldStartStore, groupKeychainStorage: groupKeychainStorage) + let notifyMessageSubscriber = NotifyMessageSubscriber(keyserver: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, notifyStorage: notifyStorage, crypto: crypto, logger: logger) + let webDidResolver = WebDidResolver() + let deleteNotifySubscriptionService = DeleteNotifySubscriptionService(keyserver: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, webDidResolver: webDidResolver, kms: kms, logger: logger, notifyStorage: notifyStorage) + let resubscribeService = NotifyResubscribeService(networkInteractor: networkInteractor, notifyStorage: notifyStorage) + + let dappsMetadataStore = CodableStore(defaults: keyValueStorage, identifier: NotifyStorageIdntifiers.dappsMetadataStore) + let subscriptionScopeProvider = SubscriptionScopeProvider() + + let notifySubscribeRequester = NotifySubscribeRequester(keyserverURL: keyserverURL, networkingInteractor: networkInteractor, identityClient: identityClient, logger: logger, kms: kms, webDidResolver: webDidResolver, subscriptionScopeProvider: subscriptionScopeProvider, dappsMetadataStore: dappsMetadataStore) + + let notifySubscribeResponseSubscriber = NotifySubscribeResponseSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, groupKeychainStorage: groupKeychainStorage, notifyStorage: notifyStorage, dappsMetadataStore: dappsMetadataStore, subscriptionScopeProvider: subscriptionScopeProvider) + + let notifyUpdateRequester = NotifyUpdateRequester(keyserverURL: keyserverURL, webDidResolver: webDidResolver, identityClient: identityClient, networkingInteractor: networkInteractor, subscriptionScopeProvider: subscriptionScopeProvider, logger: logger, notifyStorage: notifyStorage) + + let notifyUpdateResponseSubscriber = NotifyUpdateResponseSubscriber(networkingInteractor: networkInteractor, logger: logger, subscriptionScopeProvider: subscriptionScopeProvider, notifyStorage: notifyStorage) + + let deleteNotifySubscriptionSubscriber = DeleteNotifySubscriptionSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, notifyStorage: notifyStorage) + + let subscriptionsAutoUpdater = SubscriptionsAutoUpdater(notifyUpdateRequester: notifyUpdateRequester, logger: logger, notifyStorage: notifyStorage) + + return NotifyClient( + logger: logger, + kms: kms, + pushClient: pushClient, + notifyMessageSubscriber: notifyMessageSubscriber, + notifyStorage: notifyStorage, + notifySyncService: notifySyncService, + deleteNotifySubscriptionService: deleteNotifySubscriptionService, + resubscribeService: resubscribeService, + notifySubscribeRequester: notifySubscribeRequester, + notifySubscribeResponseSubscriber: notifySubscribeResponseSubscriber, + deleteNotifySubscriptionSubscriber: deleteNotifySubscriptionSubscriber, + notifyUpdateRequester: notifyUpdateRequester, + notifyUpdateResponseSubscriber: notifyUpdateResponseSubscriber, + subscriptionsAutoUpdater: subscriptionsAutoUpdater + ) + } +} diff --git a/Sources/WalletConnectPush/Client/Wallet/PushMessageRecord.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyMessageRecord.swift similarity index 60% rename from Sources/WalletConnectPush/Client/Wallet/PushMessageRecord.swift rename to Sources/WalletConnectNotify/Client/Wallet/NotifyMessageRecord.swift index 214d9234e..a9431587a 100644 --- a/Sources/WalletConnectPush/Client/Wallet/PushMessageRecord.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyMessageRecord.swift @@ -1,9 +1,9 @@ import Foundation -public struct PushMessageRecord: Codable, Equatable, DatabaseObject { +public struct NotifyMessageRecord: Codable, Equatable, DatabaseObject { public let id: String public let topic: String - public let message: PushMessage + public let message: NotifyMessage public let publishedAt: Date public var databaseId: String { diff --git a/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift new file mode 100644 index 000000000..23867f8f3 --- /dev/null +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifyStorage.swift @@ -0,0 +1,126 @@ +import Foundation +import Combine + +protocol NotifyStoring { + func getSubscriptions() -> [NotifySubscription] + func getSubscription(topic: String) -> NotifySubscription? + func setSubscription(_ subscription: NotifySubscription) async throws + func deleteSubscription(topic: String) async throws +} + +final class NotifyStorage: NotifyStoring { + + private var publishers = Set() + + private let subscriptionStore: SyncStore + private let messagesStore: KeyedDatabase + + private let newSubscriptionSubject = PassthroughSubject() + private let updateSubscriptionSubject = PassthroughSubject() + private let deleteSubscriptionSubject = PassthroughSubject() + + private let subscriptionStoreDelegate: NotifySubscriptionStoreDelegate + + var newSubscriptionPublisher: AnyPublisher { + return newSubscriptionSubject.eraseToAnyPublisher() + } + + var updateSubscriptionPublisher: AnyPublisher { + return newSubscriptionSubject.eraseToAnyPublisher() + } + + var deleteSubscriptionPublisher: AnyPublisher { + return deleteSubscriptionSubject.eraseToAnyPublisher() + } + + var subscriptionsPublisher: AnyPublisher<[NotifySubscription], Never> { + return subscriptionStore.dataUpdatePublisher + } + + init( + subscriptionStore: SyncStore, + messagesStore: KeyedDatabase, + subscriptionStoreDelegate: NotifySubscriptionStoreDelegate + ) { + self.subscriptionStore = subscriptionStore + self.messagesStore = messagesStore + self.subscriptionStoreDelegate = subscriptionStoreDelegate + setupSubscriptions() + } + + // MARK: Configuration + + func initialize(account: Account) async throws { + try await subscriptionStore.create(for: account) + try subscriptionStore.setupDatabaseSubscriptions(account: account) + } + + func subscribe(account: Account) async throws { + try await subscriptionStore.subscribe(for: account) + } + + // MARK: Subscriptions + + func getSubscriptions() -> [NotifySubscription] { + return subscriptionStore.getAll() + } + + func getSubscription(topic: String) -> NotifySubscription? { + return subscriptionStore.get(for: topic) + } + + func setSubscription(_ subscription: NotifySubscription) 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) + } + + func updateSubscription(_ subscription: NotifySubscription, scope: [String: ScopeValue], expiry: UInt64) async throws { + let expiry = Date(timeIntervalSince1970: TimeInterval(expiry)) + let updated = NotifySubscription(topic: subscription.topic, account: subscription.account, relay: subscription.relay, metadata: subscription.metadata, scope: scope, expiry: expiry, symKey: subscription.symKey) + try await setSubscription(updated) + updateSubscriptionSubject.send(updated) + } + + // MARK: Messages + + func getMessages(topic: String) -> [NotifyMessageRecord] { + return messagesStore.getAll(for: topic) + .sorted{$0.publishedAt > $1.publishedAt} + } + + func deleteMessages(topic: String) { + messagesStore.deleteAll(for: topic) + } + + func deleteMessage(id: String) { + guard let result = messagesStore.find(id: id) else { return } + messagesStore.delete(id: id, for: result.key) + } + + func setMessage(_ record: NotifyMessageRecord) { + messagesStore.set(element: record, for: record.topic) + } +} + +private extension NotifyStorage { + + func setupSubscriptions() { + subscriptionStore.syncUpdatePublisher.sink { [unowned self] (_, _, update) in + switch update { + case .set(let subscription): + subscriptionStoreDelegate.onUpdate(subscription) + newSubscriptionSubject.send(subscription) + case .delete(let object): + subscriptionStoreDelegate.onDelete(object, notifyStorage: self) + deleteSubscriptionSubject.send(object.topic) + case .update(let subscription): + newSubscriptionSubject.send(subscription) + } + }.store(in: &publishers) + } +} diff --git a/Sources/WalletConnectPush/Client/Wallet/PushSubscriptionStoreDelegate.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifySubscriptionStoreDelegate.swift similarity index 80% rename from Sources/WalletConnectPush/Client/Wallet/PushSubscriptionStoreDelegate.swift rename to Sources/WalletConnectNotify/Client/Wallet/NotifySubscriptionStoreDelegate.swift index 5831e2453..77a2c1a4d 100644 --- a/Sources/WalletConnectPush/Client/Wallet/PushSubscriptionStoreDelegate.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifySubscriptionStoreDelegate.swift @@ -1,6 +1,6 @@ import Foundation -final class PushSubscriptionStoreDelegate { +final class NotifySubscriptionStoreDelegate { private let networkingInteractor: NetworkInteracting private let kms: KeyManagementServiceProtocol @@ -12,7 +12,7 @@ final class PushSubscriptionStoreDelegate { self.groupKeychainStorage = groupKeychainStorage } - func onUpdate(_ subscription: PushSubscription) { + func onUpdate(_ subscription: NotifySubscription) { Task(priority: .high) { let symmetricKey = try SymmetricKey(hex: subscription.symKey) try kms.setSymmetricKey(symmetricKey, for: subscription.topic) @@ -21,12 +21,12 @@ final class PushSubscriptionStoreDelegate { } } - func onDelete(_ subscription: PushSubscription, pushStorage: PushStorage) { + func onDelete(_ subscription: NotifySubscription, notifyStorage: NotifyStorage) { Task(priority: .high) { kms.deleteSymmetricKey(for: subscription.topic) try? groupKeychainStorage.delete(key: subscription.topic) networkingInteractor.unsubscribe(topic: subscription.topic) - pushStorage.deleteMessages(topic: subscription.topic) + notifyStorage.deleteMessages(topic: subscription.topic) } } } diff --git a/Sources/WalletConnectPush/Client/Wallet/PushSyncService.swift b/Sources/WalletConnectNotify/Client/Wallet/NotifySyncService.swift similarity index 69% rename from Sources/WalletConnectPush/Client/Wallet/PushSyncService.swift rename to Sources/WalletConnectNotify/Client/Wallet/NotifySyncService.swift index 7b8ab9979..517b81e50 100644 --- a/Sources/WalletConnectPush/Client/Wallet/PushSyncService.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/NotifySyncService.swift @@ -1,12 +1,13 @@ import Foundation -final class PushSyncService { +final class NotifySyncService { private let syncClient: SyncClient private let historyClient: HistoryClient + private let identityClient: IdentityClient private let logger: ConsoleLogging - private let subscriptionsStore: SyncStore - private let messagesStore: KeyedDatabase + private let subscriptionsStore: SyncStore + private let messagesStore: KeyedDatabase private let networkingInteractor: NetworkInteracting private let kms: KeyManagementServiceProtocol private let coldStartStore: CodableStore @@ -16,8 +17,9 @@ final class PushSyncService { syncClient: SyncClient, logger: ConsoleLogging, historyClient: HistoryClient, - subscriptionsStore: SyncStore, - messagesStore: KeyedDatabase, + identityClient: IdentityClient, + subscriptionsStore: SyncStore, + messagesStore: KeyedDatabase, networkingInteractor: NetworkInteracting, kms: KeyManagementServiceProtocol, coldStartStore: CodableStore, @@ -26,6 +28,7 @@ final class PushSyncService { self.syncClient = syncClient self.logger = logger self.historyClient = historyClient + self.identityClient = identityClient self.subscriptionsStore = subscriptionsStore self.messagesStore = messagesStore self.networkingInteractor = networkingInteractor @@ -34,6 +37,10 @@ final class PushSyncService { self.groupKeychainStorage = groupKeychainStorage } + func registerIdentity(account: Account, onSign: @escaping SigningCallback) async throws { + _ = try await identityClient.register(account: account, onSign: onSign) + } + func registerSyncIfNeeded(account: Account, onSign: @escaping SigningCallback) async throws { guard !syncClient.isRegistered(account: account) else { return } @@ -42,9 +49,9 @@ final class PushSyncService { switch result { case .signed(let signature): try await syncClient.register(account: account, signature: signature) - logger.debug("Sync pushSubscriptions store registered and initialized") + logger.debug("Sync notifySubscriptions store registered and initialized") case .rejected: - throw PushError.registerSignatureRejected + throw NotifyError.registerSignatureRejected } } @@ -54,7 +61,7 @@ final class PushSyncService { try await historyClient.register(tags: [ "5000", // sync_set "5002", // sync_delete - "4002" // push_message + "4002" // notify_message ]) let syncTopic = try subscriptionsStore.getStoreTopic(account: account) @@ -65,9 +72,9 @@ final class PushSyncService { direction: .backward ) - let inserts: [PushSubscription] = updates.compactMap { update in + let inserts: [NotifySubscription] = updates.compactMap { update in guard let value = update.value else { return nil } - return try? JSONDecoder().decode(PushSubscription.self, from: Data(value.utf8)) + return try? JSONDecoder().decode(NotifySubscription.self, from: Data(value.utf8)) } let deletions: [String] = updates.compactMap { update in @@ -77,7 +84,9 @@ final class PushSyncService { let subscriptions = inserts.filter { !deletions.contains( $0.databaseId ) } - try subscriptionsStore.setInStore(objects: subscriptions, for: account) + logger.debug("Received object from history: \(subscriptions)") + + try subscriptionsStore.replaceInStore(objects: subscriptions, for: account) for subscription in subscriptions { let symmetricKey = try SymmetricKey(hex: subscription.symKey) @@ -85,17 +94,21 @@ final class PushSyncService { try groupKeychainStorage.add(symmetricKey, forKey: subscription.topic) try await networkingInteractor.subscribe(topic: subscription.topic) - let historyRecords: [HistoryRecord] = try await historyClient.getRecords( + let historyRecords: [HistoryRecord] = try await historyClient.getRecords( topic: subscription.topic, count: 200, direction: .backward ) - let messageRecords = historyRecords.map { record in - return PushMessageRecord( + let messageRecords = historyRecords.compactMap { record in + guard + let (messagePayload, _) = try? NotifyMessagePayload.decodeAndVerify(from: record.object) + else { fatalError() /* TODO: Handle error */ } + + return NotifyMessageRecord( id: record.id.string, topic: subscription.topic, - message: record.object, + message: messagePayload.message, publishedAt: Date() ) } @@ -107,7 +120,7 @@ final class PushSyncService { } } -private extension PushSyncService { +private extension NotifySyncService { struct StoreSetDelete: Codable, Equatable { let key: String diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyMessage/NotifyMessageSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyMessage/NotifyMessageSubscriber.swift new file mode 100644 index 000000000..727751621 --- /dev/null +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyMessage/NotifyMessageSubscriber.swift @@ -0,0 +1,69 @@ +import Foundation +import Combine + +class NotifyMessageSubscriber { + private let keyserver: URL + private let networkingInteractor: NetworkInteracting + private let identityClient: IdentityClient + private let notifyStorage: NotifyStorage + private let crypto: CryptoProvider + private let logger: ConsoleLogging + private var publishers = [AnyCancellable]() + private let notifyMessagePublisherSubject = PassthroughSubject() + + public var notifyMessagePublisher: AnyPublisher { + notifyMessagePublisherSubject.eraseToAnyPublisher() + } + + init(keyserver: URL, networkingInteractor: NetworkInteracting, identityClient: IdentityClient, notifyStorage: NotifyStorage, crypto: CryptoProvider, logger: ConsoleLogging) { + self.keyserver = keyserver + self.networkingInteractor = networkingInteractor + self.identityClient = identityClient + self.notifyStorage = notifyStorage + self.crypto = crypto + self.logger = logger + subscribeForNotifyMessages() + } + + private func subscribeForNotifyMessages() { + let protocolMethod = NotifyMessageProtocolMethod() + networkingInteractor.requestSubscription(on: protocolMethod) + .sink { [unowned self] (payload: RequestSubscriptionPayload) in + + logger.debug("Received Notify Message") + + Task(priority: .high) { + let (messagePayload, claims) = try NotifyMessagePayload.decodeAndVerify(from: payload.request) + let dappPubKey = try DIDKey(did: claims.iss) + let messageData = try JSONEncoder().encode(messagePayload.message) + + let record = NotifyMessageRecord(id: payload.id.string, topic: payload.topic, message: messagePayload.message, publishedAt: payload.publishedAt) + notifyStorage.setMessage(record) + notifyMessagePublisherSubject.send(record) + + let receiptPayload = NotifyMessageReceiptPayload( + keyserver: keyserver, dappPubKey: dappPubKey, + messageHash: crypto.keccak256(messageData).toHexString(), + app: messagePayload.app + ) + + let wrapper = try identityClient.signAndCreateWrapper( + payload: receiptPayload, + account: messagePayload.account + ) + + let response = RPCResponse(id: payload.id, result: wrapper) + + try await networkingInteractor.respond( + topic: payload.topic, + response: response, + protocolMethod: NotifyMessageProtocolMethod() + ) + + logger.debug("Sent Notify Receipt Response") + } + + }.store(in: &publishers) + + } +} diff --git a/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift new file mode 100644 index 000000000..8f4c8a103 --- /dev/null +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift @@ -0,0 +1,66 @@ +import Foundation + +protocol NotifyUpdateRequesting { + func update(topic: String, scope: Set) async throws +} + +class NotifyUpdateRequester: NotifyUpdateRequesting { + enum Errors: Error { + case noSubscriptionForGivenTopic + } + + private let keyserverURL: URL + private let webDidResolver: WebDidResolver + private let identityClient: IdentityClient + private let networkingInteractor: NetworkInteracting + private let subscriptionScopeProvider: SubscriptionScopeProvider + private let logger: ConsoleLogging + private let notifyStorage: NotifyStorage + + init( + keyserverURL: URL, + webDidResolver: WebDidResolver, + identityClient: IdentityClient, + networkingInteractor: NetworkInteracting, + subscriptionScopeProvider: SubscriptionScopeProvider, + logger: ConsoleLogging, + notifyStorage: NotifyStorage + ) { + self.keyserverURL = keyserverURL + self.webDidResolver = webDidResolver + self.identityClient = identityClient + self.networkingInteractor = networkingInteractor + self.subscriptionScopeProvider = subscriptionScopeProvider + self.logger = logger + self.notifyStorage = notifyStorage + } + + func update(topic: String, scope: Set) async throws { + logger.debug("NotifyUpdateRequester: updating subscription for topic: \(topic)") + + guard let subscription = notifyStorage.getSubscription(topic: topic) else { throw Errors.noSubscriptionForGivenTopic } + + let dappPubKey = try await webDidResolver.resolvePublicKey(dappUrl: subscription.metadata.url) + + let request = try createJWTRequest( + dappPubKey: DIDKey(rawData: dappPubKey.rawRepresentation), + subscriptionAccount: subscription.account, + dappUrl: subscription.metadata.url, scope: scope + ) + + let protocolMethod = NotifyUpdateProtocolMethod() + + try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) + } + + private func createJWTRequest(dappPubKey: DIDKey, subscriptionAccount: Account, dappUrl: String, scope: Set) throws -> RPCRequest { + let protocolMethod = NotifyUpdateProtocolMethod().method + let scopeClaim = scope.joined(separator: " ") + let jwtPayload = NotifyUpdatePayload(dappPubKey: dappPubKey, keyserver: keyserverURL, subscriptionAccount: subscriptionAccount, dappUrl: dappUrl, scope: scopeClaim) + let wrapper = try identityClient.signAndCreateWrapper( + payload: jwtPayload, + account: subscriptionAccount + ) + return RPCRequest(method: protocolMethod, params: wrapper) + } +} diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift similarity index 51% rename from Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift rename to Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift index e3b77d8ca..0b84e961d 100644 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateResponseSubscriber.swift @@ -1,56 +1,62 @@ - import Foundation import Combine class NotifyUpdateResponseSubscriber { - enum Errors: Error { - case subscriptionDoesNotExist - } - private let networkingInteractor: NetworkInteracting private var publishers = [AnyCancellable]() private let logger: ConsoleLogging - private let pushStorage: PushStorage + private let notifyStorage: NotifyStorage private let subscriptionScopeProvider: SubscriptionScopeProvider - private var subscriptionPublisherSubject = PassthroughSubject, Never>() - var updateSubscriptionPublisher: AnyPublisher, Never> { + private var subscriptionPublisherSubject = PassthroughSubject, Never>() + var updateSubscriptionPublisher: AnyPublisher, Never> { return subscriptionPublisherSubject.eraseToAnyPublisher() } init(networkingInteractor: NetworkInteracting, logger: ConsoleLogging, subscriptionScopeProvider: SubscriptionScopeProvider, - pushStorage: PushStorage + notifyStorage: NotifyStorage ) { self.networkingInteractor = networkingInteractor self.logger = logger - self.pushStorage = pushStorage + self.notifyStorage = notifyStorage self.subscriptionScopeProvider = subscriptionScopeProvider subscribeForUpdateResponse() } - private func subscribeForUpdateResponse() { + // TODO: handle error response +} + +private extension NotifyUpdateResponseSubscriber { + enum Errors: Error { + case subscriptionDoesNotExist + case selectedScopeNotFound + } + + func subscribeForUpdateResponse() { let protocolMethod = NotifyUpdateProtocolMethod() networkingInteractor.responseSubscription(on: protocolMethod) - .sink {[unowned self] (payload: ResponseSubscriptionPayload) in + .sink {[unowned self] (payload: ResponseSubscriptionPayload) in Task(priority: .high) { - logger.debug("Received Push Update response") + logger.debug("Received Notify Update response") let subscriptionTopic = payload.topic - let (_, claims) = try SubscriptionJWTPayload.decodeAndVerify(from: payload.request) - let scope = try await buildScope(selected: claims.scp, dappUrl: claims.aud) + let (requestPayload, requestClaims) = try NotifyUpdatePayload.decodeAndVerify(from: payload.request) + let (_, _) = try NotifyUpdateResponsePayload.decodeAndVerify(from: payload.response) + + let scope = try await buildScope(selected: requestPayload.scope, dappUrl: requestPayload.dappUrl) - guard let oldSubscription = pushStorage.getSubscription(topic: subscriptionTopic) else { + guard let oldSubscription = notifyStorage.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, symKey: oldSubscription.symKey) + let expiry = Date(timeIntervalSince1970: TimeInterval(requestClaims.exp)) - try await pushStorage.setSubscription(updatedSubscription) + let updatedSubscription = NotifySubscription(topic: subscriptionTopic, account: oldSubscription.account, relay: oldSubscription.relay, metadata: oldSubscription.metadata, scope: scope, expiry: expiry, symKey: oldSubscription.symKey) + + try await notifyStorage.setSubscription(updatedSubscription) subscriptionPublisherSubject.send(.success(updatedSubscription)) @@ -59,13 +65,11 @@ class NotifyUpdateResponseSubscriber { }.store(in: &publishers) } - private func buildScope(selected: String, dappUrl: String) async throws -> [String: ScopeValue] { - let selectedScope = selected - .components(separatedBy: " ") - + func buildScope(selected: String, dappUrl: String) async throws -> [String: ScopeValue] { + let selectedScope = selected.components(separatedBy: " ") let availableScope = try await subscriptionScopeProvider.getSubscriptionScope(dappUrl: dappUrl) - return availableScope.reduce(into: [:]) { $0[$1.name] = ScopeValue(description: $1.description, enabled: selectedScope.contains($1.name)) } + return availableScope.reduce(into: [:]) { + $0[$1.name] = ScopeValue(description: $1.description, enabled: selectedScope.contains($1.name)) + } } - - // TODO: handle error response } diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeRequester.swift similarity index 61% rename from Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift rename to Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeRequester.swift index cfd061084..7a77693ed 100644 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeRequester.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeRequester.swift @@ -1,12 +1,9 @@ import Foundation -class PushSubscribeRequester { +class NotifySubscribeRequester { enum Errors: Error { - case didDocDoesNotContainKeyAgreement - case noVerificationMethodForKey - case unsupportedCurve case signatureRejected } @@ -24,7 +21,7 @@ class PushSubscribeRequester { identityClient: IdentityClient, logger: ConsoleLogging, kms: KeyManagementService, - webDidResolver: WebDidResolver = WebDidResolver(), + webDidResolver: WebDidResolver, subscriptionScopeProvider: SubscriptionScopeProvider, dappsMetadataStore: CodableStore ) { @@ -38,35 +35,36 @@ class PushSubscribeRequester { self.dappsMetadataStore = dappsMetadataStore } - @discardableResult func subscribe(metadata: AppMetadata, account: Account, onSign: @escaping SigningCallback) async throws -> SubscriptionJWTPayload.Wrapper { + @discardableResult func subscribe(metadata: AppMetadata, account: Account, onSign: @escaping SigningCallback) async throws -> NotifySubscriptionPayload.Wrapper { let dappUrl = metadata.url - logger.debug("Subscribing for Push") + logger.debug("Subscribing for Notify") - let peerPublicKey = try await resolvePublicKey(dappUrl: metadata.url) + let peerPublicKey = try await webDidResolver.resolvePublicKey(dappUrl: metadata.url) let subscribeTopic = peerPublicKey.rawRepresentation.sha256().toHexString() let keysY = try generateAgreementKeys(peerPublicKey: peerPublicKey) - let responseTopic = keysY.derivedTopic() + let responseTopic = keysY.derivedTopic() dappsMetadataStore.set(metadata, forKey: responseTopic) try kms.setSymmetricKey(keysY.sharedKey, for: subscribeTopic) - - _ = try await identityClient.register(account: account, onSign: onSign) - try kms.setAgreementSecret(keysY, topic: responseTopic) logger.debug("setting symm key for response topic \(responseTopic)") - let protocolMethod = PushSubscribeProtocolMethod() + let protocolMethod = NotifySubscribeProtocolMethod() - let subscriptionAuthWrapper = try await createJWTWrapper(subscriptionAccount: account, dappUrl: dappUrl) + let subscriptionAuthWrapper = try await createJWTWrapper( + dappPubKey: DIDKey(did: peerPublicKey.did), + subscriptionAccount: account, + dappUrl: dappUrl + ) let request = RPCRequest(method: protocolMethod.method, params: subscriptionAuthWrapper) - logger.debug("PushSubscribeRequester: subscribing to response topic: \(responseTopic)") + logger.debug("NotifySubscribeRequester: subscribing to response topic: \(responseTopic)") try await networkingInteractor.subscribe(topic: responseTopic) @@ -74,17 +72,6 @@ class PushSubscribeRequester { return subscriptionAuthWrapper } - private func resolvePublicKey(dappUrl: String) async throws -> AgreementPublicKey { - logger.debug("PushSubscribeRequester: Resolving DIDDoc for: \(dappUrl)") - let didDoc = try await webDidResolver.resolveDidDoc(domainUrl: dappUrl) - guard let keyAgreement = didDoc.keyAgreement.first else { throw Errors.didDocDoesNotContainKeyAgreement } - guard let verificationMethod = didDoc.verificationMethod.first(where: { verificationMethod in verificationMethod.id == keyAgreement }) else { throw Errors.noVerificationMethodForKey } - guard verificationMethod.publicKeyJwk.crv == .X25519 else { throw Errors.unsupportedCurve} - let pubKeyBase64Url = verificationMethod.publicKeyJwk.x - return try AgreementPublicKey(base64url: pubKeyBase64Url) - } - - private func generateAgreementKeys(peerPublicKey: AgreementPublicKey) throws -> AgreementKeys { let selfPubKey = try kms.createX25519KeyPair() @@ -92,10 +79,10 @@ class PushSubscribeRequester { return keys } - private func createJWTWrapper(subscriptionAccount: Account, dappUrl: String) async throws -> SubscriptionJWTPayload.Wrapper { + private func createJWTWrapper(dappPubKey: DIDKey, subscriptionAccount: Account, dappUrl: String) async throws -> NotifySubscriptionPayload.Wrapper { let types = try await subscriptionScopeProvider.getSubscriptionScope(dappUrl: dappUrl) let scope = types.map{$0.name}.joined(separator: " ") - let jwtPayload = SubscriptionJWTPayload(keyserver: keyserverURL, subscriptionAccount: subscriptionAccount, dappUrl: dappUrl, scope: scope) + let jwtPayload = NotifySubscriptionPayload(dappPubKey: dappPubKey, keyserver: keyserverURL, subscriptionAccount: subscriptionAccount, dappUrl: dappUrl, scope: scope) return try identityClient.signAndCreateWrapper( payload: jwtPayload, account: subscriptionAccount diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeResponseSubscriber.swift b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeResponseSubscriber.swift similarity index 63% rename from Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeResponseSubscriber.swift rename to Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeResponseSubscriber.swift index d8c5fe66c..48d32401b 100644 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushSubscribe/PushSubscribeResponseSubscriber.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/ProtocolEngine/wc_pushSubscribe/NotifySubscribeResponseSubscriber.swift @@ -1,7 +1,7 @@ import Foundation import Combine -class PushSubscribeResponseSubscriber { +class NotifySubscribeResponseSubscriber { enum Errors: Error { case couldNotCreateSubscription } @@ -16,7 +16,7 @@ class PushSubscribeResponseSubscriber { private let kms: KeyManagementServiceProtocol private var publishers = [AnyCancellable]() private let logger: ConsoleLogging - private let pushStorage: PushStorage + private let notifyStorage: NotifyStorage private let groupKeychainStorage: KeychainStorageProtocol private let dappsMetadataStore: CodableStore private let subscriptionScopeProvider: SubscriptionScopeProvider @@ -25,7 +25,7 @@ class PushSubscribeResponseSubscriber { kms: KeyManagementServiceProtocol, logger: ConsoleLogging, groupKeychainStorage: KeychainStorageProtocol, - pushStorage: PushStorage, + notifyStorage: NotifyStorage, dappsMetadataStore: CodableStore, subscriptionScopeProvider: SubscriptionScopeProvider ) { @@ -33,65 +33,69 @@ class PushSubscribeResponseSubscriber { self.kms = kms self.logger = logger self.groupKeychainStorage = groupKeychainStorage - self.pushStorage = pushStorage + self.notifyStorage = notifyStorage self.dappsMetadataStore = dappsMetadataStore self.subscriptionScopeProvider = subscriptionScopeProvider subscribeForSubscriptionResponse() } private func subscribeForSubscriptionResponse() { - let protocolMethod = PushSubscribeProtocolMethod() + let protocolMethod = NotifySubscribeProtocolMethod() networkingInteractor.responseSubscription(on: protocolMethod) - .sink {[unowned self] (payload: ResponseSubscriptionPayload) in + .sink { [unowned self] (payload: ResponseSubscriptionPayload) in Task(priority: .high) { - logger.debug("PushSubscribeResponseSubscriber: Received Push Subscribe response") + logger.debug("NotifySubscribeResponseSubscriber: Received Notify Subscribe response") + + guard + let (responsePayload, _) = try? NotifySubscriptionResponsePayload.decodeAndVerify(from: payload.response) + else { fatalError() /* TODO: Handle error */ } guard let responseKeys = kms.getAgreementSecret(for: payload.topic) else { - logger.debug("PushSubscribeResponseSubscriber: no symmetric key for topic \(payload.topic)") + logger.debug("NotifySubscribeResponseSubscriber: no symmetric key for topic \(payload.topic)") return subscriptionErrorSubject.send(Errors.couldNotCreateSubscription) } // get keypair Y let pubKeyY = responseKeys.publicKey - let peerPubKeyZ = payload.response.publicKey + let peerPubKeyZ = responsePayload.publicKey.hexString var account: Account! var metadata: AppMetadata! - var pushSubscriptionTopic: String! + var notifySubscriptionTopic: String! var subscribedTypes: Set! var agreementKeysP: AgreementKeys! - let (subscriptionPayload, claims) = try SubscriptionJWTPayload.decodeAndVerify(from: payload.request) + let (subscriptionPayload, claims) = try NotifySubscriptionPayload.decodeAndVerify(from: payload.request) let subscribedScope = subscriptionPayload.scope .components(separatedBy: " ") do { // generate symm key P agreementKeysP = try kms.performKeyAgreement(selfPublicKey: pubKeyY, peerPublicKey: peerPubKeyZ) - pushSubscriptionTopic = agreementKeysP.derivedTopic() - try kms.setAgreementSecret(agreementKeysP, topic: pushSubscriptionTopic) - try groupKeychainStorage.add(agreementKeysP, forKey: pushSubscriptionTopic) + notifySubscriptionTopic = agreementKeysP.derivedTopic() + try kms.setAgreementSecret(agreementKeysP, topic: notifySubscriptionTopic) + try groupKeychainStorage.add(agreementKeysP, forKey: notifySubscriptionTopic) account = try Account(DIDPKHString: claims.sub) metadata = try dappsMetadataStore.get(key: payload.topic) let availableTypes = try await subscriptionScopeProvider.getSubscriptionScope(dappUrl: metadata!.url) subscribedTypes = availableTypes.filter{subscribedScope.contains($0.name)} - logger.debug("PushSubscribeResponseSubscriber: subscribing push subscription topic: \(pushSubscriptionTopic!)") - try await networkingInteractor.subscribe(topic: pushSubscriptionTopic) + logger.debug("NotifySubscribeResponseSubscriber: subscribing notify subscription topic: \(notifySubscriptionTopic!)") + try await networkingInteractor.subscribe(topic: notifySubscriptionTopic) } catch { - logger.debug("PushSubscribeResponseSubscriber: error: \(error)") + logger.debug("NotifySubscribeResponseSubscriber: error: \(error)") return subscriptionErrorSubject.send(Errors.couldNotCreateSubscription) } guard let metadata = metadata else { - logger.debug("PushSubscribeResponseSubscriber: no metadata for topic: \(pushSubscriptionTopic!)") + logger.debug("NotifySubscribeResponseSubscriber: no metadata for topic: \(notifySubscriptionTopic!)") 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, symKey: agreementKeysP.sharedKey.hexRepresentation) + let notifySubscription = NotifySubscription(topic: notifySubscriptionTopic, account: account, relay: RelayProtocolOptions(protocol: "irn", data: nil), metadata: metadata, scope: scope, expiry: expiry, symKey: agreementKeysP.sharedKey.hexRepresentation) - try await pushStorage.setSubscription(pushSubscription) + try await notifyStorage.setSubscription(notifySubscription) - logger.debug("PushSubscribeResponseSubscriber: unsubscribing response topic: \(payload.topic)") + logger.debug("NotifySubscribeResponseSubscriber: unsubscribing response topic: \(payload.topic)") networkingInteractor.unsubscribe(topic: payload.topic) } }.store(in: &publishers) diff --git a/Sources/WalletConnectPush/Client/Wallet/SubscriptionScopeProvider.swift b/Sources/WalletConnectNotify/Client/Wallet/SubscriptionScopeProvider.swift similarity index 92% rename from Sources/WalletConnectPush/Client/Wallet/SubscriptionScopeProvider.swift rename to Sources/WalletConnectNotify/Client/Wallet/SubscriptionScopeProvider.swift index a2140addd..a362dd4e6 100644 --- a/Sources/WalletConnectPush/Client/Wallet/SubscriptionScopeProvider.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/SubscriptionScopeProvider.swift @@ -12,7 +12,7 @@ class SubscriptionScopeProvider { if let availableScope = cache[dappUrl] { return availableScope } - guard let scopeUrl = URL(string: "\(dappUrl)/.well-known/wc-push-config.json") else { throw Errors.invalidUrl } + guard let scopeUrl = URL(string: "\(dappUrl)/.well-known/wc-notify-config.json") else { throw Errors.invalidUrl } let (data, _) = try await URLSession.shared.data(from: scopeUrl) let config = try JSONDecoder().decode(NotificationConfig.self, from: data) let availableScope = Set(config.types) diff --git a/Sources/WalletConnectPush/Client/Wallet/SubscriptionsAutoUpdater.swift b/Sources/WalletConnectNotify/Client/Wallet/SubscriptionsAutoUpdater.swift similarity index 83% rename from Sources/WalletConnectPush/Client/Wallet/SubscriptionsAutoUpdater.swift rename to Sources/WalletConnectNotify/Client/Wallet/SubscriptionsAutoUpdater.swift index d6e939a5b..4eff66e89 100644 --- a/Sources/WalletConnectPush/Client/Wallet/SubscriptionsAutoUpdater.swift +++ b/Sources/WalletConnectNotify/Client/Wallet/SubscriptionsAutoUpdater.swift @@ -4,19 +4,19 @@ import Foundation class SubscriptionsAutoUpdater { private let notifyUpdateRequester: NotifyUpdateRequesting private let logger: ConsoleLogging - private let pushStorage: PushStoring + private let notifyStorage: NotifyStoring init(notifyUpdateRequester: NotifyUpdateRequesting, logger: ConsoleLogging, - pushStorage: PushStoring) { + notifyStorage: NotifyStoring) { self.notifyUpdateRequester = notifyUpdateRequester self.logger = logger - self.pushStorage = pushStorage + self.notifyStorage = notifyStorage updateSubscriptionsIfNeeded() } private func updateSubscriptionsIfNeeded() { - for subscription in pushStorage.getSubscriptions() { + for subscription in notifyStorage.getSubscriptions() { if shouldUpdate(subscription: subscription) { let scope = Set(subscription.scope.filter{ $0.value.enabled == true }.keys) let topic = subscription.topic @@ -31,7 +31,7 @@ class SubscriptionsAutoUpdater { } } - private func shouldUpdate(subscription: PushSubscription) -> Bool { + private func shouldUpdate(subscription: NotifySubscription) -> Bool { let currentDate = Date() let calendar = Calendar.current let expiryDate = subscription.expiry diff --git a/Sources/WalletConnectNotify/Client/Wallet/WebDidResolver.swift b/Sources/WalletConnectNotify/Client/Wallet/WebDidResolver.swift new file mode 100644 index 000000000..2f27c2693 --- /dev/null +++ b/Sources/WalletConnectNotify/Client/Wallet/WebDidResolver.swift @@ -0,0 +1,29 @@ +import Foundation + +final class WebDidResolver { + + func resolvePublicKey(dappUrl: String) async throws -> AgreementPublicKey { + let didDoc = try await resolveDidDoc(domainUrl: dappUrl) + guard let keyAgreement = didDoc.keyAgreement.first else { throw Errors.didDocDoesNotContainKeyAgreement } + guard let verificationMethod = didDoc.verificationMethod.first(where: { verificationMethod in verificationMethod.id == keyAgreement }) else { throw Errors.noVerificationMethodForKey } + guard verificationMethod.publicKeyJwk.crv == .X25519 else { throw Errors.unsupportedCurve} + let pubKeyBase64Url = verificationMethod.publicKeyJwk.x + return try AgreementPublicKey(base64url: pubKeyBase64Url) + } +} + +private extension WebDidResolver { + + enum Errors: Error { + case invalidUrl + case didDocDoesNotContainKeyAgreement + case noVerificationMethodForKey + case unsupportedCurve + } + + func resolveDidDoc(domainUrl: String) async throws -> WebDidDoc { + guard let didDocUrl = URL(string: "\(domainUrl)/.well-known/did.json") else { throw Errors.invalidUrl } + let (data, _) = try await URLSession.shared.data(from: didDocUrl) + return try JSONDecoder().decode(WebDidDoc.self, from: data) + } +} diff --git a/Sources/WalletConnectNotify/Notify.swift b/Sources/WalletConnectNotify/Notify.swift new file mode 100644 index 000000000..129291010 --- /dev/null +++ b/Sources/WalletConnectNotify/Notify.swift @@ -0,0 +1,28 @@ +import Foundation + +public class Notify { + public static var instance: NotifyClient = { + guard let config = Notify.config else { + fatalError("Error - you must call Notify.configure(_:) before accessing the shared wallet instance.") + } + Push.configure(pushHost: config.pushHost, environment: config.environment) + return NotifyClientFactory.create( + networkInteractor: Networking.interactor, + pairingRegisterer: Pair.registerer, + pushClient: Push.instance, + syncClient: Sync.instance, + historyClient: History.instance, + crypto: config.crypto + ) + }() + + private static var config: Config? + + private init() { } + + /// Wallet's configuration method + static public func configure(pushHost: String = "echo.walletconnect.com", environment: APNSEnvironment, crypto: CryptoProvider) { + Notify.config = Notify.Config(pushHost: pushHost, environment: environment, crypto: crypto) + } + +} diff --git a/Sources/WalletConnectEcho/EchoConfig.swift b/Sources/WalletConnectNotify/NotifyConfig.swift similarity index 51% rename from Sources/WalletConnectEcho/EchoConfig.swift rename to Sources/WalletConnectNotify/NotifyConfig.swift index 595ea2b73..479c830ab 100644 --- a/Sources/WalletConnectEcho/EchoConfig.swift +++ b/Sources/WalletConnectNotify/NotifyConfig.swift @@ -1,8 +1,9 @@ import Foundation -extension Echo { +extension Notify { struct Config { - let echoHost: String + let pushHost: String let environment: APNSEnvironment + let crypto: CryptoProvider } } diff --git a/Sources/WalletConnectNotify/NotifyImports.swift b/Sources/WalletConnectNotify/NotifyImports.swift new file mode 100644 index 000000000..1c0a1db16 --- /dev/null +++ b/Sources/WalletConnectNotify/NotifyImports.swift @@ -0,0 +1,7 @@ +#if !CocoaPods +@_exported import WalletConnectPairing +@_exported import WalletConnectPush +@_exported import WalletConnectIdentity +@_exported import WalletConnectSync +@_exported import WalletConnectHistory +#endif diff --git a/Sources/WalletConnectNotify/NotifyStorageIdntifiers.swift b/Sources/WalletConnectNotify/NotifyStorageIdntifiers.swift new file mode 100644 index 000000000..fb1b21c53 --- /dev/null +++ b/Sources/WalletConnectNotify/NotifyStorageIdntifiers.swift @@ -0,0 +1,9 @@ +import Foundation + +enum NotifyStorageIdntifiers { + static let notifySubscription = "com.walletconnect.notify.notifySubscription" + + static let notifyMessagesRecords = "com.walletconnect.sdk.notifyMessagesRecords" + static let dappsMetadataStore = "com.walletconnect.sdk.dappsMetadataStore" + static let coldStartStore = "com.walletconnect.sdk.coldStartStore" +} diff --git a/Sources/WalletConnectPush/ProtocolMethods/PushDeleteProtocolMethod.swift b/Sources/WalletConnectNotify/ProtocolMethods/NotifyDeleteProtocolMethod.swift similarity index 64% rename from Sources/WalletConnectPush/ProtocolMethods/PushDeleteProtocolMethod.swift rename to Sources/WalletConnectNotify/ProtocolMethods/NotifyDeleteProtocolMethod.swift index dd90fe7e2..79e123797 100644 --- a/Sources/WalletConnectPush/ProtocolMethods/PushDeleteProtocolMethod.swift +++ b/Sources/WalletConnectNotify/ProtocolMethods/NotifyDeleteProtocolMethod.swift @@ -1,7 +1,7 @@ import Foundation -struct PushDeleteProtocolMethod: ProtocolMethod { - let method: String = "wc_pushDelete" +struct NotifyDeleteProtocolMethod: ProtocolMethod { + let method: String = "wc_notifyDelete" let requestConfig = RelayConfig(tag: 4004, prompt: false, ttl: 86400) diff --git a/Sources/WalletConnectPush/ProtocolMethods/PushMessageProtocolMethod.swift b/Sources/WalletConnectNotify/ProtocolMethods/NotifyMessageProtocolMethod.swift similarity index 58% rename from Sources/WalletConnectPush/ProtocolMethods/PushMessageProtocolMethod.swift rename to Sources/WalletConnectNotify/ProtocolMethods/NotifyMessageProtocolMethod.swift index 6345d1dc8..361aec841 100644 --- a/Sources/WalletConnectPush/ProtocolMethods/PushMessageProtocolMethod.swift +++ b/Sources/WalletConnectNotify/ProtocolMethods/NotifyMessageProtocolMethod.swift @@ -1,9 +1,9 @@ import Foundation -struct PushMessageProtocolMethod: ProtocolMethod { - let method: String = "wc_pushMessage" +struct NotifyMessageProtocolMethod: ProtocolMethod { + let method: String = "wc_notifyMessage" let requestConfig: RelayConfig = RelayConfig(tag: 4002, prompt: true, ttl: 2592000) - let responseConfig: RelayConfig = RelayConfig(tag: 4003, prompt: true, ttl: 2592000) + let responseConfig: RelayConfig = RelayConfig(tag: 4003, prompt: false, ttl: 2592000) } diff --git a/Sources/WalletConnectNotify/ProtocolMethods/NotifySubscribeProtocolMethod.swift b/Sources/WalletConnectNotify/ProtocolMethods/NotifySubscribeProtocolMethod.swift new file mode 100644 index 000000000..efb256dd7 --- /dev/null +++ b/Sources/WalletConnectNotify/ProtocolMethods/NotifySubscribeProtocolMethod.swift @@ -0,0 +1,10 @@ + +import Foundation + +struct NotifySubscribeProtocolMethod: ProtocolMethod { + let method: String = "wc_notifySubscribe" + + let requestConfig: RelayConfig = RelayConfig(tag: 4000, prompt: false, ttl: 86400) + + let responseConfig: RelayConfig = RelayConfig(tag: 4001, prompt: false, ttl: 86400) +} diff --git a/Sources/WalletConnectPush/ProtocolMethods/NotifyUpdateProtocolMethod.swift b/Sources/WalletConnectNotify/ProtocolMethods/NotifyUpdateProtocolMethod.swift similarity index 69% rename from Sources/WalletConnectPush/ProtocolMethods/NotifyUpdateProtocolMethod.swift rename to Sources/WalletConnectNotify/ProtocolMethods/NotifyUpdateProtocolMethod.swift index 198ec859f..01ed5a6d8 100644 --- a/Sources/WalletConnectPush/ProtocolMethods/NotifyUpdateProtocolMethod.swift +++ b/Sources/WalletConnectNotify/ProtocolMethods/NotifyUpdateProtocolMethod.swift @@ -2,10 +2,10 @@ import Foundation struct NotifyUpdateProtocolMethod: ProtocolMethod { - let method: String = "wc_pushUpdate" + let method: String = "wc_notifyUpdate" - let requestConfig: RelayConfig = RelayConfig(tag: 4008, prompt: true, ttl: 86400) + let requestConfig: RelayConfig = RelayConfig(tag: 4008, prompt: false, ttl: 86400) - let responseConfig: RelayConfig = RelayConfig(tag: 4009, prompt: true, ttl: 86400) + let responseConfig: RelayConfig = RelayConfig(tag: 4009, prompt: false, ttl: 86400) } diff --git a/Sources/WalletConnectNotify/RPCRequests/NotifyDeleteParams.swift b/Sources/WalletConnectNotify/RPCRequests/NotifyDeleteParams.swift new file mode 100644 index 000000000..7b998c753 --- /dev/null +++ b/Sources/WalletConnectNotify/RPCRequests/NotifyDeleteParams.swift @@ -0,0 +1,10 @@ +import Foundation + +public struct NotifyDeleteParams: Codable { + let code: Int + let message: String + + static var userDisconnected: NotifyDeleteParams { + return NotifyDeleteParams(code: 6000, message: "User Disconnected") + } +} diff --git a/Sources/WalletConnectPush/RPCRequests/NotifyProposeParams.swift b/Sources/WalletConnectNotify/RPCRequests/NotifyProposeParams.swift similarity index 100% rename from Sources/WalletConnectPush/RPCRequests/NotifyProposeParams.swift rename to Sources/WalletConnectNotify/RPCRequests/NotifyProposeParams.swift diff --git a/Sources/WalletConnectPush/RPCRequests/NotifyProposeResponseParams.swift b/Sources/WalletConnectNotify/RPCRequests/NotifyProposeResponseParams.swift similarity index 100% rename from Sources/WalletConnectPush/RPCRequests/NotifyProposeResponseParams.swift rename to Sources/WalletConnectNotify/RPCRequests/NotifyProposeResponseParams.swift diff --git a/Sources/WalletConnectPush/RPCRequests/SubscribeResponseParams.swift b/Sources/WalletConnectNotify/RPCRequests/SubscribeResponseParams.swift similarity index 100% rename from Sources/WalletConnectPush/RPCRequests/SubscribeResponseParams.swift rename to Sources/WalletConnectNotify/RPCRequests/SubscribeResponseParams.swift diff --git a/Sources/WalletConnectPush/Types/NotificationConfig.swift b/Sources/WalletConnectNotify/Types/NotificationConfig.swift similarity index 99% rename from Sources/WalletConnectPush/Types/NotificationConfig.swift rename to Sources/WalletConnectNotify/Types/NotificationConfig.swift index c53e9b674..f17133c93 100644 --- a/Sources/WalletConnectPush/Types/NotificationConfig.swift +++ b/Sources/WalletConnectNotify/Types/NotificationConfig.swift @@ -5,5 +5,4 @@ struct NotificationConfig: Codable { let version: Int let lastModified: TimeInterval let types: [NotificationType] - } diff --git a/Sources/WalletConnectPush/Types/NotificationType.swift b/Sources/WalletConnectNotify/Types/NotificationType.swift similarity index 100% rename from Sources/WalletConnectPush/Types/NotificationType.swift rename to Sources/WalletConnectNotify/Types/NotificationType.swift diff --git a/Sources/WalletConnectPush/Types/PushError.swift b/Sources/WalletConnectNotify/Types/NotifyError.swift similarity index 90% rename from Sources/WalletConnectPush/Types/PushError.swift rename to Sources/WalletConnectNotify/Types/NotifyError.swift index 82c16c00e..31d567893 100644 --- a/Sources/WalletConnectPush/Types/PushError.swift +++ b/Sources/WalletConnectNotify/Types/NotifyError.swift @@ -1,13 +1,13 @@ import Foundation -public enum PushError: Codable, Equatable, Error { +public enum NotifyError: Codable, Equatable, Error { case userRejeted case userHasExistingSubscription case methodUnsupported case registerSignatureRejected } -extension PushError: Reason { +extension NotifyError: Reason { init?(code: Int) { switch code { @@ -41,7 +41,7 @@ extension PushError: Reason { case .methodUnsupported: return "Method Unsupported" case .userRejeted: - return "Push request rejected" + return "Notify request rejected" case .userHasExistingSubscription: return "User Has Existing Subscription" case .registerSignatureRejected: diff --git a/Sources/WalletConnectPush/Types/PushMessage.swift b/Sources/WalletConnectNotify/Types/NotifyMessage.swift similarity index 88% rename from Sources/WalletConnectPush/Types/PushMessage.swift rename to Sources/WalletConnectNotify/Types/NotifyMessage.swift index d48604937..783721bbd 100644 --- a/Sources/WalletConnectPush/Types/PushMessage.swift +++ b/Sources/WalletConnectNotify/Types/NotifyMessage.swift @@ -1,6 +1,6 @@ import Foundation -public struct PushMessage: Codable, Equatable { +public struct NotifyMessage: Codable, Equatable { public let title: String public let body: String public let icon: String diff --git a/Sources/WalletConnectNotify/Types/NotifyRequest.swift b/Sources/WalletConnectNotify/Types/NotifyRequest.swift new file mode 100644 index 000000000..be2a15c42 --- /dev/null +++ b/Sources/WalletConnectNotify/Types/NotifyRequest.swift @@ -0,0 +1,3 @@ +import Foundation + +public typealias NotifyRequest = (id: RPCID, account: Account, metadata: AppMetadata) diff --git a/Sources/WalletConnectPush/Types/PushSubscription.swift b/Sources/WalletConnectNotify/Types/NotifySubscription.swift similarity index 89% rename from Sources/WalletConnectPush/Types/PushSubscription.swift rename to Sources/WalletConnectNotify/Types/NotifySubscription.swift index 756d33781..b44189c80 100644 --- a/Sources/WalletConnectPush/Types/PushSubscription.swift +++ b/Sources/WalletConnectNotify/Types/NotifySubscription.swift @@ -1,6 +1,6 @@ import Foundation -public struct PushSubscription: DatabaseObject { +public struct NotifySubscription: DatabaseObject { public let topic: String public let account: Account public let relay: RelayProtocolOptions diff --git a/Sources/WalletConnectNotify/Types/NotifySubscriptionResult.swift b/Sources/WalletConnectNotify/Types/NotifySubscriptionResult.swift new file mode 100644 index 000000000..c9df6a3de --- /dev/null +++ b/Sources/WalletConnectNotify/Types/NotifySubscriptionResult.swift @@ -0,0 +1,7 @@ + +import Foundation + +public struct NotifySubscriptionResult: Equatable, Codable { + public let notifySubscription: NotifySubscription + public let subscriptionAuth: String +} diff --git a/Sources/WalletConnectNotify/Types/Payload/NotifyDeletePayload.swift b/Sources/WalletConnectNotify/Types/Payload/NotifyDeletePayload.swift new file mode 100644 index 000000000..62aa74204 --- /dev/null +++ b/Sources/WalletConnectNotify/Types/Payload/NotifyDeletePayload.swift @@ -0,0 +1,77 @@ +import Foundation + +struct NotifyDeletePayload: JWTClaimsCodable { + + struct Claims: JWTClaims { + /// Timestamp when JWT was issued + let iat: UInt64 + /// Timestamp when JWT must expire + let exp: UInt64 + /// Key server URL + let ksu: String + /// Description of action intent. Must be equal to `notify_delete` + let act: String? + + /// `did:key` of an identity key. Enables to resolve attached blockchain account. + let iss: String + /// `did:key` of an identity key. Enables to resolve associated Dapp domain used. + let aud: String + /// Reason for deleting the subscription + let sub: String + /// Dapp's domain url + let app: String + + static var action: String? { + return "notify_delete" + } + } + + struct Wrapper: JWTWrapper { + let deleteAuth: String + + init(jwtString: String) { + self.deleteAuth = jwtString + } + + var jwtString: String { + return deleteAuth + } + } + + let keyserver: URL + let dappPubKey: DIDKey + let reason: String + let app: String + + init( + keyserver: URL, + dappPubKey: DIDKey, + reason: String, + app: String + ) { + self.keyserver = keyserver + self.dappPubKey = dappPubKey + self.reason = reason + self.app = app + } + + init(claims: Claims) throws { + self.keyserver = try claims.ksu.asURL() + self.dappPubKey = try DIDKey(did: claims.aud) + self.reason = claims.sub + self.app = claims.app + } + + func encode(iss: String) throws -> Claims { + return Claims( + iat: defaultIat(), + exp: expiry(days: 1), + ksu: keyserver.absoluteString, + act: Claims.action, + iss: iss, + aud: dappPubKey.did(variant: .ED25519), + sub: reason, + app: app + ) + } +} diff --git a/Sources/WalletConnectNotify/Types/Payload/NotifyDeleteResponsePayload.swift b/Sources/WalletConnectNotify/Types/Payload/NotifyDeleteResponsePayload.swift new file mode 100644 index 000000000..338ff414e --- /dev/null +++ b/Sources/WalletConnectNotify/Types/Payload/NotifyDeleteResponsePayload.swift @@ -0,0 +1,77 @@ +import Foundation + +struct NotifyDeleteResponsePayload: JWTClaimsCodable { + + struct Claims: JWTClaims { + /// Timestamp when JWT was issued + let iat: UInt64 + /// Timestamp when JWT must expire + let exp: UInt64 + /// Key server URL + let ksu: String + /// Description of action intent. Must be equal to `notify_delete_response` + let act: String? + + /// `did:key` of an identity key. Enables to resolve associated Dapp domain used + let iss: String + /// `did:key` of an identity key. Enables to resolve attached blockchain account. + let aud: String + /// Hash of the existing subscription payload + let sub: String + /// Dapp's domain url + let app: String + + static var action: String? { + return "notify_delete_response" + } + } + + struct Wrapper: JWTWrapper { + let responseAuth: String + + init(jwtString: String) { + self.responseAuth = jwtString + } + + var jwtString: String { + return responseAuth + } + } + + let keyserver: URL + let selfPubKey: DIDKey + let subscriptionHash: String + let app: String + + init( + keyserver: URL, + selfPubKey: DIDKey, + subscriptionHash: String, + app: String + ) { + self.keyserver = keyserver + self.selfPubKey = selfPubKey + self.subscriptionHash = subscriptionHash + self.app = app + } + + init(claims: Claims) throws { + self.keyserver = try claims.ksu.asURL() + self.selfPubKey = try DIDKey(did: claims.aud) + self.subscriptionHash = claims.sub + self.app = claims.app + } + + func encode(iss: String) throws -> Claims { + return Claims( + iat: defaultIat(), + exp: expiry(days: 1), + ksu: keyserver.absoluteString, + act: Claims.action, + iss: iss, + aud: selfPubKey.did(variant: .ED25519), + sub: subscriptionHash, + app: app + ) + } +} diff --git a/Sources/WalletConnectNotify/Types/Payload/NotifyMessagePayload.swift b/Sources/WalletConnectNotify/Types/Payload/NotifyMessagePayload.swift new file mode 100644 index 000000000..e932f80fe --- /dev/null +++ b/Sources/WalletConnectNotify/Types/Payload/NotifyMessagePayload.swift @@ -0,0 +1,89 @@ +import Foundation + +struct NotifyMessagePayload: JWTClaimsCodable { + + struct Claims: JWTClaims { + /// Timestamp when JWT was issued + let iat: UInt64 + /// Timestamp when JWT must expire + let exp: UInt64 + /// Key server URL + let ksu: String + /// Action intent (must be `notify_message`) + let act: String? + + /// `did:key` of an identity key. Enables to resolve associated Dapp domain used. diddoc authentication key + let iss: String + /// Blockchain account `did:pkh` + let aud: String + /// Subscription ID (sha256 hash of subscriptionAuth) + let sub: String + /// Dapp domain url + let app: String + /// Message object + let msg: NotifyMessage + + static var action: String? { + return "notify_message" + } + } + + struct Wrapper: JWTWrapper { + let messageAuth: String + + init(jwtString: String) { + self.messageAuth = jwtString + } + + var jwtString: String { + return messageAuth + } + } + + let castServerPubKey: DIDKey + let keyserver: URL + let account: Account + let subscriptionId: String + let app: String + let message: NotifyMessage + + init( + castServerPubKey: DIDKey, + keyserver: URL, + account: Account, + subscriptionId: String, + app: String, + message: NotifyMessage + ) { + self.castServerPubKey = castServerPubKey + self.keyserver = keyserver + self.account = account + self.subscriptionId = subscriptionId + self.app = app + self.message = message + } + + init(claims: Claims) throws { + self.castServerPubKey = try DIDKey(did: claims.iss) + self.keyserver = try claims.ksu.asURL() + self.account = try DIDPKH(did: claims.aud).account + self.subscriptionId = claims.sub + self.app = claims.app + self.message = claims.msg + } + + func encode(iss: String) throws -> Claims { + return Claims( + iat: defaultIat(), + exp: expiry(days: 1), + ksu: keyserver.absoluteString, + act: Claims.action, + iss: castServerPubKey.multibase(variant: .ED25519), + aud: account.did, + sub: subscriptionId, + app: app, + msg: message + ) + } + +} diff --git a/Sources/WalletConnectNotify/Types/Payload/NotifyMessageReceiptPayload.swift b/Sources/WalletConnectNotify/Types/Payload/NotifyMessageReceiptPayload.swift new file mode 100644 index 000000000..57934d03c --- /dev/null +++ b/Sources/WalletConnectNotify/Types/Payload/NotifyMessageReceiptPayload.swift @@ -0,0 +1,77 @@ +import Foundation + +struct NotifyMessageReceiptPayload: JWTClaimsCodable { + + struct Claims: JWTClaims { + /// Timestamp when JWT was issued + let iat: UInt64 + /// Timestamp when JWT must expire + let exp: UInt64 + /// Key server URL + let ksu: String + /// Action intent (must be `notify_receipt`) + let act: String? + + /// `did:key` of an identity key. Enables to resolve attached blockchain account. + let iss: String + /// `did:key` of an identity key. Enables to resolve associated Dapp domain used. + let aud: String + /// Hash of the stringified notify message object received + let sub: String + /// Dapp's domain url + let app: String + + static var action: String? { + return "notify_receipt" + } + } + + struct Wrapper: JWTWrapper { + let receiptAuth: String + + init(jwtString: String) { + self.receiptAuth = jwtString + } + + var jwtString: String { + return receiptAuth + } + } + + let keyserver: URL + let dappPubKey: DIDKey + let messageHash: String + let app: String + + init( + keyserver: URL, + dappPubKey: DIDKey, + messageHash: String, + app: String + ) { + self.keyserver = keyserver + self.dappPubKey = dappPubKey + self.messageHash = messageHash + self.app = app + } + + init(claims: Claims) throws { + self.keyserver = try claims.ksu.asURL() + self.dappPubKey = try DIDKey(did: claims.aud) + self.messageHash = claims.sub + self.app = claims.app + } + + func encode(iss: String) throws -> Claims { + return Claims( + iat: defaultIat(), + exp: expiry(days: 1), + ksu: keyserver.absoluteString, + act: Claims.action, + iss: iss, + aud: dappPubKey.did(variant: .ED25519), + sub: messageHash, + app: app + ) + } +} diff --git a/Sources/WalletConnectNotify/Types/Payload/NotifySubscriptionPayload.swift b/Sources/WalletConnectNotify/Types/Payload/NotifySubscriptionPayload.swift new file mode 100644 index 000000000..847635da0 --- /dev/null +++ b/Sources/WalletConnectNotify/Types/Payload/NotifySubscriptionPayload.swift @@ -0,0 +1,79 @@ +import Foundation + +struct NotifySubscriptionPayload: JWTClaimsCodable { + + struct Claims: JWTClaims { + /// Timestamp when JWT was issued + let iat: UInt64 + /// Timestamp when JWT must expire + let exp: UInt64 + /// Key server URL + let ksu: String + /// Description of action intent. Must be equal to `notify_subscription` + let act: String? + + /// `did:key` of an identity key. Enables to resolve attached blockchain account. + let iss: String + /// `did:key` of an identity key. Enables to resolve associated Dapp domain used. + let aud: String + /// Blockchain account that notify subscription has been proposed for -`did:pkh` + let sub: String + /// Scope of notification types authorized by the user + let scp: String + /// Dapp's domain url + let app: String + + static var action: String? { + return "notify_subscription" + } + } + + struct Wrapper: JWTWrapper { + let subscriptionAuth: String + + init(jwtString: String) { + self.subscriptionAuth = jwtString + } + + var jwtString: String { + return subscriptionAuth + } + } + + let dappPubKey: DIDKey + let keyserver: URL + let subscriptionAccount: Account + let dappUrl: String + let scope: String + + init(dappPubKey: DIDKey, keyserver: URL, subscriptionAccount: Account, dappUrl: String, scope: String) { + self.dappPubKey = dappPubKey + self.keyserver = keyserver + self.subscriptionAccount = subscriptionAccount + self.dappUrl = dappUrl + self.scope = scope + } + + init(claims: Claims) throws { + self.dappPubKey = try DIDKey(did: claims.aud) + self.keyserver = try claims.ksu.asURL() + self.subscriptionAccount = try Account(DIDPKHString: claims.sub) + self.dappUrl = claims.app + self.scope = claims.scp + } + + func encode(iss: String) throws -> Claims { + return Claims( + iat: defaultIat(), + exp: expiry(days: 30), + ksu: keyserver.absoluteString, + act: Claims.action, + iss: iss, + aud: dappPubKey.did(variant: .ED25519), + sub: subscriptionAccount.did, + scp: scope, + app: dappUrl + ) + } +} + diff --git a/Sources/WalletConnectNotify/Types/Payload/NotifySubscriptionResponsePayload.swift b/Sources/WalletConnectNotify/Types/Payload/NotifySubscriptionResponsePayload.swift new file mode 100644 index 000000000..b330882c1 --- /dev/null +++ b/Sources/WalletConnectNotify/Types/Payload/NotifySubscriptionResponsePayload.swift @@ -0,0 +1,65 @@ +import Foundation + +struct NotifySubscriptionResponsePayload: JWTClaimsCodable { + + struct Claims: JWTClaims { + /// timestamp when jwt was issued + let iat: UInt64 + /// timestamp when jwt must expire + let exp: UInt64 + /// Key server URL + let ksu: String + /// Description of action intent. Must be equal to "notify_subscription_response" + let act: String? + + /// `did:key` of an identity key. Allows for the resolution of which Notify server was used. + let iss: String + /// `did:key` of an identity key. Allows for the resolution of the attached blockchain account. + let aud: String + /// `did:key` of the public key used for key agreement on the Notify topic + let sub: String + /// Dapp's domain url + let app: String + + static var action: String? { + return "notify_subscription_response" + } + } + + struct Wrapper: JWTWrapper { + let responseAuth: String + + init(jwtString: String) { + self.responseAuth = jwtString + } + + var jwtString: String { + return responseAuth + } + } + + let keyserver: URL + let selfPubKey: DIDKey + let publicKey: DIDKey + let app: String + + init(claims: Claims) throws { + self.keyserver = try claims.ksu.asURL() + self.selfPubKey = try DIDKey(did: claims.aud) + self.publicKey = try DIDKey(did: claims.sub) + self.app = claims.app + } + + func encode(iss: String) throws -> Claims { + return Claims( + iat: defaultIat(), + exp: expiry(days: 1), + ksu: keyserver.absoluteString, + act: Claims.action, + iss: iss, + aud: selfPubKey.did(variant: .ED25519), + sub: publicKey.did(variant: .X25519), + app: app + ) + } +} diff --git a/Sources/WalletConnectNotify/Types/Payload/NotifyUpdatePayload.swift b/Sources/WalletConnectNotify/Types/Payload/NotifyUpdatePayload.swift new file mode 100644 index 000000000..ef88ebc87 --- /dev/null +++ b/Sources/WalletConnectNotify/Types/Payload/NotifyUpdatePayload.swift @@ -0,0 +1,78 @@ +import Foundation + +struct NotifyUpdatePayload: JWTClaimsCodable { + + struct Claims: JWTClaims { + /// Timestamp when JWT was issued + let iat: UInt64 + /// Timestamp when JWT must expire + let exp: UInt64 + /// Key server URL + let ksu: String + /// Description of action intent. Must be equal to `notify_update` + let act: String? + + /// `did:key` of an identity key. Enables to resolve attached blockchain account. + let iss: String + /// `did:key` of an identity key. Enables to resolve associated Dapp domain used. + let aud: String + /// Blockchain account that notify subscription has been proposed for -`did:pkh` + let sub: String + /// Scope of notification types authorized by the user + let scp: String + /// Dapp's domain url + let app: String + + static var action: String? { + return "notify_update" + } + } + + struct Wrapper: JWTWrapper { + let updateAuth: String + + init(jwtString: String) { + self.updateAuth = jwtString + } + + var jwtString: String { + return updateAuth + } + } + + let dappPubKey: DIDKey + let keyserver: URL + let subscriptionAccount: Account + let dappUrl: String + let scope: String + + init(dappPubKey: DIDKey, keyserver: URL, subscriptionAccount: Account, dappUrl: String, scope: String) { + self.dappPubKey = dappPubKey + self.keyserver = keyserver + self.subscriptionAccount = subscriptionAccount + self.dappUrl = dappUrl + self.scope = scope + } + + init(claims: Claims) throws { + self.dappPubKey = try DIDKey(did: claims.aud) + self.keyserver = try claims.ksu.asURL() + self.subscriptionAccount = try Account(DIDPKHString: claims.sub) + self.dappUrl = claims.app + self.scope = claims.scp + } + + func encode(iss: String) throws -> Claims { + return Claims( + iat: defaultIat(), + exp: expiry(days: 30), + ksu: keyserver.absoluteString, + act: Claims.action, + iss: iss, + aud: dappPubKey.did(variant: .ED25519), + sub: subscriptionAccount.did, + scp: scope, + app: dappUrl + ) + } +} diff --git a/Sources/WalletConnectNotify/Types/Payload/NotifyUpdateResponsePayload.swift b/Sources/WalletConnectNotify/Types/Payload/NotifyUpdateResponsePayload.swift new file mode 100644 index 000000000..db607ffea --- /dev/null +++ b/Sources/WalletConnectNotify/Types/Payload/NotifyUpdateResponsePayload.swift @@ -0,0 +1,65 @@ +import Foundation + +struct NotifyUpdateResponsePayload: JWTClaimsCodable { + + struct Claims: JWTClaims { + /// timestamp when jwt was issued + let iat: UInt64 + /// timestamp when jwt must expire + let exp: UInt64 + /// Key server URL + let ksu: String + /// Description of action intent. Must be equal to "notify_update_response" + let act: String? + + /// `did:key` of an identity key. Enables to resolve associated Dapp domain used. + let iss: String + /// `did:key` of an identity key. Enables to resolve attached blockchain account. + let aud: String + /// Hash of the new subscription payload + let sub: String + /// Dapp's domain url + let app: String + + static var action: String? { + return "notify_update_response" + } + } + + struct Wrapper: JWTWrapper { + let responseAuth: String + + init(jwtString: String) { + self.responseAuth = jwtString + } + + var jwtString: String { + return responseAuth + } + } + + let keyserver: URL + let selfPubKey: DIDKey + let subscriptionHash: String + let app: String + + init(claims: Claims) throws { + self.keyserver = try claims.ksu.asURL() + self.selfPubKey = try DIDKey(did: claims.aud) + self.subscriptionHash = claims.sub + self.app = claims.app + } + + func encode(iss: String) throws -> Claims { + return Claims( + iat: defaultIat(), + exp: expiry(days: 1), + ksu: keyserver.absoluteString, + act: Claims.action, + iss: iss, + aud: selfPubKey.did(variant: .ED25519), + sub: subscriptionHash, + app: app + ) + } +} diff --git a/Sources/WalletConnectPush/Types/WebDidDoc.swift b/Sources/WalletConnectNotify/Types/WebDidDoc.swift similarity index 94% rename from Sources/WalletConnectPush/Types/WebDidDoc.swift rename to Sources/WalletConnectNotify/Types/WebDidDoc.swift index aca4975f0..adf0c17f9 100644 --- a/Sources/WalletConnectPush/Types/WebDidDoc.swift +++ b/Sources/WalletConnectNotify/Types/WebDidDoc.swift @@ -24,7 +24,8 @@ extension WebDidDoc { struct PublicKeyJwk: Codable { enum Curve: String, Codable { - case X25519 = "X25519" + case X25519 + case Ed25519 } let kty: String diff --git a/Sources/WalletConnectEcho/APNSEnvironment.swift b/Sources/WalletConnectPush/APNSEnvironment.swift similarity index 100% rename from Sources/WalletConnectEcho/APNSEnvironment.swift rename to Sources/WalletConnectPush/APNSEnvironment.swift diff --git a/Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionService.swift b/Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionService.swift deleted file mode 100644 index 2751fca46..000000000 --- a/Sources/WalletConnectPush/Client/Common/DeletePushSubscriptionService.swift +++ /dev/null @@ -1,38 +0,0 @@ -import Foundation - -class DeletePushSubscriptionService { - enum Errors: Error { - case pushSubscriptionNotFound - } - private let networkingInteractor: NetworkInteracting - private let kms: KeyManagementServiceProtocol - private let logger: ConsoleLogging - private let pushStorage: PushStorage - - init(networkingInteractor: NetworkInteracting, - kms: KeyManagementServiceProtocol, - logger: ConsoleLogging, - pushStorage: PushStorage) { - self.networkingInteractor = networkingInteractor - self.kms = kms - self.logger = logger - 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 _ = pushStorage.getSubscription(topic: topic) - else { throw Errors.pushSubscriptionNotFound} - let protocolMethod = PushDeleteProtocolMethod() - try await pushStorage.deleteSubscription(topic: topic) - pushStorage.deleteMessages(topic: topic) - let request = RPCRequest(method: protocolMethod.method, params: params) - try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) - - networkingInteractor.unsubscribe(topic: topic) - logger.debug("Subscription removed, topic: \(topic)") - - kms.deleteSymmetricKey(for: topic) - } -} diff --git a/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift b/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift deleted file mode 100644 index 9c7d820ea..000000000 --- a/Sources/WalletConnectPush/Client/Dapp/DappPushClient.swift +++ /dev/null @@ -1,43 +0,0 @@ -import Foundation -import Combine - -public class DappPushClient { - public var proposalResponsePublisher: AnyPublisher, Never> { - return notifyProposeResponseSubscriber.proposalResponsePublisher - } - - public var deleteSubscriptionPublisher: AnyPublisher { - return pushStorage.deleteSubscriptionPublisher - } - - public let logger: ConsoleLogging - - private let notifyProposer: NotifyProposer - private let pushStorage: PushStorage - private let deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber - private let resubscribeService: PushResubscribeService - private let notifyProposeResponseSubscriber: NotifyProposeResponseSubscriber - - init(logger: ConsoleLogging, - kms: KeyManagementServiceProtocol, - pushStorage: PushStorage, - deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber, - resubscribeService: PushResubscribeService, - notifyProposer: NotifyProposer, - notifyProposeResponseSubscriber: NotifyProposeResponseSubscriber) { - self.logger = logger - self.pushStorage = pushStorage - self.deletePushSubscriptionSubscriber = deletePushSubscriptionSubscriber - self.resubscribeService = resubscribeService - self.notifyProposer = notifyProposer - self.notifyProposeResponseSubscriber = notifyProposeResponseSubscriber - } - - public func propose(account: Account, topic: String) async throws { - try await notifyProposer.propose(topic: topic, account: account) - } - - public func getActiveSubscriptions() -> [PushSubscription] { - pushStorage.getSubscriptions() - } -} diff --git a/Sources/WalletConnectPush/Client/Dapp/DappPushClientFactory.swift b/Sources/WalletConnectPush/Client/Dapp/DappPushClientFactory.swift deleted file mode 100644 index 610edff97..000000000 --- a/Sources/WalletConnectPush/Client/Dapp/DappPushClientFactory.swift +++ /dev/null @@ -1,41 +0,0 @@ -import Foundation - -public struct DappPushClientFactory { - - 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") - let groupKeychainStorage = GroupKeychainStorage(serviceIdentifier: "group.com.walletconnect.sdk") - return DappPushClientFactory.create( - metadata: metadata, - logger: logger, - keyValueStorage: keyValueStorage, - keychainStorage: keychainStorage, - groupKeychainStorage: groupKeychainStorage, - networkInteractor: networkInteractor, - syncClient: syncClient - ) - } - - static func create(metadata: AppMetadata, logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, groupKeychainStorage: KeychainStorageProtocol, networkInteractor: NetworkInteracting, syncClient: SyncClient) -> DappPushClient { - let kms = KeyManagementService(keychain: keychainStorage) - let subscriptionStore: SyncStore = SyncStoreFactory.create(name: PushStorageIdntifiers.pushSubscription, syncClient: syncClient, storage: keyValueStorage) - let messagesStore = KeyedDatabase(storage: keyValueStorage, identifier: PushStorageIdntifiers.pushMessagesRecords) - let subscriptionStoreDelegate = PushSubscriptionStoreDelegate(networkingInteractor: networkInteractor, kms: kms, groupKeychainStorage: groupKeychainStorage) - let pushStorage = PushStorage(subscriptionStore: subscriptionStore, messagesStore: messagesStore, 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, - pushStorage: pushStorage, - deletePushSubscriptionSubscriber: deletePushSubscriptionSubscriber, - resubscribeService: resubscribeService, - notifyProposer: notifyProposer, - notifyProposeResponseSubscriber: notifyProposeResponseSubscriber - ) - } -} diff --git a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift deleted file mode 100644 index ee294a384..000000000 --- a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposeResponseSubscriber.swift +++ /dev/null @@ -1,71 +0,0 @@ - -import Foundation -import Combine - -class NotifyProposeResponseSubscriber { - private let networkingInteractor: NetworkInteracting - private let metadata: AppMetadata - private let kms: KeyManagementServiceProtocol - private let logger: ConsoleLogging - var proposalResponsePublisher: AnyPublisher, Never> { - proposalResponsePublisherSubject.eraseToAnyPublisher() - } - private let proposalResponsePublisherSubject = PassthroughSubject, Never>() - - private var publishers = [AnyCancellable]() - - init(networkingInteractor: NetworkInteracting, - kms: KeyManagementServiceProtocol, - logger: ConsoleLogging, - metadata: AppMetadata) { - self.networkingInteractor = networkingInteractor - self.kms = kms - self.logger = logger - self.metadata = metadata - subscribeForProposalResponse() - subscribeForProposalErrors() - } - - - private func subscribeForProposalResponse() { - let protocolMethod = NotifyProposeProtocolMethod() - networkingInteractor.responseSubscription(on: protocolMethod) - .sink { [unowned self] (payload: ResponseSubscriptionPayload) in - logger.debug("Received Notify Proposal response") - Task(priority: .userInitiated) { - do { - let pushSubscription = try await handleResponse(payload: payload) - proposalResponsePublisherSubject.send(.success(pushSubscription)) - } catch { - logger.error(error) - } - } - }.store(in: &publishers) - } - - func handleResponse(payload: ResponseSubscriptionPayload) async throws -> PushSubscription { - let jwtWrapper = SubscriptionJWTPayload.Wrapper(jwtString: payload.response.subscriptionAuth) - let (_, claims) = try SubscriptionJWTPayload.decodeAndVerify(from: jwtWrapper) - logger.debug("subscriptionAuth JWT validated") - let expiry = Date(timeIntervalSince1970: TimeInterval(claims.exp)) - 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, symKey: subscriptionKey.hexRepresentation) - try kms.setSymmetricKey(subscriptionKey, for: subscriptionTopic) - try await networkingInteractor.subscribe(topic: subscriptionTopic) - return subscription - } - - private func subscribeForProposalErrors() { - let protocolMethod = NotifyProposeProtocolMethod() - networkingInteractor.responseErrorSubscription(on: protocolMethod) - .sink { [unowned self] (payload: ResponseSubscriptionErrorPayload) in - kms.deletePrivateKey(for: payload.request.publicKey) - networkingInteractor.unsubscribe(topic: payload.topic) - guard let error = PushError(code: payload.error.code) else { return } - proposalResponsePublisherSubject.send(.failure(error)) - }.store(in: &publishers) - } - -} diff --git a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposer.swift b/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposer.swift deleted file mode 100644 index 02c3720f2..000000000 --- a/Sources/WalletConnectPush/Client/Dapp/wc_notifyPropose/NotifyProposer.swift +++ /dev/null @@ -1,33 +0,0 @@ - -import Foundation - -class NotifyProposer { - private let networkingInteractor: NetworkInteracting - private let kms: KeyManagementService - private let logger: ConsoleLogging - private let metadata: AppMetadata - - init(networkingInteractor: NetworkInteracting, - kms: KeyManagementService, - appMetadata: AppMetadata, - logger: ConsoleLogging) { - self.networkingInteractor = networkingInteractor - self.kms = kms - self.metadata = appMetadata - self.logger = logger - } - - func propose(topic: String, account: Account) async throws { - logger.debug("NotifyProposer: Sending Notify Proposal") - let protocolMethod = NotifyProposeProtocolMethod() - let publicKey = try kms.createX25519KeyPair() - let responseTopic = publicKey.rawRepresentation.sha256().toHexString() - try kms.setPublicKey(publicKey: publicKey, for: responseTopic) - - let params = NotifyProposeParams(publicKey: publicKey.hexRepresentation, metadata: metadata, account: account, scope: []) - let request = RPCRequest(method: protocolMethod.method, params: params) - try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) - try await networkingInteractor.subscribe(topic: responseTopic) - } - -} diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift deleted file mode 100644 index 01b4fbcf2..000000000 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeResponder.swift +++ /dev/null @@ -1,93 +0,0 @@ - -import Foundation -import Combine - -class NotifyProposeResponder { - enum Errors: Error { - case recordForIdNotFound - case malformedRequestParams - case subscriptionNotFound - } - 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 publishers = [AnyCancellable]() - - init(networkingInteractor: NetworkInteracting, - kms: KeyManagementServiceProtocol, - logger: ConsoleLogging, - pushStorage: PushStorage, - pushSubscribeRequester: PushSubscribeRequester, - rpcHistory: RPCHistory, - pushSubscribeResponseSubscriber: PushSubscribeResponseSubscriber - ) { - self.networkingInteractor = networkingInteractor - self.kms = kms - self.logger = logger - self.pushStorage = pushStorage - self.pushSubscribeRequester = pushSubscribeRequester - self.rpcHistory = rpcHistory - } - - func approve(requestId: RPCID, onSign: @escaping SigningCallback) async throws { - - logger.debug("NotifyProposeResponder: approving proposal") - - guard let requestRecord = rpcHistory.get(recordId: requestId) else { throw Errors.recordForIdNotFound } - let proposal = try requestRecord.request.params!.get(NotifyProposeParams.self) - - let subscriptionAuthWrapper = try await pushSubscribeRequester.subscribe(metadata: proposal.metadata, account: proposal.account, onSign: onSign) - - var pushSubscription: PushSubscription! - try await withCheckedThrowingContinuation { [unowned self] continuation in - pushStorage.newSubscriptionPublisher - .first() - .sink { value in - pushSubscription = value - continuation.resume() - }.store(in: &publishers) - } - - guard let peerPublicKey = try? AgreementPublicKey(hex: proposal.publicKey) else { - throw Errors.malformedRequestParams - } - - let responseTopic = peerPublicKey.rawRepresentation.sha256().toHexString() - - let keys = try generateAgreementKeys(peerPublicKey: peerPublicKey) - - try kms.setSymmetricKey(keys.sharedKey, for: responseTopic) - - guard let subscriptionKey = kms.getSymmetricKeyRepresentable(for: pushSubscription.topic)?.toHexString() else { throw Errors.subscriptionNotFound } - - let responseParams = NotifyProposeResponseParams(subscriptionAuth: subscriptionAuthWrapper.subscriptionAuth, subscriptionSymKey: subscriptionKey) - - let response = RPCResponse(id: requestId, result: responseParams) - - let protocolMethod = NotifyProposeProtocolMethod() - - logger.debug("NotifyProposeResponder: sending response") - - try await networkingInteractor.respond(topic: responseTopic, response: response, protocolMethod: protocolMethod, envelopeType: .type1(pubKey: keys.publicKey.rawRepresentation)) - kms.deleteSymmetricKey(for: responseTopic) - } - - func reject(requestId: RPCID) async throws { - logger.debug("NotifyProposeResponder - rejecting notify request") - guard let requestRecord = rpcHistory.get(recordId: requestId) else { throw Errors.recordForIdNotFound } - let pairingTopic = requestRecord.topic - - try await networkingInteractor.respondError(topic: pairingTopic, requestId: requestId, protocolMethod: NotifyProposeProtocolMethod(), reason: PushError.userRejeted) - } - - private func generateAgreementKeys(peerPublicKey: AgreementPublicKey) throws -> AgreementKeys { - let selfPubKey = try kms.createX25519KeyPair() - let keys = try kms.performKeyAgreement(selfPublicKey: selfPubKey, peerPublicKey: peerPublicKey.hexRepresentation) - return keys - } -} - diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeSubscriber.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeSubscriber.swift deleted file mode 100644 index 3ea41d805..000000000 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyPropose/NotifyProposeSubscriber.swift +++ /dev/null @@ -1,53 +0,0 @@ - -import Foundation -import Combine - -class NotifyProposeSubscriber { - - private let requestPublisherSubject = PassthroughSubject() - private let networkingInteractor: NetworkInteracting - private let pushStorage: PushStorage - private var publishers = Set() - public var requestPublisher: AnyPublisher { - requestPublisherSubject.eraseToAnyPublisher() - } - public let logger: ConsoleLogging - private let pairingRegisterer: PairingRegisterer - - init(networkingInteractor: NetworkInteracting, - pushStorage: PushStorage, - publishers: Set = Set(), - logger: ConsoleLogging, - pairingRegisterer: PairingRegisterer) { - self.networkingInteractor = networkingInteractor - self.pushStorage = pushStorage - self.publishers = publishers - self.logger = logger - self.pairingRegisterer = pairingRegisterer - setupSubscription() - } - - func setupSubscription() { - pairingRegisterer.register(method: NotifyProposeProtocolMethod()) - .sink { [unowned self] (payload: RequestSubscriptionPayload) in - logger.debug("NotifyProposeSubscriber - new notify propose request") - guard hasNoSubscription(for: payload.request.metadata.url) else { - Task(priority: .high) { try await respondError(requestId: payload.id, pairingTopic: payload.topic) } - return - } - requestPublisherSubject.send((id: payload.id, account: payload.request.account, metadata: payload.request.metadata)) - }.store(in: &publishers) - } - - func hasNoSubscription(for domain: String) -> Bool { - pushStorage.getSubscriptions().first { $0.metadata.url == domain } == nil - } - - func respondError(requestId: RPCID, pairingTopic: String) async throws { - logger.debug("NotifyProposeSubscriber - responding error for notify propose") - - let pairingTopic = pairingTopic - - try await networkingInteractor.respondError(topic: pairingTopic, requestId: requestId, protocolMethod: NotifyProposeProtocolMethod(), reason: PushError.userHasExistingSubscription) - } -} diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift deleted file mode 100644 index aa1c4fffe..000000000 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_notifyUpdate/NotifyUpdateRequester.swift +++ /dev/null @@ -1,54 +0,0 @@ -import Foundation - -protocol NotifyUpdateRequesting { - func update(topic: String, scope: Set) async throws -} - -class NotifyUpdateRequester: NotifyUpdateRequesting { - enum Errors: Error { - case noSubscriptionForGivenTopic - } - - private let keyserverURL: URL - private let identityClient: IdentityClient - private let networkingInteractor: NetworkInteracting - private let logger: ConsoleLogging - private let pushStorage: PushStorage - - init(keyserverURL: URL, - identityClient: IdentityClient, - networkingInteractor: NetworkInteracting, - logger: ConsoleLogging, - pushStorage: PushStorage - ) { - self.keyserverURL = keyserverURL - self.identityClient = identityClient - self.networkingInteractor = networkingInteractor - self.logger = logger - self.pushStorage = pushStorage - } - - func update(topic: String, scope: Set) async throws { - logger.debug("NotifyUpdateRequester: updating subscription for topic: \(topic)") - - guard let subscription = pushStorage.getSubscription(topic: topic) else { throw Errors.noSubscriptionForGivenTopic } - - let request = try createJWTRequest(subscriptionAccount: subscription.account, dappUrl: subscription.metadata.url, scope: scope) - - let protocolMethod = NotifyUpdateProtocolMethod() - - try await networkingInteractor.request(request, topic: topic, protocolMethod: protocolMethod) - } - - private func createJWTRequest(subscriptionAccount: Account, dappUrl: String, scope: Set) throws -> RPCRequest { - let protocolMethod = NotifyUpdateProtocolMethod().method - let scopeClaim = scope.joined(separator: " ") - let jwtPayload = SubscriptionJWTPayload(keyserver: keyserverURL, subscriptionAccount: subscriptionAccount, dappUrl: dappUrl, scope: scopeClaim) - let wrapper = try identityClient.signAndCreateWrapper( - payload: jwtPayload, - account: subscriptionAccount - ) - print(wrapper.subscriptionAuth) - return RPCRequest(method: protocolMethod, params: wrapper) - } -} diff --git a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushMessage/PushMessageSubscriber.swift b/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushMessage/PushMessageSubscriber.swift deleted file mode 100644 index 897d86f65..000000000 --- a/Sources/WalletConnectPush/Client/Wallet/ProtocolEngine/wc_pushMessage/PushMessageSubscriber.swift +++ /dev/null @@ -1,35 +0,0 @@ -import Foundation -import Combine - -class PushMessageSubscriber { - private let networkingInteractor: NetworkInteracting - private let pushStorage: PushStorage - private let logger: ConsoleLogging - private var publishers = [AnyCancellable]() - private let pushMessagePublisherSubject = PassthroughSubject() - - public var pushMessagePublisher: AnyPublisher { - pushMessagePublisherSubject.eraseToAnyPublisher() - } - - init(networkingInteractor: NetworkInteracting, pushStorage: PushStorage, logger: ConsoleLogging) { - self.networkingInteractor = networkingInteractor - self.pushStorage = pushStorage - self.logger = logger - subscribeForPushMessages() - } - - private func subscribeForPushMessages() { - let protocolMethod = PushMessageProtocolMethod() - networkingInteractor.requestSubscription(on: protocolMethod) - .sink { [unowned self] (payload: RequestSubscriptionPayload) in - logger.debug("Received Push Message") - - let record = PushMessageRecord(id: payload.id.string, topic: payload.topic, message: payload.request, publishedAt: payload.publishedAt) - pushStorage.setMessage(record) - pushMessagePublisherSubject.send(record) - - }.store(in: &publishers) - - } -} diff --git a/Sources/WalletConnectPush/Client/Wallet/PushStorage.swift b/Sources/WalletConnectPush/Client/Wallet/PushStorage.swift deleted file mode 100644 index fa04a9297..000000000 --- a/Sources/WalletConnectPush/Client/Wallet/PushStorage.swift +++ /dev/null @@ -1,111 +0,0 @@ -import Foundation -import Combine - -protocol PushStoring { - func getSubscriptions() -> [PushSubscription] - func getSubscription(topic: String) -> PushSubscription? - func setSubscription(_ subscription: PushSubscription) async throws - func deleteSubscription(topic: String) async throws -} - -final class PushStorage: PushStoring { - - private var publishers = Set() - - private let subscriptionStore: SyncStore - private let messagesStore: KeyedDatabase - - 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, - messagesStore: KeyedDatabase, - subscriptionStoreDelegate: PushSubscriptionStoreDelegate - ) { - self.subscriptionStore = subscriptionStore - self.messagesStore = messagesStore - self.subscriptionStoreDelegate = subscriptionStoreDelegate - setupSubscriptions() - } - - // MARK: Configuration - - func initialize(account: Account) async throws { - try await subscriptionStore.initialize(for: account) - } - - func setupSubscriptions(account: Account) async throws { - try subscriptionStore.setupSubscriptions(account: account) - } - - // MARK: Subscriptions - - 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) - } - - // MARK: Messages - - func getMessages(topic: String) -> [PushMessageRecord] { - return messagesStore.getAll(for: topic) - .sorted{$0.publishedAt > $1.publishedAt} - } - - func deleteMessages(topic: String) { - messagesStore.deleteAll(for: topic) - } - - func deleteMessage(id: String) { - guard let result = messagesStore.find(id: id) else { return } - messagesStore.delete(id: id, for: result.key) - } - - func setMessage(_ record: PushMessageRecord) { - messagesStore.set(element: record, for: record.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 object): - subscriptionStoreDelegate.onDelete(object, pushStorage: self) - deleteSubscriptionSubject.send(object.topic) - } - }.store(in: &publishers) - } -} diff --git a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift b/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift deleted file mode 100644 index cb90ef867..000000000 --- a/Sources/WalletConnectPush/Client/Wallet/WalletPushClient.swift +++ /dev/null @@ -1,141 +0,0 @@ -import Foundation -import Combine - -public class WalletPushClient { - - private var publishers = Set() - - /// publishes new subscriptions - public var newSubscriptionPublisher: AnyPublisher { - return pushStorage.newSubscriptionPublisher - } - - public var subscriptionErrorPublisher: AnyPublisher { - return pushSubscribeResponseSubscriber.subscriptionErrorPublisher - } - - public var deleteSubscriptionPublisher: AnyPublisher { - return pushStorage.deleteSubscriptionPublisher - } - - public var subscriptionsPublisher: AnyPublisher<[PushSubscription], Never> { - return pushStorage.subscriptionsPublisher - } - - public var requestPublisher: AnyPublisher { - notifyProposeSubscriber.requestPublisher - } - - public var pushMessagePublisher: AnyPublisher { - pushMessageSubscriber.pushMessagePublisher - } - - public var updateSubscriptionPublisher: AnyPublisher, Never> { - return notifyUpdateResponseSubscriber.updateSubscriptionPublisher - } - - private let deletePushSubscriptionService: DeletePushSubscriptionService - private let pushSubscribeRequester: PushSubscribeRequester - - public let logger: ConsoleLogging - - private let echoClient: EchoClient - private let pushStorage: PushStorage - private let pushSyncService: PushSyncService - private let pushMessageSubscriber: PushMessageSubscriber - private let resubscribeService: PushResubscribeService - private let pushSubscribeResponseSubscriber: PushSubscribeResponseSubscriber - private let deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber - private let notifyUpdateRequester: NotifyUpdateRequester - private let notifyUpdateResponseSubscriber: NotifyUpdateResponseSubscriber - private let notifyProposeResponder: NotifyProposeResponder - private let notifyProposeSubscriber: NotifyProposeSubscriber - private let subscriptionsAutoUpdater: SubscriptionsAutoUpdater - - init(logger: ConsoleLogging, - kms: KeyManagementServiceProtocol, - echoClient: EchoClient, - pushMessageSubscriber: PushMessageSubscriber, - pushStorage: PushStorage, - pushSyncService: PushSyncService, - deletePushSubscriptionService: DeletePushSubscriptionService, - resubscribeService: PushResubscribeService, - pushSubscribeRequester: PushSubscribeRequester, - pushSubscribeResponseSubscriber: PushSubscribeResponseSubscriber, - deletePushSubscriptionSubscriber: DeletePushSubscriptionSubscriber, - notifyUpdateRequester: NotifyUpdateRequester, - notifyUpdateResponseSubscriber: NotifyUpdateResponseSubscriber, - notifyProposeResponder: NotifyProposeResponder, - notifyProposeSubscriber: NotifyProposeSubscriber, - subscriptionsAutoUpdater: SubscriptionsAutoUpdater - ) { - self.logger = logger - self.echoClient = echoClient - self.pushMessageSubscriber = pushMessageSubscriber - self.pushStorage = pushStorage - self.pushSyncService = pushSyncService - self.deletePushSubscriptionService = deletePushSubscriptionService - self.resubscribeService = resubscribeService - self.pushSubscribeRequester = pushSubscribeRequester - self.pushSubscribeResponseSubscriber = pushSubscribeResponseSubscriber - self.deletePushSubscriptionSubscriber = deletePushSubscriptionSubscriber - self.notifyUpdateRequester = notifyUpdateRequester - self.notifyUpdateResponseSubscriber = notifyUpdateResponseSubscriber - self.notifyProposeResponder = notifyProposeResponder - self.notifyProposeSubscriber = notifyProposeSubscriber - self.subscriptionsAutoUpdater = subscriptionsAutoUpdater - } - - // TODO: Add docs - public func enableSync(account: Account, onSign: @escaping SigningCallback) async throws { - try await pushSyncService.registerSyncIfNeeded(account: account, onSign: onSign) - try await pushStorage.initialize(account: account) - try await pushStorage.setupSubscriptions(account: account) - try await pushSyncService.fetchHistoryIfNeeded(account: account) - } - - public func subscribe(metadata: AppMetadata, account: Account, onSign: @escaping SigningCallback) async throws { - try await pushSubscribeRequester.subscribe(metadata: metadata, account: account, onSign: onSign) - } - - public func approve(id: RPCID, onSign: @escaping SigningCallback) async throws { - try await notifyProposeResponder.approve(requestId: id, onSign: onSign) - } - - public func reject(id: RPCID) async throws { - try await notifyProposeResponder.reject(requestId: id) - } - - public func update(topic: String, scope: Set) async throws { - try await notifyUpdateRequester.update(topic: topic, scope: scope) - } - - public func getActiveSubscriptions() -> [PushSubscription] { - return pushStorage.getSubscriptions() - } - - public func getMessageHistory(topic: String) -> [PushMessageRecord] { - pushStorage.getMessages(topic: topic) - } - - public func deleteSubscription(topic: String) async throws { - try await deletePushSubscriptionService.delete(topic: topic) - } - - public func deletePushMessage(id: String) { - pushStorage.deleteMessage(id: id) - } - - public func register(deviceToken: Data) async throws { - try await echoClient.register(deviceToken: deviceToken) - } -} - -#if targetEnvironment(simulator) -extension WalletPushClient { - public func register(deviceToken: String) async throws { - try await echoClient.register(deviceToken: deviceToken) - } -} -#endif - diff --git a/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift b/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift deleted file mode 100644 index d95d7cb6d..000000000 --- a/Sources/WalletConnectPush/Client/Wallet/WalletPushClientFactory.swift +++ /dev/null @@ -1,88 +0,0 @@ -import Foundation - -public struct WalletPushClientFactory { - - public static func create(networkInteractor: NetworkInteracting, pairingRegisterer: PairingRegisterer, echoClient: EchoClient, syncClient: SyncClient, historyClient: HistoryClient) -> WalletPushClient { - let logger = ConsoleLogger(suffix: "🔔",loggingLevel: .debug) - let keyValueStorage = UserDefaults.standard - let keyserverURL = URL(string: "https://keys.walletconnect.com")! - let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk") - let groupKeychainService = GroupKeychainStorage(serviceIdentifier: "group.com.walletconnect.sdk") - - return WalletPushClientFactory.create( - keyserverURL: keyserverURL, - logger: logger, - keyValueStorage: keyValueStorage, - keychainStorage: keychainStorage, - groupKeychainStorage: groupKeychainService, - networkInteractor: networkInteractor, - pairingRegisterer: pairingRegisterer, - echoClient: echoClient, - syncClient: syncClient, - historyClient: historyClient - ) - } - - static func create( - keyserverURL: URL, - logger: ConsoleLogging, - keyValueStorage: KeyValueStorage, - keychainStorage: KeychainStorageProtocol, - groupKeychainStorage: KeychainStorageProtocol, - networkInteractor: NetworkInteracting, - pairingRegisterer: PairingRegisterer, - echoClient: EchoClient, - syncClient: SyncClient, - historyClient: HistoryClient - ) -> WalletPushClient { - let kms = KeyManagementService(keychain: keychainStorage) - let history = RPCHistoryFactory.createForNetwork(keyValueStorage: keyValueStorage) - let subscriptionStore: SyncStore = SyncStoreFactory.create(name: PushStorageIdntifiers.pushSubscription, syncClient: syncClient, storage: keyValueStorage) - let subscriptionStoreDelegate = PushSubscriptionStoreDelegate(networkingInteractor: networkInteractor, kms: kms, groupKeychainStorage: groupKeychainStorage) - let messagesStore = KeyedDatabase(storage: keyValueStorage, identifier: PushStorageIdntifiers.pushMessagesRecords) - let pushStorage = PushStorage(subscriptionStore: subscriptionStore, messagesStore: messagesStore, subscriptionStoreDelegate: subscriptionStoreDelegate) - let coldStartStore = CodableStore(defaults: keyValueStorage, identifier: PushStorageIdntifiers.coldStartStore) - let pushSyncService = PushSyncService(syncClient: syncClient, logger: logger, historyClient: historyClient, subscriptionsStore: subscriptionStore, messagesStore: messagesStore, networkingInteractor: networkInteractor, kms: kms, coldStartStore: coldStartStore, groupKeychainStorage: groupKeychainStorage) - let identityClient = IdentityClientFactory.create(keyserver: keyserverURL, keychain: keychainStorage, logger: logger) - let pushMessageSubscriber = PushMessageSubscriber(networkingInteractor: networkInteractor, pushStorage: pushStorage, logger: logger) - let deletePushSubscriptionService = DeletePushSubscriptionService(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushStorage: pushStorage) - 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, pushStorage: pushStorage, dappsMetadataStore: dappsMetadataStore, subscriptionScopeProvider: subscriptionScopeProvider) - - let notifyUpdateRequester = NotifyUpdateRequester(keyserverURL: keyserverURL, identityClient: identityClient, networkingInteractor: networkInteractor, logger: logger, pushStorage: pushStorage) - - 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, pushStorage: pushStorage, logger: logger, pairingRegisterer: pairingRegisterer) - - let deletePushSubscriptionSubscriber = DeletePushSubscriptionSubscriber(networkingInteractor: networkInteractor, kms: kms, logger: logger, pushStorage: pushStorage) - - let subscriptionsAutoUpdater = SubscriptionsAutoUpdater(notifyUpdateRequester: notifyUpdateRequester, logger: logger, pushStorage: pushStorage) - - return WalletPushClient( - logger: logger, - kms: kms, - echoClient: echoClient, - pushMessageSubscriber: pushMessageSubscriber, - pushStorage: pushStorage, - pushSyncService: pushSyncService, - deletePushSubscriptionService: deletePushSubscriptionService, - resubscribeService: resubscribeService, - pushSubscribeRequester: pushSubscribeRequester, - pushSubscribeResponseSubscriber: pushSubscribeResponseSubscriber, - deletePushSubscriptionSubscriber: deletePushSubscriptionSubscriber, - notifyUpdateRequester: notifyUpdateRequester, - notifyUpdateResponseSubscriber: notifyUpdateResponseSubscriber, - notifyProposeResponder: notifyProposeResponder, - notifyProposeSubscriber: notifyProposeSubscriber, - subscriptionsAutoUpdater: subscriptionsAutoUpdater - ) - } -} diff --git a/Sources/WalletConnectPush/Client/Wallet/WebDidResolver.swift b/Sources/WalletConnectPush/Client/Wallet/WebDidResolver.swift deleted file mode 100644 index f7e6617ad..000000000 --- a/Sources/WalletConnectPush/Client/Wallet/WebDidResolver.swift +++ /dev/null @@ -1,15 +0,0 @@ - -import Foundation - -class WebDidResolver { - - enum Errors: Error { - case invalidUrl - } - - func resolveDidDoc(domainUrl: String) async throws -> WebDidDoc { - guard let didDocUrl = URL(string: "\(domainUrl)/.well-known/did.json") else { throw Errors.invalidUrl } - let (data, _) = try await URLSession.shared.data(from: didDocUrl) - return try JSONDecoder().decode(WebDidDoc.self, from: data) - } -} diff --git a/Sources/WalletConnectPush/ProtocolMethods/NotifyProposeProtocolMethod.swift b/Sources/WalletConnectPush/ProtocolMethods/NotifyProposeProtocolMethod.swift deleted file mode 100644 index 027293d19..000000000 --- a/Sources/WalletConnectPush/ProtocolMethods/NotifyProposeProtocolMethod.swift +++ /dev/null @@ -1,12 +0,0 @@ -// - -import Foundation - -struct NotifyProposeProtocolMethod: ProtocolMethod { - let method: String = "wc_pushPropose" - - let requestConfig: RelayConfig = RelayConfig(tag: 4010, prompt: true, ttl: 86400) - - let responseConfig: RelayConfig = RelayConfig(tag: 4011, prompt: true, ttl: 86400) -} - diff --git a/Sources/WalletConnectPush/ProtocolMethods/PushSubscribeProtocolMethod.swift b/Sources/WalletConnectPush/ProtocolMethods/PushSubscribeProtocolMethod.swift deleted file mode 100644 index a101dc0e5..000000000 --- a/Sources/WalletConnectPush/ProtocolMethods/PushSubscribeProtocolMethod.swift +++ /dev/null @@ -1,10 +0,0 @@ - -import Foundation - -struct PushSubscribeProtocolMethod: ProtocolMethod { - let method: String = "wc_pushSubscribe" - - let requestConfig: RelayConfig = RelayConfig(tag: 4006, prompt: true, ttl: 86400) - - let responseConfig: RelayConfig = RelayConfig(tag: 4007, prompt: true, ttl: 86400) -} diff --git a/Sources/WalletConnectPush/Push.swift b/Sources/WalletConnectPush/Push.swift index 019b64aa6..fe955d08e 100644 --- a/Sources/WalletConnectPush/Push.swift +++ b/Sources/WalletConnectPush/Push.swift @@ -1,36 +1,28 @@ import Foundation public class Push { - - public static var dapp: DappPushClient = { - return DappPushClientFactory.create( - metadata: Pair.metadata, - networkInteractor: Networking.interactor, - syncClient: Sync.instance - ) - }() - - public static var wallet: WalletPushClient = { + static public let pushHost = "echo.walletconnect.com" + public static var instance: PushClient = { guard let config = Push.config else { - fatalError("Error - you must call Push.configure(_:) before accessing the shared wallet instance.") + fatalError("Error - you must call Push.configure(_:) before accessing the shared instance.") } - Echo.configure(echoHost: config.echoHost, environment: config.environment) - return WalletPushClientFactory.create( - networkInteractor: Networking.interactor, - pairingRegisterer: Pair.registerer, - echoClient: Echo.instance, - syncClient: Sync.instance, - historyClient: History.instance - ) + + return PushClientFactory.create( + projectId: Networking.projectId, + pushHost: config.pushHost, + environment: config.environment) }() private static var config: Config? private init() { } - /// Wallet's configuration method - static public func configure(echoHost: String = "echo.walletconnect.com", environment: APNSEnvironment) { - Push.config = Push.Config(echoHost: echoHost, environment: environment) + /// Push instance config method + /// - Parameter clientId: https://github.com/WalletConnect/walletconnect-docs/blob/main/docs/specs/clients/core/relay/relay-client-auth.md#overview + static public func configure( + pushHost: String = pushHost, + environment: APNSEnvironment + ) { + Push.config = Push.Config(pushHost: pushHost, environment: environment) } - } diff --git a/Sources/WalletConnectEcho/EchoClient.swift b/Sources/WalletConnectPush/PushClient.swift similarity index 75% rename from Sources/WalletConnectEcho/EchoClient.swift rename to Sources/WalletConnectPush/PushClient.swift index fc619f667..2eb23aacd 100644 --- a/Sources/WalletConnectEcho/EchoClient.swift +++ b/Sources/WalletConnectPush/PushClient.swift @@ -1,9 +1,9 @@ import Foundation -public class EchoClient: EchoClientProtocol { - private let registerService: EchoRegisterService +public class PushClient: PushClientProtocol { + private let registerService: PushRegisterService - init(registerService: EchoRegisterService) { + init(registerService: PushRegisterService) { self.registerService = registerService } @@ -20,7 +20,7 @@ public class EchoClient: EchoClientProtocol { #if DEBUG -final class EchoClientMock: EchoClientProtocol { +final class PushClientMock: PushClientProtocol { var registedCalled = false func register(deviceToken: Data) async throws { diff --git a/Sources/WalletConnectEcho/EchoClientFactory.swift b/Sources/WalletConnectPush/PushClientFactory.swift similarity index 54% rename from Sources/WalletConnectEcho/EchoClientFactory.swift rename to Sources/WalletConnectPush/PushClientFactory.swift index 3e13db72f..50a0145f9 100644 --- a/Sources/WalletConnectEcho/EchoClientFactory.swift +++ b/Sources/WalletConnectPush/PushClientFactory.swift @@ -1,40 +1,39 @@ import Foundation -public struct EchoClientFactory { +public struct PushClientFactory { public static func create(projectId: String, - echoHost: String, - environment: APNSEnvironment) -> EchoClient { + pushHost: String, + environment: APNSEnvironment) -> PushClient { let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk") - return EchoClientFactory.create( + return PushClientFactory.create( projectId: projectId, - echoHost: echoHost, + pushHost: pushHost, keychainStorage: keychainStorage, environment: environment) } public static func create( projectId: String, - echoHost: String, + pushHost: String, keychainStorage: KeychainStorageProtocol, environment: APNSEnvironment - ) -> EchoClient { + ) -> PushClient { let sessionConfiguration = URLSessionConfiguration.default sessionConfiguration.timeoutIntervalForRequest = 5.0 sessionConfiguration.timeoutIntervalForResource = 5.0 let session = URLSession(configuration: sessionConfiguration) - - let logger = ConsoleLogger(suffix: "👂đŸģ", loggingLevel: .debug) - let httpClient = HTTPNetworkClient(host: echoHost, session: session) + + let logger = ConsoleLogger(prefix: "👂đŸģ", loggingLevel: .off) + let httpClient = HTTPNetworkClient(host: pushHost, session: session) let clientIdStorage = ClientIdStorage(keychain: keychainStorage) - let echoAuthenticator = EchoAuthenticator(clientIdStorage: clientIdStorage, echoHost: echoHost) + let pushAuthenticator = PushAuthenticator(clientIdStorage: clientIdStorage, pushHost: pushHost) - let registerService = EchoRegisterService(httpClient: httpClient, projectId: projectId, clientIdStorage: clientIdStorage, echoAuthenticator: echoAuthenticator, logger: logger, environment: environment) + let registerService = PushRegisterService(httpClient: httpClient, projectId: projectId, clientIdStorage: clientIdStorage, pushAuthenticator: pushAuthenticator, logger: logger, environment: environment) - return EchoClient( - registerService: registerService) + return PushClient(registerService: registerService) } } diff --git a/Sources/WalletConnectEcho/EchoClientProtocol.swift b/Sources/WalletConnectPush/PushClientProtocol.swift similarity index 79% rename from Sources/WalletConnectEcho/EchoClientProtocol.swift rename to Sources/WalletConnectPush/PushClientProtocol.swift index c3b783df9..35eb8f3fa 100644 --- a/Sources/WalletConnectEcho/EchoClientProtocol.swift +++ b/Sources/WalletConnectPush/PushClientProtocol.swift @@ -1,6 +1,6 @@ import Foundation -public protocol EchoClientProtocol { +public protocol PushClientProtocol { func register(deviceToken: Data) async throws #if DEBUG func register(deviceToken: String) async throws diff --git a/Sources/WalletConnectPush/PushConfig.swift b/Sources/WalletConnectPush/PushConfig.swift index 9c955803a..e8d2eab40 100644 --- a/Sources/WalletConnectPush/PushConfig.swift +++ b/Sources/WalletConnectPush/PushConfig.swift @@ -2,7 +2,7 @@ import Foundation extension Push { struct Config { - let echoHost: String + let pushHost: String let environment: APNSEnvironment } } diff --git a/Sources/WalletConnectPush/PushImports.swift b/Sources/WalletConnectPush/PushImports.swift index 5d03d0533..463cb7c23 100644 --- a/Sources/WalletConnectPush/PushImports.swift +++ b/Sources/WalletConnectPush/PushImports.swift @@ -1,7 +1,4 @@ #if !CocoaPods -@_exported import WalletConnectPairing -@_exported import WalletConnectEcho -@_exported import WalletConnectIdentity -@_exported import WalletConnectSync -@_exported import WalletConnectHistory +@_exported import WalletConnectNetworking +@_exported import WalletConnectJWT #endif diff --git a/Sources/WalletConnectPush/PushStorageIdntifiers.swift b/Sources/WalletConnectPush/PushStorageIdntifiers.swift deleted file mode 100644 index 7be76bf7e..000000000 --- a/Sources/WalletConnectPush/PushStorageIdntifiers.swift +++ /dev/null @@ -1,9 +0,0 @@ -import Foundation - -enum PushStorageIdntifiers { - static let pushSubscription = "com.walletconnect.notify.pushSubscription" - - static let pushMessagesRecords = "com.walletconnect.sdk.pushMessagesRecords" - static let dappsMetadataStore = "com.walletconnect.sdk.dappsMetadataStore" - static let coldStartStore = "com.walletconnect.sdk.coldStartStore" -} diff --git a/Sources/WalletConnectPush/RPCRequests/PushDeleteParams.swift b/Sources/WalletConnectPush/RPCRequests/PushDeleteParams.swift deleted file mode 100644 index cc8829319..000000000 --- a/Sources/WalletConnectPush/RPCRequests/PushDeleteParams.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Foundation - -public struct PushDeleteParams: Codable { - let code: Int - let message: String - - static var userDisconnected: PushDeleteParams { - return PushDeleteParams(code: 6000, message: "User Disconnected") - } -} diff --git a/Sources/WalletConnectPush/RPCRequests/SubscriptionJWTPayload.swift b/Sources/WalletConnectPush/RPCRequests/SubscriptionJWTPayload.swift deleted file mode 100644 index d45ac558b..000000000 --- a/Sources/WalletConnectPush/RPCRequests/SubscriptionJWTPayload.swift +++ /dev/null @@ -1,68 +0,0 @@ -import Foundation - -struct SubscriptionJWTPayload: JWTClaimsCodable { - - struct Claims: JWTClaims { - /// timestamp when jwt was issued - let iat: UInt64 - /// timestamp when jwt must expire - let exp: UInt64 - /// did:key of an identity key. Enables to resolve attached blockchain account. - let iss: String - /// key server for identity key verification - let ksu: String - /// dapp's url - let aud: String - /// blockchain account that push subscription has been proposed for (did:pkh) - let sub: String - /// description of action intent. Must be equal to "push_subscription" - let act: String - - let scp: String - } - - struct Wrapper: JWTWrapper { - let subscriptionAuth: String - - init(jwtString: String) { - self.subscriptionAuth = jwtString - } - - var jwtString: String { - return subscriptionAuth - } - } - - let keyserver: URL - let subscriptionAccount: Account - let dappUrl: String - let scope: String - - init(keyserver: URL, subscriptionAccount: Account, dappUrl: String, scope: String) { - self.keyserver = keyserver - self.subscriptionAccount = subscriptionAccount - self.dappUrl = dappUrl - self.scope = scope - } - - init(claims: Claims) throws { - self.keyserver = try claims.ksu.asURL() - self.subscriptionAccount = try Account(DIDPKHString: claims.sub) - self.dappUrl = claims.aud - self.scope = claims.scp - } - - func encode(iss: String) throws -> Claims { - return Claims( - iat: defaultIatMilliseconds(), - exp: expiry(days: 30), - iss: iss, - ksu: keyserver.absoluteString, - aud: dappUrl, - sub: subscriptionAccount.did, - act: "push_subscription", - scp: scope - ) - } -} - diff --git a/Sources/WalletConnectEcho/Register/EchoAuthPayload.swift b/Sources/WalletConnectPush/Register/PushAuthPayload.swift similarity index 80% rename from Sources/WalletConnectEcho/Register/EchoAuthPayload.swift rename to Sources/WalletConnectPush/Register/PushAuthPayload.swift index 12b0e26e9..ef4ecb8a3 100644 --- a/Sources/WalletConnectEcho/Register/EchoAuthPayload.swift +++ b/Sources/WalletConnectPush/Register/PushAuthPayload.swift @@ -1,6 +1,6 @@ import Foundation -struct EchoAuthPayload: JWTClaimsCodable { +struct PushAuthPayload: JWTClaimsCodable { struct Claims: JWTClaims { let iss: String @@ -8,6 +8,10 @@ struct EchoAuthPayload: JWTClaimsCodable { let aud: String let iat: UInt64 let exp: UInt64 + + let act: String? + + static var action: String? { nil } } struct Wrapper: JWTWrapper { @@ -33,7 +37,8 @@ struct EchoAuthPayload: JWTClaimsCodable { sub: subject, aud: audience, iat: defaultIat(), - exp: expiry(days: 1) + exp: expiry(days: 1), + act: Claims.action ) } } diff --git a/Sources/WalletConnectEcho/Register/EchoAuthenticator.swift b/Sources/WalletConnectPush/Register/PushAuthenticator.swift similarity index 63% rename from Sources/WalletConnectEcho/Register/EchoAuthenticator.swift rename to Sources/WalletConnectPush/Register/PushAuthenticator.swift index c47247fa4..0091c6bf0 100644 --- a/Sources/WalletConnectEcho/Register/EchoAuthenticator.swift +++ b/Sources/WalletConnectPush/Register/PushAuthenticator.swift @@ -1,26 +1,26 @@ import Foundation -protocol EchoAuthenticating { +protocol PushAuthenticating { func createAuthToken() throws -> String } -class EchoAuthenticator: EchoAuthenticating { +class PushAuthenticator: PushAuthenticating { private let clientIdStorage: ClientIdStoring - private let echoHost: String + private let pushHost: String - init(clientIdStorage: ClientIdStoring, echoHost: String) { + init(clientIdStorage: ClientIdStoring, pushHost: String) { self.clientIdStorage = clientIdStorage - self.echoHost = echoHost + self.pushHost = pushHost } func createAuthToken() throws -> String { let keyPair = try clientIdStorage.getOrCreateKeyPair() - let payload = EchoAuthPayload(subject: getSubject(), audience: getAudience()) + let payload = PushAuthPayload(subject: getSubject(), audience: getAudience()) return try payload.signAndCreateWrapper(keyPair: keyPair).jwtString } private func getAudience() -> String { - return "https://\(echoHost)" + return "https://\(pushHost)" } private func getSubject() -> String { diff --git a/Sources/WalletConnectEcho/Register/EchoRegisterService.swift b/Sources/WalletConnectPush/Register/PushRegisterService.swift similarity index 83% rename from Sources/WalletConnectEcho/Register/EchoRegisterService.swift rename to Sources/WalletConnectPush/Register/PushRegisterService.swift index 702ba10cb..2e142ae26 100644 --- a/Sources/WalletConnectEcho/Register/EchoRegisterService.swift +++ b/Sources/WalletConnectPush/Register/PushRegisterService.swift @@ -1,11 +1,11 @@ import Foundation -actor EchoRegisterService { +actor PushRegisterService { private let httpClient: HTTPClient private let projectId: String private let logger: ConsoleLogging private let environment: APNSEnvironment - private let echoAuthenticator: EchoAuthenticating + private let pushAuthenticator: PushAuthenticating private let clientIdStorage: ClientIdStoring /// The property is used to determine whether echo.walletconnect.org will be used /// in case echo.walletconnect.com doesn't respond for some reason (most likely due to being blocked in the user's location). @@ -18,12 +18,12 @@ actor EchoRegisterService { init(httpClient: HTTPClient, projectId: String, clientIdStorage: ClientIdStoring, - echoAuthenticator: EchoAuthenticating, + pushAuthenticator: PushAuthenticating, logger: ConsoleLogging, environment: APNSEnvironment) { self.httpClient = httpClient self.clientIdStorage = clientIdStorage - self.echoAuthenticator = echoAuthenticator + self.pushAuthenticator = pushAuthenticator self.projectId = projectId self.logger = logger self.environment = environment @@ -32,15 +32,15 @@ actor EchoRegisterService { func register(deviceToken: Data) async throws { let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) } let token = tokenParts.joined() - let echoAuthToken = try echoAuthenticator.createAuthToken() + let pushAuthToken = try pushAuthenticator.createAuthToken() let clientId = try clientIdStorage.getClientId() let clientIdMutlibase = try DIDKey(did: clientId).multibase(variant: .ED25519) logger.debug("APNS device token: \(token)") do { let response = try await httpClient.request( - EchoResponse.self, - at: EchoAPI.register(clientId: clientIdMutlibase, token: token, projectId: projectId, environment: environment, auth: echoAuthToken) + PushResponse.self, + at: PushAPI.register(clientId: clientIdMutlibase, token: token, projectId: projectId, environment: environment, auth: pushAuthToken) ) guard response.status == .success else { throw Errors.registrationFailed @@ -62,14 +62,14 @@ actor EchoRegisterService { #if DEBUG public func register(deviceToken: String) async throws { - let echoAuthToken = try echoAuthenticator.createAuthToken() + let pushAuthToken = try pushAuthenticator.createAuthToken() let clientId = try clientIdStorage.getClientId() let clientIdMutlibase = try DIDKey(did: clientId).multibase(variant: .ED25519) do { let response = try await httpClient.request( - EchoResponse.self, - at: EchoAPI.register(clientId: clientIdMutlibase, token: deviceToken, projectId: projectId, environment: environment, auth: echoAuthToken) + PushResponse.self, + at: PushAPI.register(clientId: clientIdMutlibase, token: deviceToken, projectId: projectId, environment: environment, auth: pushAuthToken) ) guard response.status == .success else { throw Errors.registrationFailed diff --git a/Sources/WalletConnectEcho/Register/EchoResponse.swift b/Sources/WalletConnectPush/Register/PushResponse.swift similarity index 82% rename from Sources/WalletConnectEcho/Register/EchoResponse.swift rename to Sources/WalletConnectPush/Register/PushResponse.swift index f480c510c..3dd803c7b 100644 --- a/Sources/WalletConnectEcho/Register/EchoResponse.swift +++ b/Sources/WalletConnectPush/Register/PushResponse.swift @@ -1,6 +1,6 @@ import Foundation -struct EchoResponse: Codable { +struct PushResponse: Codable { enum Status: String, Codable { case success = "SUCCESS" case failed = "FAILED" diff --git a/Sources/WalletConnectEcho/Register/EchoService.swift b/Sources/WalletConnectPush/Register/PushService.swift similarity index 98% rename from Sources/WalletConnectEcho/Register/EchoService.swift rename to Sources/WalletConnectPush/Register/PushService.swift index 44c328101..d6ddbc6f3 100644 --- a/Sources/WalletConnectEcho/Register/EchoService.swift +++ b/Sources/WalletConnectPush/Register/PushService.swift @@ -1,6 +1,6 @@ import Foundation -enum EchoAPI: HTTPService { +enum PushAPI: HTTPService { case register(clientId: String, token: String, projectId: String, environment: APNSEnvironment, auth: String) case unregister(clientId: String, projectId: String, auth: String) diff --git a/Sources/WalletConnectPush/Types/PushRequest.swift b/Sources/WalletConnectPush/Types/PushRequest.swift deleted file mode 100644 index d1c4c8a92..000000000 --- a/Sources/WalletConnectPush/Types/PushRequest.swift +++ /dev/null @@ -1,3 +0,0 @@ -import Foundation - -public typealias PushRequest = (id: RPCID, account: Account, metadata: AppMetadata) diff --git a/Sources/WalletConnectPush/Types/PushSubscriptionResult.swift b/Sources/WalletConnectPush/Types/PushSubscriptionResult.swift deleted file mode 100644 index 3e7044917..000000000 --- a/Sources/WalletConnectPush/Types/PushSubscriptionResult.swift +++ /dev/null @@ -1,7 +0,0 @@ - -import Foundation - -public struct PushSubscriptionResult: Equatable, Codable { - public let pushSubscription: PushSubscription - public let subscriptionAuth: String -} diff --git a/Sources/WalletConnectRelay/ClientAuth/RelayAuthPayload.swift b/Sources/WalletConnectRelay/ClientAuth/RelayAuthPayload.swift index 737f515c0..3c44c4e5c 100644 --- a/Sources/WalletConnectRelay/ClientAuth/RelayAuthPayload.swift +++ b/Sources/WalletConnectRelay/ClientAuth/RelayAuthPayload.swift @@ -12,6 +12,10 @@ struct RelayAuthPayload: JWTClaimsCodable { let aud: String let iat: UInt64 let exp: UInt64 + + let act: String? + + static var action: String? { nil } } let subject: String @@ -33,7 +37,8 @@ struct RelayAuthPayload: JWTClaimsCodable { sub: subject, aud: audience, iat: defaultIat(), - exp: expiry(days: 1) + exp: expiry(days: 1), + act: Claims.action ) } } diff --git a/Sources/WalletConnectRelay/PackageConfig.json b/Sources/WalletConnectRelay/PackageConfig.json index 9ed5ec2e3..86f515d51 100644 --- a/Sources/WalletConnectRelay/PackageConfig.json +++ b/Sources/WalletConnectRelay/PackageConfig.json @@ -1 +1 @@ -{"version": "1.6.18"} +{"version": "1.7.0"} diff --git a/Sources/WalletConnectRelay/RelayClient.swift b/Sources/WalletConnectRelay/RelayClient.swift index ddb9cf91b..60eb6aa06 100644 --- a/Sources/WalletConnectRelay/RelayClient.swift +++ b/Sources/WalletConnectRelay/RelayClient.swift @@ -63,6 +63,10 @@ public final class RelayClient { } } + public func setLogging(level: LoggingLevel) { + logger.setLogging(level: level) + } + /// Connects web socket /// /// Use this method for manual socket connection only @@ -82,12 +86,12 @@ public final class RelayClient { let request = Publish(params: .init(topic: topic, message: payload, ttl: ttl, prompt: prompt, tag: tag)) .asRPCRequest() let message = try request.asJSONEncodedString() - logger.debug("[RelayClient]: Publishing payload on topic: \(topic)") + logger.debug("Publishing payload on topic: \(topic)") try await dispatcher.protectedSend(message) } public func subscribe(topic: String) async throws { - logger.debug("[RelayClient]: Subscribing to topic: \(topic)") + logger.debug("Subscribing to topic: \(topic)") let rpc = Subscribe(params: .init(topic: topic)) let request = rpc .asRPCRequest() @@ -98,7 +102,7 @@ public final class RelayClient { public func batchSubscribe(topics: [String]) async throws { guard !topics.isEmpty else { return } - logger.debug("[RelayClient]: Subscribing to topics: \(topics)") + logger.debug("Subscribing to topics: \(topics)") let rpc = BatchSubscribe(params: .init(topics: topics)) let request = rpc .asRPCRequest() @@ -134,7 +138,7 @@ public final class RelayClient { completion(Errors.subscriptionIdNotFound) return } - logger.debug("[RelayClient]: Unsubscribing from topic: \(topic)") + logger.debug("Unsubscribing from topic: \(topic)") let rpc = Unsubscribe(params: .init(id: subscriptionId, topic: topic)) let request = rpc .asRPCRequest() @@ -142,7 +146,7 @@ public final class RelayClient { rpcHistory.deleteAll(forTopic: topic) dispatcher.protectedSend(message) { [weak self] error in if let error = error { - self?.logger.debug("[RelayClient]:Failed to unsubscribe from topic") + self?.logger.debug("Failed to unsubscribe from topic") completion(error) } else { self?.concurrentQueue.async(flags: .barrier) { @@ -161,9 +165,9 @@ public final class RelayClient { .sink { [unowned self] (_, subscriptionIds) in cancellable?.cancel() concurrentQueue.async(flags: .barrier) { [unowned self] in - logger.debug("[RelayClient]: Subscribed to topics: \(topics)") + logger.debug("Subscribed to topics: \(topics)") guard topics.count == subscriptionIds.count else { - logger.warn("RelayClient: Number of topics in (batch)subscribe does not match number of subscriptions") + logger.warn("Number of topics in (batch)subscribe does not match number of subscriptions") return } for i in 0..(account: Account, store: String, object: Object) async throws { let protocolMethod = SyncSetMethod() let params = StoreSet(key: object.databaseId, value: try object.json()) @@ -57,11 +73,6 @@ final class SyncService { logger.debug("Did delete value for \(store). Sent on: \(record.topic). Key: \n\(key)\n") } - - func create(account: Account, store: String) async throws { - let topic = try getTopic(for: account, store: store) - try await networkInteractor.subscribe(topic: topic) - } } private extension SyncService { @@ -87,14 +98,4 @@ private extension SyncService { } .store(in: &publishers) } - - func getTopic(for account: Account, store: String) throws -> String { - if let record = try? indexStore.getRecord(account: account, name: store) { - return record.topic - } - - let topic = try derivationService.deriveTopic(account: account, store: store) - indexStore.set(topic: topic, name: store, account: account) - return topic - } } diff --git a/Sources/WalletConnectSync/Stores/SyncStore.swift b/Sources/WalletConnectSync/Stores/SyncStore.swift index fae99c935..e6d93f32c 100644 --- a/Sources/WalletConnectSync/Stores/SyncStore.swift +++ b/Sources/WalletConnectSync/Stores/SyncStore.swift @@ -4,6 +4,7 @@ import Combine public enum SyncUpdate { case set(object: Object) case delete(object: Object) + case update(object: Object) } public final class SyncStore { @@ -39,11 +40,15 @@ public final class SyncStore { setupSubscriptions() } - public func initialize(for account: Account) async throws { + public func create(for account: Account) async throws { try await syncClient.create(account: account, store: name) } - public func setupSubscriptions(account: Account) throws { + public func subscribe(for account: Account) async throws { + try await syncClient.subscribe(account: account, store: name) + } + + public func setupDatabaseSubscriptions(account: Account) throws { let record = try indexStore.getRecord(account: account, name: name) objectStore.onUpdate = { [unowned self] in @@ -93,8 +98,9 @@ public final class SyncStore { return record.topic } - public func setInStore(objects: [Object], for account: Account) throws { + public func replaceInStore(objects: [Object], for account: Account) throws { let record = try indexStore.getRecord(account: account, name: name) + objectStore.deleteAll(for: record.topic) objectStore.set(elements: objects, for: record.topic) } } @@ -111,8 +117,10 @@ private extension SyncStore { switch update { case .set(let set): let object = try! JSONDecoder().decode(Object.self, from: Data(set.value.utf8)) + let exists = objectStore.exists(for: record.topic, id: object.databaseId) if try! setInStore(object: object, for: record.account) { - syncUpdateSubject.send((topic, record.account, .set(object: object))) + let update: SyncUpdate = exists ? .update(object: object) : .set(object: object) + syncUpdateSubject.send((topic, record.account, update)) } case .delete(let delete): if let object = get(for: delete.key), try! deleteInStore(id: delete.key, for: record.account) { diff --git a/Sources/WalletConnectSync/SyncClient.swift b/Sources/WalletConnectSync/SyncClient.swift index fbe09bf6f..e7e336d8e 100644 --- a/Sources/WalletConnectSync/SyncClient.swift +++ b/Sources/WalletConnectSync/SyncClient.swift @@ -40,6 +40,11 @@ public final class SyncClient { try await syncService.create(account: account, store: store) } + /// Subscribe for sync topic + public func subscribe(account: Account, store: String) async throws { + try await syncService.subscribe(account: account, store: store) + } + // Set value to store public func set( account: Account, diff --git a/Sources/WalletConnectUtils/Cacao/IATProvider.swift b/Sources/WalletConnectUtils/Cacao/IATProvider.swift index 22aefa45d..17637c22c 100644 --- a/Sources/WalletConnectUtils/Cacao/IATProvider.swift +++ b/Sources/WalletConnectUtils/Cacao/IATProvider.swift @@ -12,3 +12,11 @@ public struct DefaultIATProvider: IATProvider { return ISO8601DateFormatter().string(from: Date()) } } + +#if DEBUG +struct IATProviderMock: IATProvider { + var iat: String { + return "2022-10-10T23:03:35.700Z" + } +} +#endif diff --git a/Sources/WalletConnectUtils/KeyedDatabase.swift b/Sources/WalletConnectUtils/KeyedDatabase.swift index a7807aecd..c619d62d5 100644 --- a/Sources/WalletConnectUtils/KeyedDatabase.swift +++ b/Sources/WalletConnectUtils/KeyedDatabase.swift @@ -51,6 +51,11 @@ public class KeyedDatabase where Element: DatabaseObject { return (value.key, element) } + public func exists(for key: String, id: String) -> Bool { + let element = getElement(for: key, id: id) + return element != nil + } + @discardableResult public func set(elements: [Element], for key: String) -> Bool { var map = index[key] ?? [:] @@ -71,7 +76,7 @@ public class KeyedDatabase where Element: DatabaseObject { var map = index[key] ?? [:] guard - map[element.databaseId] == nil else { return false } + map[element.databaseId] == nil || map[element.databaseId] != element else { return false } map[element.databaseId] = element index[key] = map @@ -94,8 +99,6 @@ public class KeyedDatabase where Element: DatabaseObject { @discardableResult public func deleteAll(for key: String) -> Bool { - var map = index[key] - guard index[key] != nil else { return false } index[key] = nil diff --git a/Sources/WalletConnectUtils/Logger/ConsoleLogger.swift b/Sources/WalletConnectUtils/Logger/ConsoleLogger.swift index 3a0b0ee6e..8c8b1a736 100644 --- a/Sources/WalletConnectUtils/Logger/ConsoleLogger.swift +++ b/Sources/WalletConnectUtils/Logger/ConsoleLogger.swift @@ -1,27 +1,33 @@ import Foundation import Combine -/// Logging Protocol public protocol ConsoleLogging { var logsPublisher: AnyPublisher { get } - /// Writes a debug message to the log. - func debug(_ items: Any...) - - /// Writes an informative message to the log. - func info(_ items: Any...) - - /// Writes information about a warning to the log. - func warn(_ items: Any...) - - /// Writes information about an error to the log. - func error(_ items: Any...) - + func debug(_ items: Any..., file: String, function: String, line: Int) + func info(_ items: Any..., file: String, function: String, line: Int) + func warn(_ items: Any..., file: String, function: String, line: Int) + func error(_ items: Any..., file: String, function: String, line: Int) func setLogging(level: LoggingLevel) } -public class ConsoleLogger: ConsoleLogging { +public extension ConsoleLogging { + func debug(_ items: Any..., file: String = #file, function: String = #function, line: Int = #line) { + debug(items, file: file, function: function, line: line) + } + func info(_ items: Any..., file: String = #file, function: String = #function, line: Int = #line) { + info(items, file: file, function: function, line: line) + } + func warn(_ items: Any..., file: String = #file, function: String = #function, line: Int = #line) { + warn(items, file: file, function: function, line: line) + } + func error(_ items: Any..., file: String = #file, function: String = #function, line: Int = #line) { + error(items, file: file, function: function, line: line) + } +} + +public class ConsoleLogger { private var loggingLevel: LoggingLevel - private var suffix: String + private var prefix: String private var logsPublisherSubject = PassthroughSubject() public var logsPublisher: AnyPublisher { return logsPublisherSubject.eraseToAnyPublisher() @@ -31,57 +37,70 @@ public class ConsoleLogger: ConsoleLogging { self.loggingLevel = level } - public init(suffix: String? = nil, loggingLevel: LoggingLevel = .warn) { - self.suffix = suffix ?? "" + public init(prefix: String? = nil, loggingLevel: LoggingLevel = .warn) { + self.prefix = prefix ?? "" self.loggingLevel = loggingLevel } - public func debug(_ items: Any...) { - if loggingLevel >= .debug { - items.forEach { - let log = "\(suffix) \($0) - \(logFormattedDate(Date()))" - Swift.print(log) + private func logMessage(_ items: Any..., logType: LoggingLevel, file: String = #file, function: String = #function, line: Int = #line) { + let fileName = (file as NSString).lastPathComponent + items.forEach { + var log = "[\(fileName)]: \($0) - \(function) - line: \(line) - \(logFormattedDate(Date()))" + + switch logType { + case .debug: + log = "\(prefix) \(log)" logsPublisherSubject.send(.debug(log)) + case .info: + log = "\(prefix) ℹī¸ \(log)" + logsPublisherSubject.send(.info(log)) + case .warn: + log = "\(prefix) ⚠ī¸ \(log)" + logsPublisherSubject.send(.warn(log)) + case .error: + log = "\(prefix) â€ŧī¸ \(log)" + logsPublisherSubject.send(.error(log)) + case .off: + return } + + Swift.print(log) } } - public func info(_ items: Any...) { + private func logFormattedDate(_ date: Date) -> String { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss" + return formatter.string(from: date) + } +} + + +extension ConsoleLogger: ConsoleLogging { + public func debug(_ items: Any..., file: String, function: String, line: Int) { + if loggingLevel >= .debug { + logMessage(items, logType: .debug, file: file, function: function, line: line) + } + } + + public func info(_ items: Any..., file: String, function: String, line: Int) { if loggingLevel >= .info { - items.forEach { - let log = "\(suffix) \($0) - \(logFormattedDate(Date()))" - Swift.print(log) - logsPublisherSubject.send(.info(log)) } + logMessage(items, logType: .info, file: file, function: function, line: line) } } - public func warn(_ items: Any...) { + public func warn(_ items: Any..., file: String, function: String, line: Int) { if loggingLevel >= .warn { - items.forEach { - let log = "\(suffix) ⚠ī¸ \($0) - \(logFormattedDate(Date()))" - Swift.print(log) - logsPublisherSubject.send(.warn(log)) - } + logMessage(items, logType: .warn, file: file, function: function, line: line) } } - public func error(_ items: Any...) { + public func error(_ items: Any..., file: String, function: String, line: Int) { if loggingLevel >= .error { - items.forEach { - let log = "\(suffix) â€ŧī¸ \($0) - \(logFormattedDate(Date()))" - Swift.print(log) - logsPublisherSubject.send(.error(log)) - } + logMessage(items, logType: .error, file: file, function: function, line: line) } } -} - -fileprivate func logFormattedDate(_ date: Date) -> String { - let dateFormatter = DateFormatter() - dateFormatter.locale = NSLocale.current - dateFormatter.dateFormat = "HH:mm:ss.SSSS" - return dateFormatter.string(from: date) } @@ -90,11 +109,15 @@ public struct ConsoleLoggerMock: ConsoleLogging { public var logsPublisher: AnyPublisher { return PassthroughSubject().eraseToAnyPublisher() } + public init() {} - public func error(_ items: Any...) { } - public func debug(_ items: Any...) { } - public func info(_ items: Any...) { } - public func warn(_ items: Any...) { } + + public func debug(_ items: Any..., file: String, function: String, line: Int) { } + public func info(_ items: Any..., file: String, function: String, line: Int) { } + public func warn(_ items: Any..., file: String, function: String, line: Int) { } + public func error(_ items: Any..., file: String, function: String, line: Int) { } + public func setLogging(level: LoggingLevel) { } } #endif + diff --git a/Sources/Web3Inbox/ChatClient/ChatClientProxy.swift b/Sources/Web3Inbox/ChatClient/ChatClientProxy.swift index ef51bd3b4..b6fd91d00 100644 --- a/Sources/Web3Inbox/ChatClient/ChatClientProxy.swift +++ b/Sources/Web3Inbox/ChatClient/ChatClientProxy.swift @@ -5,7 +5,7 @@ final class ChatClientProxy { private let client: ChatClient var onSign: SigningCallback - var onResponse: ((RPCResponse) async throws -> Void)? + var onResponse: ((RPCResponse, RPCRequest) async throws -> Void)? init(client: ChatClient, onSign: @escaping SigningCallback) { self.client = client @@ -119,6 +119,6 @@ private extension ChatClientProxy { func respond(with object: Object = Blob(), request: RPCRequest) async throws { let response = RPCResponse(matchingRequest: request, result: object) - try await onResponse?(response) + try await onResponse?(response, request) } } diff --git a/Sources/Web3Inbox/ConfigParam.swift b/Sources/Web3Inbox/ConfigParam.swift index 429be268d..ad2ff6db7 100644 --- a/Sources/Web3Inbox/ConfigParam.swift +++ b/Sources/Web3Inbox/ConfigParam.swift @@ -3,6 +3,6 @@ import Foundation public enum ConfigParam { case chatEnabled - case pushEnabled + case notifyEnabled case settingsEnabled } diff --git a/Sources/Web3Inbox/PushClientProxy/PushClientProxy.swift b/Sources/Web3Inbox/NotifyClientProxy/NotifyClientProxy.swift similarity index 68% rename from Sources/Web3Inbox/PushClientProxy/PushClientProxy.swift rename to Sources/Web3Inbox/NotifyClientProxy/NotifyClientProxy.swift index 8ad36f8ba..4a2a39138 100644 --- a/Sources/Web3Inbox/PushClientProxy/PushClientProxy.swift +++ b/Sources/Web3Inbox/NotifyClientProxy/NotifyClientProxy.swift @@ -1,36 +1,28 @@ import Foundation -final class PushClientProxy { +final class NotifyClientProxy { - private let client: WalletPushClient + private let client: NotifyClient var onSign: SigningCallback - var onResponse: ((RPCResponse) async throws -> Void)? + var onResponse: ((RPCResponse, RPCRequest) async throws -> Void)? - init(client: WalletPushClient, onSign: @escaping SigningCallback) { + init(client: NotifyClient, onSign: @escaping SigningCallback) { self.client = client self.onSign = onSign } func request(_ request: RPCRequest) async throws { - guard let event = PushWebViewEvent(rawValue: request.method) + guard let event = NotifyWebViewEvent(rawValue: request.method) else { throw Errors.unregisteredMethod } // TODO: Handle register event switch event { - case .approve: - let params = try parse(ApproveRequest.self, params: request.params) - try await client.approve(id: params.id, onSign: onSign) - try await respond(request: request) case .update: let params = try parse(UpdateRequest.self, params: request.params) try await client.update(topic: params.topic, scope: params.scope) try await respond(request: request) - case .reject: - let params = try parse(RejectRequest.self, params: request.params) - try await client.reject(id: params.id) - try await respond(request: request) case .subscribe: let params = try parse(SubscribeRequest.self, params: request.params) try await client.subscribe(metadata: params.metadata, account: params.account, onSign: onSign) @@ -46,19 +38,19 @@ final class PushClientProxy { let params = try parse(DeleteSubscriptionRequest.self, params: request.params) try await client.deleteSubscription(topic: params.topic) try await respond(request: request) - case .deletePushMessage: - let params = try parse(DeletePushMessageRequest.self, params: request.params) - client.deletePushMessage(id: params.id.string) + case .deleteNotifyMessage: + let params = try parse(DeleteNotifyMessageRequest.self, params: request.params) + client.deleteNotifyMessage(id: params.id.string) try await respond(request: request) - case .enableSync: - let params = try parse(EnableSyncRequest.self, params: request.params) - try await client.enableSync(account: params.account, onSign: onSign) + case .register: + let params = try parse(RegisterRequest.self, params: request.params) + try await client.register(account: params.account, onSign: onSign) try await respond(request: request) } } } -private extension PushClientProxy { +private extension NotifyClientProxy { private typealias Blob = Dictionary @@ -93,11 +85,11 @@ private extension PushClientProxy { let topic: String } - struct DeletePushMessageRequest: Codable { + struct DeleteNotifyMessageRequest: Codable { let id: RPCID } - struct EnableSyncRequest: Codable { + struct RegisterRequest: Codable { let account: Account } @@ -109,6 +101,6 @@ private extension PushClientProxy { func respond(with object: Object = Blob(), request: RPCRequest) async throws { let response = RPCResponse(matchingRequest: request, result: object) - try await onResponse?(response) + try await onResponse?(response, request) } } diff --git a/Sources/Web3Inbox/NotifyClientProxy/NotifyClientRequest.swift b/Sources/Web3Inbox/NotifyClientProxy/NotifyClientRequest.swift new file mode 100644 index 000000000..b152330ce --- /dev/null +++ b/Sources/Web3Inbox/NotifyClientProxy/NotifyClientRequest.swift @@ -0,0 +1,12 @@ +import Foundation + +enum NotifyClientRequest: String { + case notifyMessage = "notify_message" + case notifyUpdate = "notify_update" + case notifyDelete = "notify_delete" + case notifySubscription = "notify_subscription" + + var method: String { + return rawValue + } +} diff --git a/Sources/Web3Inbox/PushClientProxy/PushClientRequestSubscriber.swift b/Sources/Web3Inbox/NotifyClientProxy/NotifyClientRequestSubscriber.swift similarity index 57% rename from Sources/Web3Inbox/PushClientProxy/PushClientRequestSubscriber.swift rename to Sources/Web3Inbox/NotifyClientProxy/NotifyClientRequestSubscriber.swift index ad77322d5..73b85c486 100644 --- a/Sources/Web3Inbox/PushClientProxy/PushClientRequestSubscriber.swift +++ b/Sources/Web3Inbox/NotifyClientProxy/NotifyClientRequestSubscriber.swift @@ -1,16 +1,16 @@ import Foundation import Combine -final class PushClientRequestSubscriber { +final class NotifyClientRequestSubscriber { private var publishers: Set = [] - private let client: WalletPushClient + private let client: NotifyClient private let logger: ConsoleLogging var onRequest: ((RPCRequest) async throws -> Void)? - init(client: WalletPushClient, logger: ConsoleLogging) { + init(client: NotifyClient, logger: ConsoleLogging) { self.client = client self.logger = logger @@ -18,31 +18,22 @@ final class PushClientRequestSubscriber { } func setupSubscriptions() { - client.requestPublisher.sink { [unowned self] id, account, metadata in - let params = RequestPayload(id: id, account: account, metadata: metadata) - handle(event: .pushRequest, params: params) - }.store(in: &publishers) - - client.pushMessagePublisher.sink { [unowned self] record in - handle(event: .pushMessage, params: record.message) - }.store(in: &publishers) - - client.deleteSubscriptionPublisher.sink { [unowned self] record in - handle(event: .pushDelete, params: record) + client.notifyMessagePublisher.sink { [unowned self] record in + handle(event: .notifyMessage, params: record.message) }.store(in: &publishers) client.newSubscriptionPublisher.sink { [unowned self] subscription in - handle(event: .pushSubscription, params: subscription) + handle(event: .notifySubscription, params: subscription) }.store(in: &publishers) client.deleteSubscriptionPublisher.sink { [unowned self] topic in - handle(event: .pushDelete, params: topic) + handle(event: .notifyDelete, params: topic) }.store(in: &publishers) client.updateSubscriptionPublisher.sink { [unowned self] record in switch record { case .success(let subscription): - handle(event: .pushUpdate, params: subscription) + handle(event: .notifyUpdate, params: subscription) case .failure: //TODO - handle error break @@ -51,7 +42,7 @@ final class PushClientRequestSubscriber { } } -private extension PushClientRequestSubscriber { +private extension NotifyClientRequestSubscriber { struct RequestPayload: Codable { let id: RPCID @@ -59,7 +50,7 @@ private extension PushClientRequestSubscriber { let metadata: AppMetadata } - func handle(event: PushClientRequest, params: Codable) { + func handle(event: NotifyClientRequest, params: Codable) { Task { do { let request = RPCRequest( diff --git a/Sources/Web3Inbox/PushClientProxy/PushClientRequest.swift b/Sources/Web3Inbox/PushClientProxy/PushClientRequest.swift deleted file mode 100644 index e03e3378c..000000000 --- a/Sources/Web3Inbox/PushClientProxy/PushClientRequest.swift +++ /dev/null @@ -1,13 +0,0 @@ -import Foundation - -enum PushClientRequest: String { - case pushRequest = "push_request" - case pushMessage = "push_message" - case pushUpdate = "push_update" - case pushDelete = "push_delete" - case pushSubscription = "push_subscription" - - var method: String { - return rawValue - } -} diff --git a/Sources/Web3Inbox/Web3Inbox.swift b/Sources/Web3Inbox/Web3Inbox.swift index e2555049e..17bbc7f55 100644 --- a/Sources/Web3Inbox/Web3Inbox.swift +++ b/Sources/Web3Inbox/Web3Inbox.swift @@ -7,7 +7,7 @@ public final class Web3Inbox { guard let account, let config = config, let onSign else { fatalError("Error - you must call Web3Inbox.configure(_:) before accessing the shared instance.") } - return Web3InboxClientFactory.create(chatClient: Chat.instance, pushClient: Push.wallet, account: account, config: config, onSign: onSign) + return Web3InboxClientFactory.create(chatClient: Chat.instance, notifyClient: Notify.instance, account: account, config: config, onSign: onSign) }() private static var account: Account? @@ -22,12 +22,13 @@ public final class Web3Inbox { bip44: BIP44Provider, config: [ConfigParam: Bool] = [:], environment: APNSEnvironment, + crypto: CryptoProvider, onSign: @escaping SigningCallback ) { Web3Inbox.account = account Web3Inbox.config = config Web3Inbox.onSign = onSign Chat.configure(bip44: bip44) - Push.configure(environment: environment) + Notify.configure(environment: environment, crypto: crypto) } } diff --git a/Sources/Web3Inbox/Web3InboxClient.swift b/Sources/Web3Inbox/Web3InboxClient.swift index 3a17e4d0e..0325b6991 100644 --- a/Sources/Web3Inbox/Web3InboxClient.swift +++ b/Sources/Web3Inbox/Web3InboxClient.swift @@ -6,16 +6,16 @@ public final class Web3InboxClient { private let webView: WKWebView private var account: Account private let logger: ConsoleLogging - private let pushClient: WalletPushClient + private let notifyClient: NotifyClient private let chatClientProxy: ChatClientProxy private let chatClientSubscriber: ChatClientRequestSubscriber - private let pushClientProxy: PushClientProxy - private let pushClientSubscriber: PushClientRequestSubscriber + private let notifyClientProxy: NotifyClientProxy + private let notifyClientSubscriber: NotifyClientRequestSubscriber private let chatWebviewProxy: WebViewProxy - private let pushWebviewProxy: WebViewProxy + private let notifyWebviewProxy: WebViewProxy private let webviewSubscriber: WebViewRequestSubscriber @@ -26,11 +26,11 @@ public final class Web3InboxClient { chatClientProxy: ChatClientProxy, clientSubscriber: ChatClientRequestSubscriber, chatWebviewProxy: WebViewProxy, - pushWebviewProxy: WebViewProxy, + notifyWebviewProxy: WebViewProxy, webviewSubscriber: WebViewRequestSubscriber, - pushClientProxy: PushClientProxy, - pushClientSubscriber: PushClientRequestSubscriber, - pushClient: WalletPushClient + notifyClientProxy: NotifyClientProxy, + notifyClientSubscriber: NotifyClientRequestSubscriber, + notifyClient: NotifyClient ) { self.webView = webView self.account = account @@ -38,11 +38,11 @@ public final class Web3InboxClient { self.chatClientProxy = chatClientProxy self.chatClientSubscriber = clientSubscriber self.chatWebviewProxy = chatWebviewProxy - self.pushWebviewProxy = pushWebviewProxy + self.notifyWebviewProxy = notifyWebviewProxy self.webviewSubscriber = webviewSubscriber - self.pushClientProxy = pushClientProxy - self.pushClientSubscriber = pushClientSubscriber - self.pushClient = pushClient + self.notifyClientProxy = notifyClientProxy + self.notifyClientSubscriber = notifyClientSubscriber + self.notifyClient = notifyClient setupSubscriptions() } @@ -59,7 +59,7 @@ public final class Web3InboxClient { } public func register(deviceToken: Data) async throws { - try await pushClient.register(deviceToken: deviceToken) + try await notifyClient.register(deviceToken: deviceToken) } } @@ -71,8 +71,8 @@ private extension Web3InboxClient { // Chat - chatClientProxy.onResponse = { [unowned self] response in - try await self.chatWebviewProxy.respond(response) + chatClientProxy.onResponse = { [unowned self] response, request in + try await self.chatWebviewProxy.respond(response, request) } chatClientSubscriber.onRequest = { [unowned self] request in @@ -84,19 +84,19 @@ private extension Web3InboxClient { try await self.chatClientProxy.request(request) } - // Push + // Notify - pushClientProxy.onResponse = { [unowned self] response in - try await self.pushWebviewProxy.respond(response) + notifyClientProxy.onResponse = { [unowned self] response, request in + try await self.notifyWebviewProxy.respond(response, request) } - pushClientSubscriber.onRequest = { [unowned self] request in - try await self.pushWebviewProxy.request(request) + notifyClientSubscriber.onRequest = { [unowned self] request in + try await self.notifyWebviewProxy.request(request) } - webviewSubscriber.onPushRequest = { [unowned self] request in - logger.debug("w3i: push method \(request.method) requested") - try await self.pushClientProxy.request(request) + webviewSubscriber.onNotifyRequest = { [unowned self] request in + logger.debug("w3i: notify method \(request.method) requested") + try await self.notifyClientProxy.request(request) } } diff --git a/Sources/Web3Inbox/Web3InboxClientFactory.swift b/Sources/Web3Inbox/Web3InboxClientFactory.swift index afff11981..3257b4b7b 100644 --- a/Sources/Web3Inbox/Web3InboxClientFactory.swift +++ b/Sources/Web3Inbox/Web3InboxClientFactory.swift @@ -5,24 +5,24 @@ final class Web3InboxClientFactory { static func create( chatClient: ChatClient, - pushClient: WalletPushClient, + notifyClient: NotifyClient, account: Account, config: [ConfigParam: Bool], onSign: @escaping SigningCallback ) -> Web3InboxClient { let url = buildUrl(account: account, config: config) - let logger = ConsoleLogger(suffix: "đŸ“Ŧ", loggingLevel: .debug) + let logger = ConsoleLogger(prefix: "đŸ“Ŧ", loggingLevel: .debug) let webviewSubscriber = WebViewRequestSubscriber(url: url, logger: logger) let webView = WebViewFactory(url: url, webviewSubscriber: webviewSubscriber).create() let chatWebViewProxy = WebViewProxy(webView: webView, scriptFormatter: ChatWebViewScriptFormatter(), logger: logger) - let pushWebViewProxy = WebViewProxy(webView: webView, scriptFormatter: PushWebViewScriptFormatter(), logger: logger) + let notifyWebViewProxy = WebViewProxy(webView: webView, scriptFormatter: NotifyWebViewScriptFormatter(), logger: logger) let clientProxy = ChatClientProxy(client: chatClient, onSign: onSign) let clientSubscriber = ChatClientRequestSubscriber(chatClient: chatClient, logger: logger) - let pushClientProxy = PushClientProxy(client: pushClient, onSign: onSign) - let pushClientSubscriber = PushClientRequestSubscriber(client: pushClient, logger: logger) + let notifyClientProxy = NotifyClientProxy(client: notifyClient, onSign: onSign) + let notifyClientSubscriber = NotifyClientRequestSubscriber(client: notifyClient, logger: logger) return Web3InboxClient( webView: webView, @@ -31,17 +31,17 @@ final class Web3InboxClientFactory { chatClientProxy: clientProxy, clientSubscriber: clientSubscriber, chatWebviewProxy: chatWebViewProxy, - pushWebviewProxy: pushWebViewProxy, + notifyWebviewProxy: notifyWebViewProxy, webviewSubscriber: webviewSubscriber, - pushClientProxy: pushClientProxy, - pushClientSubscriber: pushClientSubscriber, - pushClient: pushClient + notifyClientProxy: notifyClientProxy, + notifyClientSubscriber: notifyClientSubscriber, + notifyClient: notifyClient ) } private static func buildUrl(account: Account, config: [ConfigParam: Bool]) -> URL { var urlComponents = URLComponents(string: "https://web3inbox-dev-hidden.vercel.app/")! - var queryItems = [URLQueryItem(name: "chatProvider", value: "ios"), URLQueryItem(name: "pushProvider", value: "ios"), URLQueryItem(name: "account", value: account.address), URLQueryItem(name: "authProvider", value: "ios")] + var queryItems = [URLQueryItem(name: "chatProvider", value: "ios"), URLQueryItem(name: "notifyProvider", value: "ios"), URLQueryItem(name: "account", value: account.address), URLQueryItem(name: "authProvider", value: "ios")] for param in config.filter({ $0.value == false}) { queryItems.append(URLQueryItem(name: "\(param.key)", value: "false")) diff --git a/Sources/Web3Inbox/Web3InboxImports.swift b/Sources/Web3Inbox/Web3InboxImports.swift index fd78f4977..b03055b3f 100644 --- a/Sources/Web3Inbox/Web3InboxImports.swift +++ b/Sources/Web3Inbox/Web3InboxImports.swift @@ -1,4 +1,4 @@ #if !CocoaPods @_exported import WalletConnectChat -@_exported import WalletConnectPush +@_exported import WalletConnectNotify #endif diff --git a/Sources/Web3Inbox/WebView/PushWebViewEvent.swift b/Sources/Web3Inbox/WebView/NotifyWebViewEvent.swift similarity index 56% rename from Sources/Web3Inbox/WebView/PushWebViewEvent.swift rename to Sources/Web3Inbox/WebView/NotifyWebViewEvent.swift index d320a88d7..e9a8e954b 100644 --- a/Sources/Web3Inbox/WebView/PushWebViewEvent.swift +++ b/Sources/Web3Inbox/WebView/NotifyWebViewEvent.swift @@ -1,13 +1,11 @@ import Foundation -enum PushWebViewEvent: String { - case approve +enum NotifyWebViewEvent: String { case update - case reject case subscribe case getActiveSubscriptions case getMessageHistory case deleteSubscription - case deletePushMessage - case enableSync + case deleteNotifyMessage + case register } diff --git a/Sources/Web3Inbox/WebView/WebViewFactory.swift b/Sources/Web3Inbox/WebView/WebViewFactory.swift index 5a6fc90ad..1d8cf45c1 100644 --- a/Sources/Web3Inbox/WebView/WebViewFactory.swift +++ b/Sources/Web3Inbox/WebView/WebViewFactory.swift @@ -22,7 +22,7 @@ final class WebViewFactory { ) configuration.userContentController.add( webviewSubscriber, - name: WebViewRequestSubscriber.push + name: WebViewRequestSubscriber.notify ) let webview = WKWebView(frame: .zero, configuration: configuration) diff --git a/Sources/Web3Inbox/WebView/WebViewProxy.swift b/Sources/Web3Inbox/WebView/WebViewProxy.swift index af3d0d8d7..7aa396d93 100644 --- a/Sources/Web3Inbox/WebView/WebViewProxy.swift +++ b/Sources/Web3Inbox/WebView/WebViewProxy.swift @@ -17,9 +17,9 @@ actor WebViewProxy { } @MainActor - func respond(_ response: RPCResponse) async throws { + func respond(_ response: RPCResponse, _ request: RPCRequest) async throws { let body = try response.json(dateEncodingStrategy: .millisecondsSince1970) - logger.debug("resonding to w3i with \(body)") + logger.debug("resonding to w3i request \(request.method) with \(body)") let script = scriptFormatter.formatScript(body: body) webView.evaluateJavaScript(script, completionHandler: nil) } @@ -44,8 +44,8 @@ class ChatWebViewScriptFormatter: WebViewScriptFormatter { } } -class PushWebViewScriptFormatter: WebViewScriptFormatter { +class NotifyWebViewScriptFormatter: WebViewScriptFormatter { func formatScript(body: String) -> String { - return "window.web3inbox.push.postMessage(\(body))" + return "window.web3inbox.notify.postMessage(\(body))" } } diff --git a/Sources/Web3Inbox/WebView/WebViewRequestSubscriber.swift b/Sources/Web3Inbox/WebView/WebViewRequestSubscriber.swift index 8cf66e4fd..4add0cd58 100644 --- a/Sources/Web3Inbox/WebView/WebViewRequestSubscriber.swift +++ b/Sources/Web3Inbox/WebView/WebViewRequestSubscriber.swift @@ -4,10 +4,10 @@ import WebKit final class WebViewRequestSubscriber: NSObject, WKScriptMessageHandler { static let chat = "web3inboxChat" - static let push = "web3inboxPush" + static let notify = "web3inboxNotify" var onChatRequest: ((RPCRequest) async throws -> Void)? - var onPushRequest: ((RPCRequest) async throws -> Void)? + var onNotifyRequest: ((RPCRequest) async throws -> Void)? private let url: URL private let logger: ConsoleLogging @@ -35,8 +35,8 @@ final class WebViewRequestSubscriber: NSObject, WKScriptMessageHandler { switch name { case Self.chat: try await onChatRequest?(request) - case Self.push: - try await onPushRequest?(request) + case Self.notify: + try await onNotifyRequest?(request) default: break } diff --git a/Sources/Web3Wallet/Web3Wallet.swift b/Sources/Web3Wallet/Web3Wallet.swift index 5a7c2e0a5..a00f42b4b 100644 --- a/Sources/Web3Wallet/Web3Wallet.swift +++ b/Sources/Web3Wallet/Web3Wallet.swift @@ -27,7 +27,7 @@ public class Web3Wallet { authClient: Auth.instance, signClient: Sign.instance, pairingClient: Pair.instance as! PairingClient, - echoClient: Echo.instance + pushClient: Push.instance ) }() @@ -42,12 +42,12 @@ public class Web3Wallet { static public func configure( metadata: AppMetadata, crypto: CryptoProvider, - echoHost: String = "echo.walletconnect.com", + pushHost: String = "echo.walletconnect.com", environment: APNSEnvironment = .production ) { Pair.configure(metadata: metadata) Auth.configure(crypto: crypto) - Echo.configure(echoHost: echoHost, environment: environment) + Push.configure(pushHost: pushHost, environment: environment) Web3Wallet.config = Web3Wallet.Config(crypto: crypto) } } diff --git a/Sources/Web3Wallet/Web3WalletClient.swift b/Sources/Web3Wallet/Web3WalletClient.swift index 853d338c9..a90093a10 100644 --- a/Sources/Web3Wallet/Web3WalletClient.swift +++ b/Sources/Web3Wallet/Web3WalletClient.swift @@ -67,7 +67,7 @@ public class Web3WalletClient { private let authClient: AuthClientProtocol private let signClient: SignClientProtocol private let pairingClient: PairingClientProtocol - private let echoClient: EchoClientProtocol + private let pushClient: PushClientProtocol private var account: Account? @@ -75,12 +75,12 @@ public class Web3WalletClient { authClient: AuthClientProtocol, signClient: SignClientProtocol, pairingClient: PairingClientProtocol, - echoClient: EchoClientProtocol + pushClient: PushClientProtocol ) { self.authClient = authClient self.signClient = signClient self.pairingClient = pairingClient - self.echoClient = echoClient + self.pushClient = pushClient } /// For a wallet to approve a session proposal. @@ -212,8 +212,8 @@ public class Web3WalletClient { try authClient.getPendingRequests() } - public func registerEchoClient(deviceToken: Data) async throws { - try await echoClient.register(deviceToken: deviceToken) + public func registerPushClient(deviceToken: Data) async throws { + try await pushClient.register(deviceToken: deviceToken) } /// Delete all stored data such as: pairings, sessions, keys @@ -230,8 +230,8 @@ public class Web3WalletClient { #if DEBUG extension Web3WalletClient { - public func registerEchoClient(deviceToken: String) async throws { - try await echoClient.register(deviceToken: deviceToken) + public func registerPushClient(deviceToken: String) async throws { + try await pushClient.register(deviceToken: deviceToken) } } #endif diff --git a/Sources/Web3Wallet/Web3WalletClientFactory.swift b/Sources/Web3Wallet/Web3WalletClientFactory.swift index b27654757..99b5cc969 100644 --- a/Sources/Web3Wallet/Web3WalletClientFactory.swift +++ b/Sources/Web3Wallet/Web3WalletClientFactory.swift @@ -5,13 +5,13 @@ public struct Web3WalletClientFactory { authClient: AuthClientProtocol, signClient: SignClientProtocol, pairingClient: PairingClientProtocol, - echoClient: EchoClientProtocol + pushClient: PushClientProtocol ) -> Web3WalletClient { return Web3WalletClient( authClient: authClient, signClient: signClient, pairingClient: pairingClient, - echoClient: echoClient + pushClient: pushClient ) } } diff --git a/Sources/Web3Wallet/Web3WalletImports.swift b/Sources/Web3Wallet/Web3WalletImports.swift index c27041056..f2626dea0 100644 --- a/Sources/Web3Wallet/Web3WalletImports.swift +++ b/Sources/Web3Wallet/Web3WalletImports.swift @@ -1,6 +1,6 @@ #if !CocoaPods @_exported import Auth @_exported import WalletConnectSign -@_exported import WalletConnectEcho +@_exported import WalletConnectPush @_exported import WalletConnectVerify #endif diff --git a/Tests/NotifyTests/Mocks/MockPushStoring.swift b/Tests/NotifyTests/Mocks/MockNotifyStoring.swift similarity index 58% rename from Tests/NotifyTests/Mocks/MockPushStoring.swift rename to Tests/NotifyTests/Mocks/MockNotifyStoring.swift index 06dc07960..4f1be0991 100644 --- a/Tests/NotifyTests/Mocks/MockPushStoring.swift +++ b/Tests/NotifyTests/Mocks/MockNotifyStoring.swift @@ -1,23 +1,22 @@ - import Foundation -@testable import WalletConnectPush +@testable import WalletConnectNotify -class MockPushStoring: PushStoring { - var subscriptions: [PushSubscription] +class MockNotifyStoring: NotifyStoring { + var subscriptions: [NotifySubscription] - init(subscriptions: [PushSubscription]) { + init(subscriptions: [NotifySubscription]) { self.subscriptions = subscriptions } - func getSubscriptions() -> [PushSubscription] { + func getSubscriptions() -> [NotifySubscription] { return subscriptions } - func getSubscription(topic: String) -> PushSubscription? { + func getSubscription(topic: String) -> NotifySubscription? { return subscriptions.first { $0.topic == topic } } - func setSubscription(_ subscription: PushSubscription) async throws { + func setSubscription(_ subscription: NotifySubscription) async throws { if let index = subscriptions.firstIndex(where: { $0.topic == subscription.topic }) { subscriptions[index] = subscription } else { diff --git a/Tests/NotifyTests/Mocks/MockNotifyUpdateRequester.swift b/Tests/NotifyTests/Mocks/MockNotifyUpdateRequester.swift index 8f4d698e1..927676953 100644 --- a/Tests/NotifyTests/Mocks/MockNotifyUpdateRequester.swift +++ b/Tests/NotifyTests/Mocks/MockNotifyUpdateRequester.swift @@ -1,6 +1,6 @@ import Foundation -@testable import WalletConnectPush +@testable import WalletConnectNotify class MockNotifyUpdateRequester: NotifyUpdateRequesting { diff --git a/Tests/NotifyTests/Stubs/PushSubscription.swift b/Tests/NotifyTests/Stubs/NotifySubscription.swift similarity index 74% rename from Tests/NotifyTests/Stubs/PushSubscription.swift rename to Tests/NotifyTests/Stubs/NotifySubscription.swift index de40c5772..7251c8477 100644 --- a/Tests/NotifyTests/Stubs/PushSubscription.swift +++ b/Tests/NotifyTests/Stubs/NotifySubscription.swift @@ -1,15 +1,14 @@ - import Foundation -@testable import WalletConnectPush +@testable import WalletConnectNotify -extension PushSubscription { - static func stub(topic: String, expiry: Date) -> PushSubscription { +extension NotifySubscription { + static func stub(topic: String, expiry: Date) -> NotifySubscription { let account = Account(chainIdentifier: "eip155:1", address: "0x15bca56b6e2728aec2532df9d436bd1600e86688")! let relay = RelayProtocolOptions.stub() let metadata = AppMetadata.stub() let symKey = "key1" - return PushSubscription( + return NotifySubscription( topic: topic, account: account, relay: relay, diff --git a/Tests/NotifyTests/SubscriptionsAutoUpdaterTests.swift b/Tests/NotifyTests/SubscriptionsAutoUpdaterTests.swift index 60f0c09c3..357281daf 100644 --- a/Tests/NotifyTests/SubscriptionsAutoUpdaterTests.swift +++ b/Tests/NotifyTests/SubscriptionsAutoUpdaterTests.swift @@ -1,33 +1,34 @@ import Foundation import XCTest import TestingUtils -@testable import WalletConnectPush +@testable import WalletConnectNotify class SubscriptionsAutoUpdaterTests: XCTestCase { var sut: SubscriptionsAutoUpdater! func testUpdateSubscriptionsIfNeeded() async { - let subscriptions: [PushSubscription] = [ - PushSubscription.stub(topic: "topic1", expiry: Date().addingTimeInterval(60 * 60 * 24 * 20)), - PushSubscription.stub(topic: "topic2", expiry: Date().addingTimeInterval(60 * 60 * 24 * 10)), - PushSubscription.stub(topic: "topic3", expiry: Date().addingTimeInterval(60 * 60 * 24 * 30)) + let subscriptions: [NotifySubscription] = [ + NotifySubscription.stub(topic: "topic1", expiry: Date().addingTimeInterval(60 * 60 * 24 * 20)), + NotifySubscription.stub(topic: "topic2", expiry: Date().addingTimeInterval(60 * 60 * 24 * 10)), + NotifySubscription.stub(topic: "topic3", expiry: Date().addingTimeInterval(60 * 60 * 24 * 30)) ] let expectation = expectation(description: "update") let notifyUpdateRequester = MockNotifyUpdateRequester() let logger = ConsoleLoggerMock() - let pushStorage = MockPushStoring(subscriptions: subscriptions) + let pushStorage = MockNotifyStoring(subscriptions: subscriptions) notifyUpdateRequester.completionHandler = { if notifyUpdateRequester.updatedTopics.contains("topic2") { expectation.fulfill() } } - - sut = SubscriptionsAutoUpdater(notifyUpdateRequester: notifyUpdateRequester, - logger: logger, - pushStorage: pushStorage) + + sut = SubscriptionsAutoUpdater( + notifyUpdateRequester: notifyUpdateRequester, + logger: logger, + notifyStorage: pushStorage) await waitForExpectations(timeout: 1, handler: nil) diff --git a/Tests/RelayerTests/AuthTests/JWTTests.swift b/Tests/RelayerTests/AuthTests/JWTTests.swift index 83f8ebf4c..40ad5be31 100644 --- a/Tests/RelayerTests/AuthTests/JWTTests.swift +++ b/Tests/RelayerTests/AuthTests/JWTTests.swift @@ -36,6 +36,6 @@ extension RelayAuthPayload.Claims { let aud = "wss://relay.walletconnect.com" let expDate = Calendar.current.date(byAdding: components, to: iatDate)! let exp = UInt64(expDate.timeIntervalSince1970) - return RelayAuthPayload.Claims(iss: iss, sub: sub, aud: aud, iat: iat, exp: exp) + return RelayAuthPayload.Claims(iss: iss, sub: sub, aud: aud, iat: iat, exp: exp, act: nil) } } diff --git a/Tests/WalletConnectKMSTests/SerialiserTests.swift b/Tests/WalletConnectKMSTests/SerialiserTests.swift index 627171b5a..bf36da69d 100644 --- a/Tests/WalletConnectKMSTests/SerialiserTests.swift +++ b/Tests/WalletConnectKMSTests/SerialiserTests.swift @@ -12,10 +12,10 @@ final class SerializerTests: XCTestCase { override func setUp() { self.myKms = KeyManagementServiceMock() - self.mySerializer = Serializer(kms: myKms) + self.mySerializer = Serializer(kms: myKms, logger: ConsoleLoggerMock()) self.peerKms = KeyManagementServiceMock() - self.peerSerializer = Serializer(kms: peerKms) + self.peerSerializer = Serializer(kms: peerKms, logger: ConsoleLoggerMock()) } func testSerializeDeserializeType0Envelope() { diff --git a/Tests/WalletConnectUtilsTests/KeyedDatabaseTests.swift b/Tests/WalletConnectUtilsTests/KeyedDatabaseTests.swift new file mode 100644 index 000000000..fc1674fbf --- /dev/null +++ b/Tests/WalletConnectUtilsTests/KeyedDatabaseTests.swift @@ -0,0 +1,40 @@ +import XCTest +import JSONRPC +import TestingUtils +@testable import WalletConnectUtils + +final class KeyedDatabaseTests: XCTestCase { + + struct Object: DatabaseObject { + let key: String + let value: String + + var databaseId: String { + return key + } + } + + let storageKey: String = "storageKey" + + var sut: KeyedDatabase! + + override func setUp() { + sut = KeyedDatabase(storage: RuntimeKeyValueStorage(), identifier: "identifier") + } + + override func tearDown() { + sut = nil + } + + func testIsChanged() throws { + let new = Object(key: "key1", value: "value1") + let updated = Object(key: "key1", value: "value2") + + sut.set(element: new, for: storageKey) + sut.set(element: updated, for: storageKey) + + let value = sut.getElement(for: storageKey, id: updated.databaseId) + + XCTAssertEqual(value, updated) + } +} diff --git a/Tests/Web3WalletTests/Web3WalletTests.swift b/Tests/Web3WalletTests/Web3WalletTests.swift index 26f98d81a..39165751d 100644 --- a/Tests/Web3WalletTests/Web3WalletTests.swift +++ b/Tests/Web3WalletTests/Web3WalletTests.swift @@ -3,14 +3,14 @@ import Combine @testable import Auth @testable import Web3Wallet -@testable import WalletConnectEcho +@testable import WalletConnectPush final class Web3WalletTests: XCTestCase { var web3WalletClient: Web3WalletClient! var authClient: AuthClientMock! var signClient: SignClientMock! var pairingClient: PairingClientMock! - var echoClient: EchoClientMock! + var pushClient: PushClientMock! private var disposeBag = Set() @@ -18,13 +18,13 @@ final class Web3WalletTests: XCTestCase { authClient = AuthClientMock() signClient = SignClientMock() pairingClient = PairingClientMock() - echoClient = EchoClientMock() + pushClient = PushClientMock() web3WalletClient = Web3WalletClientFactory.create( authClient: authClient, signClient: signClient, pairingClient: pairingClient, - echoClient: echoClient + pushClient: pushClient ) } @@ -268,11 +268,11 @@ final class Web3WalletTests: XCTestCase { XCTAssertEqual(1, web3WalletClient.getPairings().count) } - func testEchoClientRegisterCalled() async { - try! await echoClient.register(deviceToken: Data()) - XCTAssertTrue(echoClient.registedCalled) - echoClient.registedCalled = false - try! await echoClient.register(deviceToken: "") - XCTAssertTrue(echoClient.registedCalled) + func testPushClientRegisterCalled() async { + try! await pushClient.register(deviceToken: Data()) + XCTAssertTrue(pushClient.registedCalled) + pushClient.registedCalled = false + try! await pushClient.register(deviceToken: "") + XCTAssertTrue(pushClient.registedCalled) } }