diff --git a/.github/workflows/swift.yml b/.github/workflows/swift.yml index 257ebc38a..7dc66c564 100644 --- a/.github/workflows/swift.yml +++ b/.github/workflows/swift.yml @@ -7,12 +7,11 @@ on: branches: [ main, develop ] jobs: - build: + build: runs-on: macos-latest steps: - - name: Checkout - uses: actions/checkout@v2 + - uses: actions/checkout@v2 - name: Setup Xcode Version uses: maxim-lobanov/setup-xcode@v1 @@ -21,24 +20,50 @@ jobs: - uses: actions/cache@v2 with: - path: .build + path: | + .build + SourcePackages key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }} restore-keys: | ${{ runner.os }}-spm- # Package builds - - name: Build Package - run: swift build -v - name: Run tests - run: swift test -v + run: "xcodebuild \ + -project Example/ExampleApp.xcodeproj \ + -scheme WalletConnect \ + -clonedSourcePackagesDirPath SourcePackages \ + -sdk iphonesimulator" - # Example app builds + # Integration tests + - name: Run integration tests + run: "xcodebuild \ + -project Example/ExampleApp.xcodeproj \ + -scheme IntegrationTests \ + -clonedSourcePackagesDirPath SourcePackages \ + -destination 'platform=iOS Simulator,name=iPhone 13' test" + + # Wallet build - name: Build Example Wallet - run: xcodebuild -project Example/ExampleApp.xcodeproj -scheme Wallet -sdk iphonesimulator + run: "xcodebuild \ + -project Example/ExampleApp.xcodeproj \ + -scheme Wallet \ + -clonedSourcePackagesDirPath SourcePackages \ + -sdk iphonesimulator" + + # DApp build - name: Build Example Dapp - run: xcodebuild -project Example/ExampleApp.xcodeproj -scheme DApp -sdk iphonesimulator + run: "xcodebuild \ + -project Example/ExampleApp.xcodeproj \ + -scheme DApp \ + -clonedSourcePackagesDirPath SourcePackages \ + -sdk iphonesimulator" # UI tests - name: UI Tests - run: xcodebuild -project Example/ExampleApp.xcodeproj -scheme UITests -destination 'platform=iOS Simulator,name=iPhone 13' test + run: "xcodebuild \ + -project Example/ExampleApp.xcodeproj \ + -scheme UITests \ + -clonedSourcePackagesDirPath SourcePackages \ + -destination 'platform=iOS Simulator,name=iPhone 13' test" continue-on-error: true diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect.xcscheme b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect.xcscheme index d7d784447..3f9debd20 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect.xcscheme +++ b/.swiftpm/xcode/xcshareddata/xcschemes/WalletConnect.xcscheme @@ -192,26 +192,6 @@ - - - - - - - - + + + + WebSocketConnecting { + return WebSocket(url: url) + } +} class SceneDelegate: UIResponder, UIWindowSceneDelegate { @@ -18,7 +28,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { description: "a description", url: "wallet.connect", icons: ["https://avatars.githubusercontent.com/u/37784886"]) - Sign.configure(Sign.Config(metadata: metadata, projectId: "8ba9ee138960775e5231b70cc5ef1c3a")) + + Sign.configure(metadata: metadata, projectId: "8ba9ee138960775e5231b70cc5ef1c3a", socketFactory: SocketFactory()) if CommandLine.arguments.contains("-cleanInstall") { try? Sign.instance.cleanup() diff --git a/Example/DappTests/DappTests.swift b/Example/DappTests/DappTests.swift deleted file mode 100644 index 2792b20c0..000000000 --- a/Example/DappTests/DappTests.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// DappTests.swift -// DappTests -// -// Created by Admin on 27/01/2022. -// - -import XCTest - -class DappTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - // Any test you write for XCTest can be annotated as throws and async. - // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. - // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. - } - - func testPerformanceExample() throws { - // This is an example of a performance test case. - measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/Example/ExampleApp.xcodeproj/project.pbxproj b/Example/ExampleApp.xcodeproj/project.pbxproj index f128b6c84..df8f75814 100644 --- a/Example/ExampleApp.xcodeproj/project.pbxproj +++ b/Example/ExampleApp.xcodeproj/project.pbxproj @@ -43,8 +43,6 @@ 84CE644E279ED2FF00142511 /* SelectChainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE644D279ED2FF00142511 /* SelectChainView.swift */; }; 84CE6452279ED42B00142511 /* ConnectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE6451279ED42B00142511 /* ConnectView.swift */; }; 84CE645527A29D4D00142511 /* ResponseViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE645427A29D4C00142511 /* ResponseViewController.swift */; }; - 84CE646127A2C85B00142511 /* DappTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE646027A2C85B00142511 /* DappTests.swift */; }; - 84CE647027A2CD6B00142511 /* WalletTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84CE646F27A2CD6B00142511 /* WalletTests.swift */; }; 84F568C2279582D200D0A289 /* Signer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F568C1279582D200D0A289 /* Signer.swift */; }; 84F568C42795832A00D0A289 /* EthereumTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84F568C32795832A00D0A289 /* EthereumTransaction.swift */; }; A5A4FC56283CBB7800BBEC1E /* SessionDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A4FC55283CBB7800BBEC1E /* SessionDetailView.swift */; }; @@ -53,6 +51,18 @@ A5A4FC5C283D1F6700BBEC1E /* SessionDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A4FC5B283D1F6700BBEC1E /* SessionDetailViewController.swift */; }; A5A4FC5E283D23CA00BBEC1E /* Array.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A4FC5D283D23CA00BBEC1E /* Array.swift */; }; A5A4FC772840C12C00BBEC1E /* RegressionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5A4FC762840C12C00BBEC1E /* RegressionTests.swift */; }; + A5D85226286333D500DAF5C3 /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = A5D85225286333D500DAF5C3 /* Starscream */; }; + A5D85228286333E300DAF5C3 /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = A5D85227286333E300DAF5C3 /* Starscream */; }; + A5E03DF52864651200888481 /* Starscream in Frameworks */ = {isa = PBXBuildFile; productRef = A5E03DF42864651200888481 /* Starscream */; }; + A5E03DFA286465C700888481 /* SignClientTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E03DF9286465C700888481 /* SignClientTests.swift */; }; + A5E03DFB286465C700888481 /* ClientDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E03DF8286465C700888481 /* ClientDelegate.swift */; }; + A5E03DFD286465D100888481 /* Stubs.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E03DFC286465D100888481 /* Stubs.swift */; }; + A5E03DFF2864662500888481 /* WalletConnect in Frameworks */ = {isa = PBXBuildFile; productRef = A5E03DFE2864662500888481 /* WalletConnect */; }; + A5E03E01286466EA00888481 /* WalletConnectChat in Frameworks */ = {isa = PBXBuildFile; productRef = A5E03E00286466EA00888481 /* WalletConnectChat */; }; + A5E03E03286466F400888481 /* ChatTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E03E02286466F400888481 /* ChatTests.swift */; }; + A5E03E0D28646AD200888481 /* RelayClientEndToEndTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E03E0C28646AD200888481 /* RelayClientEndToEndTests.swift */; }; + A5E03E0F28646D8A00888481 /* WebSocketFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E03E0E28646D8A00888481 /* WebSocketFactory.swift */; }; + A5E03E1128646F8000888481 /* KeychainStorageMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E03E1028646F8000888481 /* KeychainStorageMock.swift */; }; A5E22D1A2840C62A00E36487 /* Engine.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E22D192840C62A00E36487 /* Engine.swift */; }; A5E22D1C2840C85D00E36487 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E22D1B2840C85D00E36487 /* App.swift */; }; A5E22D1E2840C8BF00E36487 /* RoutingEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E22D1D2840C8BF00E36487 /* RoutingEngine.swift */; }; @@ -63,27 +73,6 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - 84CE646227A2C85B00142511 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 764E1D3426F8D3FC00A1FB15 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 764E1D3B26F8D3FC00A1FB15; - remoteInfo = Wallet; - }; - 84CE646727A2C86100142511 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 764E1D3426F8D3FC00A1FB15 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 84CE641B27981DED00142511; - remoteInfo = DApp; - }; - 84CE647127A2CD6B00142511 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 764E1D3426F8D3FC00A1FB15 /* Project object */; - proxyType = 1; - remoteGlobalIDString = 764E1D3B26F8D3FC00A1FB15; - remoteInfo = Wallet; - }; A5A4FC7D2840C5D400BBEC1E /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 764E1D3426F8D3FC00A1FB15 /* Project object */; @@ -141,10 +130,6 @@ 84CE6451279ED42B00142511 /* ConnectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConnectView.swift; sourceTree = ""; }; 84CE6453279FFE1100142511 /* Wallet.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Wallet.entitlements; sourceTree = ""; }; 84CE645427A29D4C00142511 /* ResponseViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResponseViewController.swift; sourceTree = ""; }; - 84CE645E27A2C85B00142511 /* DappTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DappTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 84CE646027A2C85B00142511 /* DappTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DappTests.swift; sourceTree = ""; }; - 84CE646D27A2CD6B00142511 /* WalletTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WalletTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; - 84CE646F27A2CD6B00142511 /* WalletTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WalletTests.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 = ""; }; A5A4FC55283CBB7800BBEC1E /* SessionDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionDetailView.swift; sourceTree = ""; }; @@ -154,6 +139,14 @@ A5A4FC5D283D23CA00BBEC1E /* Array.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Array.swift; sourceTree = ""; }; A5A4FC722840C12C00BBEC1E /* UITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; A5A4FC762840C12C00BBEC1E /* RegressionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegressionTests.swift; sourceTree = ""; }; + A5E03DED286464DB00888481 /* IntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = IntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + A5E03DF8286465C700888481 /* ClientDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientDelegate.swift; sourceTree = ""; }; + A5E03DF9286465C700888481 /* SignClientTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignClientTests.swift; sourceTree = ""; }; + A5E03DFC286465D100888481 /* Stubs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stubs.swift; sourceTree = ""; }; + A5E03E02286466F400888481 /* ChatTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatTests.swift; sourceTree = ""; }; + A5E03E0C28646AD200888481 /* RelayClientEndToEndTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RelayClientEndToEndTests.swift; sourceTree = ""; }; + A5E03E0E28646D8A00888481 /* WebSocketFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebSocketFactory.swift; sourceTree = ""; }; + A5E03E1028646F8000888481 /* KeychainStorageMock.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeychainStorageMock.swift; sourceTree = ""; }; A5E22D192840C62A00E36487 /* Engine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Engine.swift; sourceTree = ""; }; A5E22D1B2840C85D00E36487 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; A5E22D1D2840C8BF00E36487 /* RoutingEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RoutingEngine.swift; sourceTree = ""; }; @@ -170,6 +163,7 @@ files = ( 764E1D5826F8DBAB00A1FB15 /* WalletConnect in Frameworks */, 844943A1278EC49700CC26BB /* Web3 in Frameworks */, + A5D85226286333D500DAF5C3 /* Starscream in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -179,27 +173,24 @@ files = ( 8448F1D427E4726F0000B866 /* WalletConnect in Frameworks */, 84CE64392798228D00142511 /* Web3 in Frameworks */, + A5D85228286333E300DAF5C3 /* Starscream in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; - 84CE645B27A2C85B00142511 /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 84CE646A27A2CD6B00142511 /* Frameworks */ = { + A5A4FC6F2840C12C00BBEC1E /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - A5A4FC6F2840C12C00BBEC1E /* Frameworks */ = { + A5E03DEA286464DB00888481 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + A5E03DFF2864662500888481 /* WalletConnect in Frameworks */, + A5E03DF52864651200888481 /* Starscream in Frameworks */, + A5E03E01286466EA00888481 /* WalletConnectChat in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -249,9 +240,8 @@ 84CE6453279FFE1100142511 /* Wallet.entitlements */, 764E1D3E26F8D3FC00A1FB15 /* ExampleApp */, 84CE641D27981DED00142511 /* DApp */, - 84CE645F27A2C85B00142511 /* DappTests */, - 84CE646E27A2CD6B00142511 /* WalletTests */, A5A4FC732840C12C00BBEC1E /* UITests */, + A5E03DEE286464DB00888481 /* IntegrationTests */, 764E1D3D26F8D3FC00A1FB15 /* Products */, 764E1D5326F8DAC800A1FB15 /* Frameworks */, 764E1D5626F8DB6000A1FB15 /* WalletConnectSwiftV2 */, @@ -263,9 +253,8 @@ children = ( 764E1D3C26F8D3FC00A1FB15 /* WalletConnect Wallet.app */, 84CE641C27981DED00142511 /* DApp.app */, - 84CE645E27A2C85B00142511 /* DappTests.xctest */, - 84CE646D27A2CD6B00142511 /* WalletTests.xctest */, A5A4FC722840C12C00BBEC1E /* UITests.xctest */, + A5E03DED286464DB00888481 /* IntegrationTests.xctest */, ); name = Products; sourceTree = ""; @@ -369,22 +358,6 @@ path = Connect; sourceTree = ""; }; - 84CE645F27A2C85B00142511 /* DappTests */ = { - isa = PBXGroup; - children = ( - 84CE646027A2C85B00142511 /* DappTests.swift */, - ); - path = DappTests; - sourceTree = ""; - }; - 84CE646E27A2CD6B00142511 /* WalletTests */ = { - isa = PBXGroup; - children = ( - 84CE646F27A2CD6B00142511 /* WalletTests.swift */, - ); - path = WalletTests; - sourceTree = ""; - }; A5A4FC732840C12C00BBEC1E /* UITests */ = { isa = PBXGroup; children = ( @@ -408,6 +381,52 @@ path = Engine; sourceTree = ""; }; + A5E03DEE286464DB00888481 /* IntegrationTests */ = { + isa = PBXGroup; + children = ( + A5E03E0B28646AA500888481 /* Relay */, + A5E03E0A28646A8A00888481 /* Stubs */, + A5E03E0928646A8100888481 /* Sign */, + A5E03E0828646A7B00888481 /* Chat */, + ); + path = IntegrationTests; + sourceTree = ""; + }; + A5E03E0828646A7B00888481 /* Chat */ = { + isa = PBXGroup; + children = ( + A5E03E02286466F400888481 /* ChatTests.swift */, + ); + path = Chat; + sourceTree = ""; + }; + A5E03E0928646A8100888481 /* Sign */ = { + isa = PBXGroup; + children = ( + A5E03DF8286465C700888481 /* ClientDelegate.swift */, + A5E03DF9286465C700888481 /* SignClientTests.swift */, + ); + path = Sign; + sourceTree = ""; + }; + A5E03E0A28646A8A00888481 /* Stubs */ = { + isa = PBXGroup; + children = ( + A5E03E1028646F8000888481 /* KeychainStorageMock.swift */, + A5E03E0E28646D8A00888481 /* WebSocketFactory.swift */, + A5E03DFC286465D100888481 /* Stubs.swift */, + ); + path = Stubs; + sourceTree = ""; + }; + A5E03E0B28646AA500888481 /* Relay */ = { + isa = PBXGroup; + children = ( + A5E03E0C28646AD200888481 /* RelayClientEndToEndTests.swift */, + ); + path = Relay; + sourceTree = ""; + }; A5E22D252840D08B00E36487 /* Regression */ = { isa = PBXGroup; children = ( @@ -443,6 +462,7 @@ packageProductDependencies = ( 764E1D5726F8DBAB00A1FB15 /* WalletConnect */, 844943A0278EC49700CC26BB /* Web3 */, + A5D85225286333D500DAF5C3 /* Starscream */, ); productName = ExampleApp; productReference = 764E1D3C26F8D3FC00A1FB15 /* WalletConnect Wallet.app */; @@ -464,48 +484,12 @@ packageProductDependencies = ( 84CE64382798228D00142511 /* Web3 */, 8448F1D327E4726F0000B866 /* WalletConnect */, + A5D85227286333E300DAF5C3 /* Starscream */, ); productName = DApp; productReference = 84CE641C27981DED00142511 /* DApp.app */; productType = "com.apple.product-type.application"; }; - 84CE645D27A2C85B00142511 /* DappTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 84CE646427A2C85B00142511 /* Build configuration list for PBXNativeTarget "DappTests" */; - buildPhases = ( - 84CE645A27A2C85B00142511 /* Sources */, - 84CE645B27A2C85B00142511 /* Frameworks */, - 84CE645C27A2C85B00142511 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 84CE646327A2C85B00142511 /* PBXTargetDependency */, - 84CE646827A2C86100142511 /* PBXTargetDependency */, - ); - name = DappTests; - productName = DappTests; - productReference = 84CE645E27A2C85B00142511 /* DappTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; - 84CE646C27A2CD6B00142511 /* WalletTests */ = { - isa = PBXNativeTarget; - buildConfigurationList = 84CE647327A2CD6C00142511 /* Build configuration list for PBXNativeTarget "WalletTests" */; - buildPhases = ( - 84CE646927A2CD6B00142511 /* Sources */, - 84CE646A27A2CD6B00142511 /* Frameworks */, - 84CE646B27A2CD6B00142511 /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - 84CE647227A2CD6B00142511 /* PBXTargetDependency */, - ); - name = WalletTests; - productName = WalletTests; - productReference = 84CE646D27A2CD6B00142511 /* WalletTests.xctest */; - productType = "com.apple.product-type.bundle.unit-test"; - }; A5A4FC712840C12C00BBEC1E /* UITests */ = { isa = PBXNativeTarget; buildConfigurationList = A5A4FC7A2840C12C00BBEC1E /* Build configuration list for PBXNativeTarget "UITests" */; @@ -525,6 +509,28 @@ productReference = A5A4FC722840C12C00BBEC1E /* UITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; + A5E03DEC286464DB00888481 /* IntegrationTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = A5E03DF1286464DB00888481 /* Build configuration list for PBXNativeTarget "IntegrationTests" */; + buildPhases = ( + A5E03DE9286464DB00888481 /* Sources */, + A5E03DEA286464DB00888481 /* Frameworks */, + A5E03DEB286464DB00888481 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = IntegrationTests; + packageProductDependencies = ( + A5E03DF42864651200888481 /* Starscream */, + A5E03DFE2864662500888481 /* WalletConnect */, + A5E03E00286466EA00888481 /* WalletConnectChat */, + ); + productName = IntegrationTests; + productReference = A5E03DED286464DB00888481 /* IntegrationTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -540,17 +546,12 @@ 84CE641B27981DED00142511 = { CreatedOnToolsVersion = 13.2; }; - 84CE645D27A2C85B00142511 = { - CreatedOnToolsVersion = 13.2; - TestTargetID = 84CE641B27981DED00142511; - }; - 84CE646C27A2CD6B00142511 = { - CreatedOnToolsVersion = 13.2; - TestTargetID = 764E1D3B26F8D3FC00A1FB15; - }; A5A4FC712840C12C00BBEC1E = { CreatedOnToolsVersion = 13.3; }; + A5E03DEC286464DB00888481 = { + CreatedOnToolsVersion = 13.3; + }; }; }; buildConfigurationList = 764E1D3726F8D3FC00A1FB15 /* Build configuration list for PBXProject "ExampleApp" */; @@ -564,6 +565,7 @@ mainGroup = 764E1D3326F8D3FC00A1FB15; packageReferences = ( 8449439F278EC49700CC26BB /* XCRemoteSwiftPackageReference "Web3" */, + A5D85224286333D500DAF5C3 /* XCRemoteSwiftPackageReference "Starscream" */, ); productRefGroup = 764E1D3D26F8D3FC00A1FB15 /* Products */; projectDirPath = ""; @@ -571,9 +573,8 @@ targets = ( 764E1D3B26F8D3FC00A1FB15 /* Wallet */, 84CE641B27981DED00142511 /* DApp */, - 84CE645D27A2C85B00142511 /* DappTests */, - 84CE646C27A2CD6B00142511 /* WalletTests */, A5A4FC712840C12C00BBEC1E /* UITests */, + A5E03DEC286464DB00888481 /* IntegrationTests */, ); }; /* End PBXProject section */ @@ -597,21 +598,14 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 84CE645C27A2C85B00142511 /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 84CE646B27A2CD6B00142511 /* Resources */ = { + A5A4FC702840C12C00BBEC1E /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; - A5A4FC702840C12C00BBEC1E /* Resources */ = { + A5E03DEB286464DB00888481 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( @@ -670,22 +664,6 @@ ); runOnlyForDeploymentPostprocessing = 0; }; - 84CE645A27A2C85B00142511 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 84CE646127A2C85B00142511 /* DappTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; - 84CE646927A2CD6B00142511 /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - 84CE647027A2CD6B00142511 /* WalletTests.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; A5A4FC6E2840C12C00BBEC1E /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -701,24 +679,23 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + A5E03DE9286464DB00888481 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A5E03DFB286465C700888481 /* ClientDelegate.swift in Sources */, + A5E03E03286466F400888481 /* ChatTests.swift in Sources */, + A5E03E0D28646AD200888481 /* RelayClientEndToEndTests.swift in Sources */, + A5E03DFA286465C700888481 /* SignClientTests.swift in Sources */, + A5E03E0F28646D8A00888481 /* WebSocketFactory.swift in Sources */, + A5E03E1128646F8000888481 /* KeychainStorageMock.swift in Sources */, + A5E03DFD286465D100888481 /* Stubs.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - 84CE646327A2C85B00142511 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 764E1D3B26F8D3FC00A1FB15 /* Wallet */; - targetProxy = 84CE646227A2C85B00142511 /* PBXContainerItemProxy */; - }; - 84CE646827A2C86100142511 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 84CE641B27981DED00142511 /* DApp */; - targetProxy = 84CE646727A2C86100142511 /* PBXContainerItemProxy */; - }; - 84CE647227A2CD6B00142511 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = 764E1D3B26F8D3FC00A1FB15 /* Wallet */; - targetProxy = 84CE647127A2CD6B00142511 /* PBXContainerItemProxy */; - }; A5A4FC7E2840C5D400BBEC1E /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 84CE641B27981DED00142511 /* DApp */; @@ -985,50 +962,9 @@ }; name = Release; }; - 84CE646527A2C85B00142511 /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = W5R8AG9K22; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = walletconnect.DappTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DApp.app/DApp"; - }; - name = Debug; - }; - 84CE646627A2C85B00142511 /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; - CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; - DEVELOPMENT_TEAM = W5R8AG9K22; - GENERATE_INFOPLIST_FILE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; - MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = walletconnect.DappTests; - PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_EMIT_LOC_STRINGS = NO; - SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/DApp.app/DApp"; - }; - name = Release; - }; - 84CE647427A2CD6C00142511 /* Debug */ = { + A5A4FC782840C12C00BBEC1E /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -1036,19 +972,18 @@ GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = walletconnect.WalletTests; + PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.UITests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/WalletConnect Wallet.app/WalletConnect Wallet"; }; name = Debug; }; - 84CE647527A2CD6C00142511 /* Release */ = { + A5A4FC792840C12C00BBEC1E /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - BUNDLE_LOADER = "$(TEST_HOST)"; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; @@ -1056,16 +991,16 @@ GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = walletconnect.WalletTests; + PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.UITests; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; - TEST_HOST = "$(BUILT_PRODUCTS_DIR)/WalletConnect Wallet.app/WalletConnect Wallet"; }; name = Release; }; - A5A4FC782840C12C00BBEC1E /* Debug */ = { + A5E03DF2286464DB00888481 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; @@ -1075,16 +1010,15 @@ GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.UITests; + PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.IntegrationTests; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = ""; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; - A5A4FC792840C12C00BBEC1E /* Release */ = { + A5E03DF3286464DB00888481 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; @@ -1094,9 +1028,8 @@ GENERATE_INFOPLIST_FILE = YES; IPHONEOS_DEPLOYMENT_TARGET = 13.0; MARKETING_VERSION = 1.0; - PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.UITests; + PRODUCT_BUNDLE_IDENTIFIER = com.walletconnect.IntegrationTests; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE = ""; SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1133,29 +1066,20 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - 84CE646427A2C85B00142511 /* Build configuration list for PBXNativeTarget "DappTests" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - 84CE646527A2C85B00142511 /* Debug */, - 84CE646627A2C85B00142511 /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; - 84CE647327A2CD6C00142511 /* Build configuration list for PBXNativeTarget "WalletTests" */ = { + A5A4FC7A2840C12C00BBEC1E /* Build configuration list for PBXNativeTarget "UITests" */ = { isa = XCConfigurationList; buildConfigurations = ( - 84CE647427A2CD6C00142511 /* Debug */, - 84CE647527A2CD6C00142511 /* Release */, + A5A4FC782840C12C00BBEC1E /* Debug */, + A5A4FC792840C12C00BBEC1E /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - A5A4FC7A2840C12C00BBEC1E /* Build configuration list for PBXNativeTarget "UITests" */ = { + A5E03DF1286464DB00888481 /* Build configuration list for PBXNativeTarget "IntegrationTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - A5A4FC782840C12C00BBEC1E /* Debug */, - A5A4FC792840C12C00BBEC1E /* Release */, + A5E03DF2286464DB00888481 /* Debug */, + A5E03DF3286464DB00888481 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -1171,6 +1095,14 @@ minimumVersion = 0.5.3; }; }; + A5D85224286333D500DAF5C3 /* XCRemoteSwiftPackageReference "Starscream" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/daltoniam/Starscream"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 3.0.0; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -1192,6 +1124,29 @@ package = 8449439F278EC49700CC26BB /* XCRemoteSwiftPackageReference "Web3" */; productName = Web3; }; + A5D85225286333D500DAF5C3 /* Starscream */ = { + isa = XCSwiftPackageProductDependency; + package = A5D85224286333D500DAF5C3 /* XCRemoteSwiftPackageReference "Starscream" */; + productName = Starscream; + }; + A5D85227286333E300DAF5C3 /* Starscream */ = { + isa = XCSwiftPackageProductDependency; + package = A5D85224286333D500DAF5C3 /* XCRemoteSwiftPackageReference "Starscream" */; + productName = Starscream; + }; + A5E03DF42864651200888481 /* Starscream */ = { + isa = XCSwiftPackageProductDependency; + package = A5D85224286333D500DAF5C3 /* XCRemoteSwiftPackageReference "Starscream" */; + productName = Starscream; + }; + A5E03DFE2864662500888481 /* WalletConnect */ = { + isa = XCSwiftPackageProductDependency; + productName = WalletConnect; + }; + A5E03E00286466EA00888481 /* WalletConnectChat */ = { + isa = XCSwiftPackageProductDependency; + productName = WalletConnectChat; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 764E1D3426F8D3FC00A1FB15 /* Project object */; diff --git a/.swiftpm/xcode/xcshareddata/xcschemes/IntegrationTests.xcscheme b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/IntegrationTests.xcscheme similarity index 87% rename from .swiftpm/xcode/xcshareddata/xcschemes/IntegrationTests.xcscheme rename to Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/IntegrationTests.xcscheme index 6c5757fc3..59b88b4e0 100644 --- a/.swiftpm/xcode/xcshareddata/xcschemes/IntegrationTests.xcscheme +++ b/Example/ExampleApp.xcodeproj/xcshareddata/xcschemes/IntegrationTests.xcscheme @@ -1,6 +1,6 @@ + ReferencedContainer = "container:ExampleApp.xcodeproj"> diff --git a/Example/ExampleApp/SceneDelegate.swift b/Example/ExampleApp/SceneDelegate.swift index 0d6bebcf3..a0dd1b2d6 100644 --- a/Example/ExampleApp/SceneDelegate.swift +++ b/Example/ExampleApp/SceneDelegate.swift @@ -1,5 +1,15 @@ import UIKit import WalletConnectSign +import WalletConnectRelay +import Starscream + +extension WebSocket: WebSocketConnecting { } + +struct SocketFactory: WebSocketFactory { + func create(with url: URL) -> WebSocketConnecting { + return WebSocket(url: url) + } +} class SceneDelegate: UIResponder, UIWindowSceneDelegate { @@ -12,7 +22,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { description: "wallet description", url: "example.wallet", icons: ["https://avatars.githubusercontent.com/u/37784886"]) - Sign.configure(Sign.Config(metadata: metadata, projectId: "8ba9ee138960775e5231b70cc5ef1c3a")) + + Sign.configure(metadata: metadata, projectId: "8ba9ee138960775e5231b70cc5ef1c3a", socketFactory: SocketFactory()) if CommandLine.arguments.contains("-cleanInstall") { try? Sign.instance.cleanup() diff --git a/Example/ExampleApp/SessionDetails/SessionDetailView.swift b/Example/ExampleApp/SessionDetails/SessionDetailView.swift index a0bfe05ab..da14ea271 100644 --- a/Example/ExampleApp/SessionDetails/SessionDetailView.swift +++ b/Example/ExampleApp/SessionDetails/SessionDetailView.swift @@ -136,6 +136,9 @@ private extension SessionDetailView { HStack { Text(chain) Spacer() + Button("Add Account") { Task { + await viewModel.add(chain: chain) + }} Button("Delete") { Task { await viewModel.remove(field: .chain, for: chain) }} diff --git a/Example/ExampleApp/SessionDetails/SessionDetailViewModel.swift b/Example/ExampleApp/SessionDetails/SessionDetailViewModel.swift index 36ee89a25..00e2c18d2 100644 --- a/Example/ExampleApp/SessionDetails/SessionDetailViewModel.swift +++ b/Example/ExampleApp/SessionDetails/SessionDetailViewModel.swift @@ -64,6 +64,22 @@ final class SessionDetailViewModel: ObservableObject { } } + func add(chain: String) async { + let backup = namespaces + + do { + addTestAccount(for: chain) + + try await client.update( + topic: session.topic, + namespaces: namespaces + ) + } catch { + namespaces = backup + print("[RESPONDER] Namespaces update failed with: \(error.localizedDescription)") + } + } + func ping() { client.ping(topic: session.topic) { result in switch result { @@ -82,6 +98,17 @@ final class SessionDetailViewModel: ObservableObject { private extension SessionDetailViewModel { + func addTestAccount(for chain: String) { + guard let viewModel = namespace(for: chain) else { return } + + let account = Account("eip155:56:0xe5EeF1368781911d265fDB6946613dA61915a501")! + + namespaces[chain] = SessionNamespace( + accounts: Set(viewModel.accounts.appending(account)), + namespace: viewModel.namespace + ) + } + func removeAccounts(at offsets: IndexSet, chain: String) { guard let viewModel = namespace(for: chain) else { return } @@ -121,6 +148,12 @@ private extension Array { array.remove(atOffsets: offsets) return array } + + func appending(_ element: Element) -> Self { + var array = self + array.append(element) + return array + } } private extension SessionNamespace { diff --git a/Tests/ChatTests/EndToEndTests.swift b/Example/IntegrationTests/Chat/ChatTests.swift similarity index 95% rename from Tests/ChatTests/EndToEndTests.swift rename to Example/IntegrationTests/Chat/ChatTests.swift index 21cca12dc..2c02d707f 100644 --- a/Tests/ChatTests/EndToEndTests.swift +++ b/Example/IntegrationTests/Chat/ChatTests.swift @@ -3,7 +3,6 @@ import XCTest @testable import Chat import WalletConnectUtils @testable import WalletConnectKMS -@testable import TestingUtils import WalletConnectRelay import Combine @@ -40,11 +39,10 @@ final class ChatTests: XCTestCase { func makeClient(prefix: String) -> Chat { let logger = ConsoleLogger(suffix: prefix, loggingLevel: .debug) - let relayHost = "dev.relay.walletconnect.com" + let relayHost = "relay.walletconnect.com" let projectId = "8ba9ee138960775e5231b70cc5ef1c3a" - let relayClient = RelayClient(relayHost: relayHost, projectId: projectId, logger: logger) - let keychain = KeychainStorage(keychainService: KeychainServiceFake(), serviceIdentifier: "") - + let keychain = KeychainStorageMock() + let relayClient = RelayClient(relayHost: relayHost, projectId: projectId, keychainStorage: keychain, socketFactory: SocketFactory(), logger: logger) return Chat(registry: registry, relayClient: relayClient, kms: KeyManagementService(keychain: keychain), logger: logger, keyValueStorage: RuntimeKeyValueStorage()) } diff --git a/Tests/RelayerTests/RelayClientEndToEndTests.swift b/Example/IntegrationTests/Relay/RelayClientEndToEndTests.swift similarity index 75% rename from Tests/RelayerTests/RelayClientEndToEndTests.swift rename to Example/IntegrationTests/Relay/RelayClientEndToEndTests.swift index 0a15b2fa1..adf89c272 100644 --- a/Tests/RelayerTests/RelayClientEndToEndTests.swift +++ b/Example/IntegrationTests/Relay/RelayClientEndToEndTests.swift @@ -2,26 +2,34 @@ import Foundation import Combine import XCTest import WalletConnectUtils -import TestingUtils -@testable import WalletConnectRelay import Starscream +@testable import WalletConnectRelay final class RelayClientEndToEndTests: XCTestCase { - let relayHost = "dev.relay.walletconnect.com" + let defaultTimeout: TimeInterval = 10 + + let relayHost = "relay.walletconnect.com" let projectId = "8ba9ee138960775e5231b70cc5ef1c3a" - private var publishers = [AnyCancellable]() + private var publishers = Set() func makeRelayClient() -> RelayClient { + let clientIdStorage = ClientIdStorage(keychain: KeychainStorageMock()) + let socketAuthenticator = SocketAuthenticator( + clientIdStorage: clientIdStorage, + didKeyFactory: ED25519DIDKeyFactory(), + relayHost: relayHost + ) + let urlFactory = RelayUrlFactory(socketAuthenticator: socketAuthenticator) + let socket = WebSocket(url: urlFactory.create(host: relayHost, projectId: projectId)) let logger = ConsoleLogger() - let url = RelayClient.makeRelayUrl(host: relayHost, projectId: projectId) - let socket = WebSocket(url: url) let dispatcher = Dispatcher(socket: socket, socketConnectionHandler: ManualSocketConnectionHandler(socket: socket), logger: logger) return RelayClient(dispatcher: dispatcher, logger: logger, keyValueStorage: RuntimeKeyValueStorage()) } func testSubscribe() { let relayClient = makeRelayClient() + try! relayClient.connect() let subscribeExpectation = expectation(description: "subscribe call succeeds") subscribeExpectation.assertForOverFulfill = true @@ -33,12 +41,14 @@ final class RelayClientEndToEndTests: XCTestCase { } } }.store(in: &publishers) - waitForExpectations(timeout: defaultTimeout, handler: nil) + + wait(for: [subscribeExpectation], timeout: defaultTimeout) } func testEndToEndPayload() { let relayA = makeRelayClient() let relayB = makeRelayClient() + try! relayA.connect() try! relayB.connect() @@ -65,7 +75,7 @@ final class RelayClientEndToEndTests: XCTestCase { expectationB.fulfill() } relayA.socketConnectionStatusPublisher.sink { _ in - relayA.publish(topic: randomTopic, payload: payloadA, onNetworkAcknowledge: { error in + relayA.publish(topic: randomTopic, payload: payloadA, tag: .unknown, onNetworkAcknowledge: { error in XCTAssertNil(error) }) relayA.subscribe(topic: randomTopic) { error in @@ -73,7 +83,7 @@ final class RelayClientEndToEndTests: XCTestCase { } }.store(in: &publishers) relayB.socketConnectionStatusPublisher.sink { _ in - relayB.publish(topic: randomTopic, payload: payloadB, onNetworkAcknowledge: { error in + relayB.publish(topic: randomTopic, payload: payloadB, tag: .unknown, onNetworkAcknowledge: { error in XCTAssertNil(error) }) relayB.subscribe(topic: randomTopic) { error in @@ -81,7 +91,8 @@ final class RelayClientEndToEndTests: XCTestCase { } }.store(in: &publishers) - waitForExpectations(timeout: defaultTimeout, handler: nil) + wait(for: [expectationA, expectationB], timeout: defaultTimeout) + XCTAssertEqual(subscriptionATopic, randomTopic) XCTAssertEqual(subscriptionBTopic, randomTopic) @@ -90,3 +101,9 @@ final class RelayClientEndToEndTests: XCTestCase { // XCTAssertEqual(subscriptionAPayload, payloadB) } } + +extension String { + static func randomTopic() -> String { + "\(UUID().uuidString)\(UUID().uuidString)".replacingOccurrences(of: "-", with: "").lowercased() + } +} diff --git a/Tests/IntegrationTests/ClientDelegate.swift b/Example/IntegrationTests/Sign/ClientDelegate.swift similarity index 100% rename from Tests/IntegrationTests/ClientDelegate.swift rename to Example/IntegrationTests/Sign/ClientDelegate.swift diff --git a/Tests/IntegrationTests/SignClientTests.swift b/Example/IntegrationTests/Sign/SignClientTests.swift similarity index 96% rename from Tests/IntegrationTests/SignClientTests.swift rename to Example/IntegrationTests/Sign/SignClientTests.swift index 8e1a8818d..d850af7ad 100644 --- a/Tests/IntegrationTests/SignClientTests.swift +++ b/Example/IntegrationTests/Sign/SignClientTests.swift @@ -1,30 +1,39 @@ import XCTest import WalletConnectUtils -import TestingUtils @testable import WalletConnectKMS @testable import WalletConnectSign +@testable import WalletConnectRelay final class SignClientTests: XCTestCase { - let defaultTimeout: TimeInterval = 5.0 + let defaultTimeout: TimeInterval = 5 var proposer: ClientDelegate! var responder: ClientDelegate! static private func makeClientDelegate( name: String, - relayHost: String = "dev.relay.walletconnect.com", + relayHost: String = "relay.walletconnect.com", projectId: String = "8ba9ee138960775e5231b70cc5ef1c3a" ) -> ClientDelegate { let logger = ConsoleLogger(suffix: name, loggingLevel: .debug) - let keychain = KeychainStorage(keychainService: KeychainServiceFake(), serviceIdentifier: "") + let keychain = KeychainStorageMock() + let relayClient = RelayClient( + relayHost: relayHost, + projectId: projectId, + keyValueStorage: RuntimeKeyValueStorage(), + keychainStorage: keychain, + socketFactory: SocketFactory(), + socketConnectionType: .automatic, + logger: logger + ) let client = SignClient( metadata: AppMetadata(name: name, description: "", url: "", icons: [""]), - projectId: projectId, - relayHost: relayHost, logger: logger, - kms: KeyManagementService(keychain: keychain), - keyValueStorage: RuntimeKeyValueStorage()) + keyValueStorage: RuntimeKeyValueStorage(), + keychainStorage: keychain, + relayClient: relayClient + ) return ClientDelegate(client: client) } @@ -62,7 +71,7 @@ final class SignClientTests: XCTestCase { let sessionNamespaces = SessionNamespace.make(toRespond: requiredNamespaces) wallet.onSessionProposal = { proposal in - Task { + Task(priority: .high) { do { try await wallet.client.approve(proposalId: proposal.id, namespaces: sessionNamespaces) } catch { XCTFail("\(error)") } } } @@ -90,7 +99,7 @@ final class SignClientTests: XCTestCase { try await wallet.client.pair(uri: uri!) wallet.onSessionProposal = { proposal in - Task { + Task(priority: .high) { do { try await wallet.client.reject(proposalId: proposal.id, reason: .disapprovedChains) // TODO: Review reason store.rejectedProposal = proposal diff --git a/Example/IntegrationTests/Stubs/KeychainStorageMock.swift b/Example/IntegrationTests/Stubs/KeychainStorageMock.swift new file mode 100644 index 000000000..cc0e1aa8e --- /dev/null +++ b/Example/IntegrationTests/Stubs/KeychainStorageMock.swift @@ -0,0 +1,37 @@ +import Foundation +@testable import WalletConnectKMS + +public final class KeychainStorageMock: KeychainStorageProtocol { + + public var storage: [String: Data] + + private(set) var didCallAdd = false + private(set) var didCallRead = false + private(set) var didCallDelete = false + + public init(storage: [String: Data] = [:]) { + self.storage = storage + } + + public func add(_ item: T, forKey key: String) throws where T: GenericPasswordConvertible { + didCallAdd = true + storage[key] = item.rawRepresentation + } + + public func read(key: String) throws -> T where T: GenericPasswordConvertible { + didCallRead = true + if let data = storage[key] { + return try T(rawRepresentation: data) + } + throw KeychainError(errSecItemNotFound) + } + + public func delete(key: String) throws { + didCallDelete = true + storage[key] = nil + } + + public func deleteAll() throws { + storage = [:] + } +} diff --git a/Tests/IntegrationTests/Helpers/Stubs.swift b/Example/IntegrationTests/Stubs/Stubs.swift similarity index 100% rename from Tests/IntegrationTests/Helpers/Stubs.swift rename to Example/IntegrationTests/Stubs/Stubs.swift diff --git a/Example/IntegrationTests/Stubs/WebSocketFactory.swift b/Example/IntegrationTests/Stubs/WebSocketFactory.swift new file mode 100644 index 000000000..22f45a8cb --- /dev/null +++ b/Example/IntegrationTests/Stubs/WebSocketFactory.swift @@ -0,0 +1,14 @@ +import Foundation +import Starscream +import WalletConnectRelay + +extension WebSocket: WebSocketConnecting { } + +public struct SocketFactory: WebSocketFactory { + + public init() { } + + public func create(with url: URL) -> WebSocketConnecting { + return WebSocket(url: url) + } +} diff --git a/Example/WalletTests/WalletTests.swift b/Example/WalletTests/WalletTests.swift deleted file mode 100644 index a248726c3..000000000 --- a/Example/WalletTests/WalletTests.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// WalletTests.swift -// WalletTests -// -// Created by Admin on 27/01/2022. -// - -import XCTest - -class WalletTests: XCTestCase { - - override func setUpWithError() throws { - // Put setup code here. This method is called before the invocation of each test method in the class. - } - - override func tearDownWithError() throws { - // Put teardown code here. This method is called after the invocation of each test method in the class. - } - - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct results. - // Any test you write for XCTest can be annotated as throws and async. - // Mark your test throws to produce an unexpected failure when your test encounters an uncaught error. - // Mark your test async to allow awaiting for asynchronous code to complete. Check the results with assertions afterwards. - } - - func testPerformanceExample() throws { - // This is an example of a performance test case. - measure { - // Put the code you want to measure the time of here. - } - } - -} diff --git a/Package.swift b/Package.swift index 81ab4860d..537aeed2a 100644 --- a/Package.swift +++ b/Package.swift @@ -12,11 +12,12 @@ let package = Package( products: [ .library( name: "WalletConnect", - targets: ["WalletConnectSign"]) - ], - dependencies: [ - .package(url: "https://github.com/daltoniam/Starscream.git", .upToNextMajor(from: "3.0.0")) + targets: ["WalletConnectSign"]), + .library( + name: "WalletConnectChat", + targets: ["Chat"]) ], + dependencies: [], targets: [ .target( name: "WalletConnectSign", @@ -28,7 +29,7 @@ let package = Package( path: "Sources/Chat"), .target( name: "WalletConnectRelay", - dependencies: ["WalletConnectUtils", "Starscream", "WalletConnectKMS"], + dependencies: ["WalletConnectUtils", "WalletConnectKMS"], path: "Sources/WalletConnectRelay"), .target( name: "WalletConnectKMS", @@ -45,10 +46,7 @@ let package = Package( dependencies: []), .testTarget( name: "WalletConnectSignTests", - dependencies: ["WalletConnectSign", "TestingUtils", "WalletConnectKMS"]), - .testTarget( - name: "IntegrationTests", - dependencies: ["WalletConnectSign", "TestingUtils", "WalletConnectKMS"]), + dependencies: ["WalletConnectSign", "TestingUtils"]), .testTarget( name: "ChatTests", dependencies: ["Chat", "WalletConnectUtils", "TestingUtils"]), diff --git a/Sources/Chat/NetworkingInteractor.swift b/Sources/Chat/NetworkingInteractor.swift index 8414e19de..6234cca8d 100644 --- a/Sources/Chat/NetworkingInteractor.swift +++ b/Sources/Chat/NetworkingInteractor.swift @@ -53,12 +53,12 @@ class NetworkingInteractor: NetworkInteracting { func request(_ request: JSONRPCRequest, topic: String, envelopeType: Envelope.EnvelopeType) async throws { try jsonRpcHistory.set(topic: topic, request: request) let message = try! serializer.serialize(topic: topic, encodable: request, envelopeType: envelopeType) - try await relayClient.publish(topic: topic, payload: message) + try await relayClient.publish(topic: topic, payload: message, tag: .chat) } func respond(topic: String, response: JsonRpcResult) async throws { let message = try serializer.serialize(topic: topic, encodable: response.value) - try await relayClient.publish(topic: topic, payload: message, prompt: false) + try await relayClient.publish(topic: topic, payload: message, tag: .chat, prompt: false) } func subscribe(topic: String) async throws { diff --git a/Sources/WalletConnectKMS/Crypto/CryptoKitWrapper/SigningKeyCryptoKit.swift b/Sources/WalletConnectKMS/Crypto/CryptoKitWrapper/SigningKeyCryptoKit.swift index 71caddd15..f02cc72cb 100644 --- a/Sources/WalletConnectKMS/Crypto/CryptoKitWrapper/SigningKeyCryptoKit.swift +++ b/Sources/WalletConnectKMS/Crypto/CryptoKitWrapper/SigningKeyCryptoKit.swift @@ -65,7 +65,6 @@ public struct SigningPrivateKey: GenericPasswordConvertible, Equatable { } public init(rawRepresentation: D) throws where D: ContiguousBytes { - self.key = try Curve25519.Signing.PrivateKey(rawRepresentation: rawRepresentation) } diff --git a/Sources/WalletConnectKMS/Crypto/KeyManagementService.swift b/Sources/WalletConnectKMS/Crypto/KeyManagementService.swift index 4b8fec606..8345e2174 100644 --- a/Sources/WalletConnectKMS/Crypto/KeyManagementService.swift +++ b/Sources/WalletConnectKMS/Crypto/KeyManagementService.swift @@ -26,11 +26,7 @@ public class KeyManagementService: KeyManagementServiceProtocol { private var keychain: KeychainStorageProtocol - public init(serviceIdentifier: String) { - self.keychain = KeychainStorage(serviceIdentifier: serviceIdentifier) - } - - init(keychain: KeychainStorageProtocol) { + public init(keychain: KeychainStorageProtocol) { self.keychain = keychain } diff --git a/Sources/WalletConnectKMS/Serialiser/Serializer.swift b/Sources/WalletConnectKMS/Serialiser/Serializer.swift index 1c4dcc813..a1c0aea87 100644 --- a/Sources/WalletConnectKMS/Serialiser/Serializer.swift +++ b/Sources/WalletConnectKMS/Serialiser/Serializer.swift @@ -14,7 +14,7 @@ public class Serializer: Serializing { self.codec = codec } - public init(kms: KeyManagementService) { + public init(kms: KeyManagementServiceProtocol) { self.kms = kms self.codec = ChaChaPolyCodec() } diff --git a/Sources/WalletConnectRelay/ClientAuth/AuthChallengeProvider.swift b/Sources/WalletConnectRelay/ClientAuth/AuthChallengeProvider.swift deleted file mode 100644 index aeca90262..000000000 --- a/Sources/WalletConnectRelay/ClientAuth/AuthChallengeProvider.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation - -protocol AuthChallengeProviding { - func getChallenge(for clientId: String) async throws -> String -} - -actor AuthChallengeProvider: AuthChallengeProviding { - func getChallenge(for clientId: String) async throws -> String { - fatalError("not implemented") - } -} diff --git a/Sources/WalletConnectRelay/ClientAuth/Base58.swift b/Sources/WalletConnectRelay/ClientAuth/Base58.swift new file mode 100644 index 000000000..cdb4a908d --- /dev/null +++ b/Sources/WalletConnectRelay/ClientAuth/Base58.swift @@ -0,0 +1,109 @@ +import Foundation + +public struct Base58 { + static let baseAlphabets = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + static var zeroAlphabet: Character = "1" + static var base: Int = 58 + + static func sizeFromByte(size: Int) -> Int { + return size * 138 / 100 + 1 + } + static func sizeFromBase(size: Int) -> Int { + return size * 733 / 1000 + 1 + } + + static func convertBytesToBase(_ bytes: Data) -> [UInt8] { + var length = 0 + let size = sizeFromByte(size: bytes.count) + var encodedBytes: [UInt8] = Array(repeating: 0, count: size) + + for b in bytes { + var carry = Int(b) + var i = 0 + for j in (0...encodedBytes.count - 1).reversed() where carry != 0 || i < length { + carry += 256 * Int(encodedBytes[j]) + encodedBytes[j] = UInt8(carry % base) + carry /= base + i += 1 + } + + assert(carry == 0) + + length = i + } + + var zerosToRemove = 0 + for b in encodedBytes { + if b != 0 { break } + zerosToRemove += 1 + } + + encodedBytes.removeFirst(zerosToRemove) + return encodedBytes + } + + public static func encode(_ bytes: Data) -> String { + var bytes = bytes + var zerosCount = 0 + + for b in bytes { + if b != 0 { break } + zerosCount += 1 + } + + bytes.removeFirst(zerosCount) + + let encodedBytes = convertBytesToBase(bytes) + + var str = "" + while 0 < zerosCount { + str += String(zeroAlphabet) + zerosCount -= 1 + } + + for b in encodedBytes { + str += String(baseAlphabets[String.Index(utf16Offset: Int(b), in: baseAlphabets)]) + } + + return str + } + + public static func decode(_ string: String) -> Data { + guard !string.isEmpty else { return Data() } + + var zerosCount = 0 + var length = 0 + for c in string { + if c != zeroAlphabet { break } + zerosCount += 1 + } + let size = sizeFromBase(size: string.lengthOfBytes(using: .utf8) - zerosCount) + var decodedBytes: [UInt8] = Array(repeating: 0, count: size) + for c in string { + guard let baseIndex = baseAlphabets.firstIndex(of: c) else { return Data() } + + var carry = baseIndex.utf16Offset(in: baseAlphabets) + var i = 0 + for j in (0...decodedBytes.count - 1).reversed() where carry != 0 || i < length { + carry += base * Int(decodedBytes[j]) + decodedBytes[j] = UInt8(carry % 256) + carry /= 256 + i += 1 + } + + assert(carry == 0) + length = i + } + + // skip leading zeros + var zerosToRemove = 0 + + for b in decodedBytes { + if b != 0 { break } + zerosToRemove += 1 + } + decodedBytes.removeFirst(zerosToRemove) + + return Data(repeating: 0, count: zerosCount) + Data(decodedBytes) + } +} diff --git a/Sources/WalletConnectRelay/ClientAuth/ClientIdStorage.swift b/Sources/WalletConnectRelay/ClientAuth/ClientIdStorage.swift index ad1876cef..826ae67e6 100644 --- a/Sources/WalletConnectRelay/ClientAuth/ClientIdStorage.swift +++ b/Sources/WalletConnectRelay/ClientAuth/ClientIdStorage.swift @@ -2,10 +2,10 @@ import Foundation import WalletConnectKMS protocol ClientIdStoring { - func getOrCreateKeyPair() async throws -> SigningPrivateKey + func getOrCreateKeyPair() throws -> SigningPrivateKey } -actor ClientIdStorage: ClientIdStoring { +struct ClientIdStorage: ClientIdStoring { private let key = "com.walletconnect.iridium.client_id" private let keychain: KeychainStorageProtocol @@ -13,7 +13,7 @@ actor ClientIdStorage: ClientIdStoring { self.keychain = keychain } - func getOrCreateKeyPair() async throws -> SigningPrivateKey { + func getOrCreateKeyPair() throws -> SigningPrivateKey { do { return try keychain.read(key: key) } catch { diff --git a/Sources/WalletConnectRelay/ClientAuth/DIDKeyFactory.swift b/Sources/WalletConnectRelay/ClientAuth/DIDKeyFactory.swift new file mode 100644 index 000000000..2a062c090 --- /dev/null +++ b/Sources/WalletConnectRelay/ClientAuth/DIDKeyFactory.swift @@ -0,0 +1,32 @@ +import Foundation + +protocol DIDKeyFactory { + func make(pubKey: Data, prefix: Bool) -> String +} + +/// A DID Method for Static Cryptographic Keys +/// did-key-format := did:key:MULTIBASE(base58-btc, MULTICODEC(public-key-type, raw-public-key-bytes)) +struct ED25519DIDKeyFactory: DIDKeyFactory { + private let DID_DELIMITER = ":" + private let DID_PREFIX = "did" + private let DID_METHOD = "key" + private let MULTICODEC_ED25519_HEADER: [UInt8] = [0xed, 0x01] + private let MULTICODEC_ED25519_BASE = "z" + + func make(pubKey: Data, prefix: Bool) -> String { + let multibase = multibase(pubKey: pubKey) + + guard prefix else { return multibase } + + return [ + DID_PREFIX, + DID_METHOD, + multibase + ].joined(separator: DID_DELIMITER) + } + + private func multibase(pubKey: Data) -> String { + let multicodec = Data(MULTICODEC_ED25519_HEADER) + pubKey + return MULTICODEC_ED25519_BASE + Base58.encode(multicodec) + } +} diff --git a/Sources/WalletConnectRelay/ClientAuth/ED25519DIDKeyFactory.swift b/Sources/WalletConnectRelay/ClientAuth/ED25519DIDKeyFactory.swift deleted file mode 100644 index bffaf9250..000000000 --- a/Sources/WalletConnectRelay/ClientAuth/ED25519DIDKeyFactory.swift +++ /dev/null @@ -1,33 +0,0 @@ -import Foundation - -protocol ED25519DIDKeyFactory { - func make(pubKey: Data) -> String -} - -/// did-key-format := did:key:MULTIBASE(base58-btc, MULTICODEC(public-key-type, raw-public-key-bytes)) -struct ED25519DIDKeyFactoryImpl: ED25519DIDKeyFactory { - private static let DID_DELIMITER = ":" - private static let DID_PREFIX = "did" - private static let DID_METHOD = "key" - private static let MULTICODEC_ED25519_HEADER = "K36" - private static let MULTICODEC_ED25519_ENCODING = "base58btc" - private static let MULTICODEC_ED25519_BASE = "z" - - func make(pubKey: Data) -> String { - fatalError("not implemented") - - let multicodec = multicodec() - - let multibase = multibase(multicodec: multicodec) - - return [Self.DID_PREFIX, Self.DID_METHOD, multibase].joined(separator: Self.DID_DELIMITER) - } - - private func multibase(multicodec: String) -> String { - fatalError("not implemented") - } - - private func multicodec() -> String { - fatalError("not implemented") - } -} diff --git a/Sources/WalletConnectRelay/ClientAuth/JWT/JWT+Claims.swift b/Sources/WalletConnectRelay/ClientAuth/JWT/JWT+Claims.swift index f97f3e7ca..0d7b9a6ac 100644 --- a/Sources/WalletConnectRelay/ClientAuth/JWT/JWT+Claims.swift +++ b/Sources/WalletConnectRelay/ClientAuth/JWT/JWT+Claims.swift @@ -4,6 +4,10 @@ extension JWT { struct Claims: Codable, Equatable { let iss: String let sub: String + let aud: String + let iat: Date + let exp: Date + func encode() throws -> String { let jsonEncoder = JSONEncoder() jsonEncoder.dateEncodingStrategy = .secondsSince1970 diff --git a/Sources/WalletConnectRelay/ClientAuth/SocketAuthenticator.swift b/Sources/WalletConnectRelay/ClientAuth/SocketAuthenticator.swift index deb731b69..f1f5b4c31 100644 --- a/Sources/WalletConnectRelay/ClientAuth/SocketAuthenticator.swift +++ b/Sources/WalletConnectRelay/ClientAuth/SocketAuthenticator.swift @@ -2,33 +2,46 @@ import Foundation import WalletConnectKMS protocol SocketAuthenticating { - func createAuthToken() async throws -> String + func createAuthToken() throws -> String } -actor SocketAuthenticator: SocketAuthenticating { - private let authChallengeProvider: AuthChallengeProviding +struct SocketAuthenticator: SocketAuthenticating { private let clientIdStorage: ClientIdStoring - private let didKeyFactory: ED25519DIDKeyFactory + private let didKeyFactory: DIDKeyFactory + private let relayHost: String - init(authChallengeProvider: AuthChallengeProviding = AuthChallengeProvider(), - clientIdStorage: ClientIdStoring, - didKeyFactory: ED25519DIDKeyFactory = ED25519DIDKeyFactoryImpl()) { - self.authChallengeProvider = authChallengeProvider + init(clientIdStorage: ClientIdStoring, didKeyFactory: DIDKeyFactory, relayHost: String) { self.clientIdStorage = clientIdStorage self.didKeyFactory = didKeyFactory + self.relayHost = relayHost } - func createAuthToken() async throws -> String { - let clientIdKeyPair = try await clientIdStorage.getOrCreateKeyPair() - let challenge = try await authChallengeProvider.getChallenge(for: clientIdKeyPair.publicKey.hexRepresentation) - return try signJWT(subject: challenge, keyPair: clientIdKeyPair) + func createAuthToken() throws -> String { + let clientIdKeyPair = try clientIdStorage.getOrCreateKeyPair() + let subject = generateSubject() + return try createAndSignJWT(subject: subject, keyPair: clientIdKeyPair) } - private func signJWT(subject: String, keyPair: SigningPrivateKey) throws -> String { - let issuer = didKeyFactory.make(pubKey: keyPair.publicKey.rawRepresentation) - let claims = JWT.Claims(iss: issuer, sub: subject) + private func createAndSignJWT(subject: String, keyPair: SigningPrivateKey) throws -> String { + let issuer = didKeyFactory.make(pubKey: keyPair.publicKey.rawRepresentation, prefix: true) + let claims = JWT.Claims(iss: issuer, sub: subject, aud: getAudience(), iat: Date(), exp: getExpiry()) var jwt = JWT(claims: claims) try jwt.sign(using: EdDSASigner(keyPair)) return try jwt.encoded() } + + private func generateSubject() -> String { + return Data.randomBytes(count: 32).toHexString() + } + + private func getExpiry() -> Date { + var components = DateComponents() + components.setValue(1, for: .day) + // safe to unwrap as the date must be calculated + return Calendar.current.date(byAdding: components, to: Date())! + } + + private func getAudience() -> String { + return "wss://\(relayHost)" + } } diff --git a/Sources/WalletConnectRelay/Dispatching.swift b/Sources/WalletConnectRelay/Dispatching.swift index 51b72cdef..e053051ee 100644 --- a/Sources/WalletConnectRelay/Dispatching.swift +++ b/Sources/WalletConnectRelay/Dispatching.swift @@ -1,6 +1,5 @@ import Foundation import WalletConnectUtils -import Starscream protocol Dispatching { var onConnect: (() -> Void)? {get set} @@ -18,10 +17,10 @@ final class Dispatcher: NSObject, Dispatching { var onMessage: ((String) -> Void)? private var textFramesQueue = Queue() private let logger: ConsoleLogging - var socket: WebSocketProtocol + var socket: WebSocketConnecting var socketConnectionHandler: SocketConnectionHandler - init(socket: WebSocketProtocol, + init(socket: WebSocketConnecting, socketConnectionHandler: SocketConnectionHandler, logger: ConsoleLogging) { self.socket = socket diff --git a/Sources/WalletConnectRelay/HTTP/HTTPClient.swift b/Sources/WalletConnectRelay/HTTP/HTTPClient.swift new file mode 100644 index 000000000..21bd3c9c7 --- /dev/null +++ b/Sources/WalletConnectRelay/HTTP/HTTPClient.swift @@ -0,0 +1,52 @@ +import Foundation + +struct Endpoint { + let path: String + let queryParameters: [URLQueryItem] +} + +actor HTTPClient { + + let host: String + + private let session: URLSession + + init(host: String, session: URLSession = .shared) { + self.host = host + self.session = session + } + + func request(_ type: T.Type, at endpoint: Endpoint) async throws -> T { + return try await withCheckedThrowingContinuation { continuation in + request(T.self, at: endpoint) { response in + do { + let value = try response.result.get() + continuation.resume(returning: value) + } catch { + continuation.resume(throwing: error) + } + } + } + } + + func request(_ type: T.Type, at endpoint: Endpoint, completion: @escaping (HTTPResponse) -> Void) { + let request = makeRequest(for: endpoint) + session.dataTask(with: request) { data, response, error in + completion(HTTPResponse(request: request, data: data, response: response, error: error)) + }.resume() + } + + private func makeRequest(for endpoint: Endpoint) -> URLRequest { + var components = URLComponents() + components.scheme = "https" + components.host = host + components.path = endpoint.path + components.queryItems = endpoint.queryParameters + guard let url = components.url else { + fatalError() // TODO: Remove fatal error when url fails to build + } + var request = URLRequest(url: url) + request.httpMethod = "GET" + return request + } +} diff --git a/Sources/WalletConnectRelay/HTTP/HTTPError.swift b/Sources/WalletConnectRelay/HTTP/HTTPError.swift new file mode 100644 index 000000000..1673046c3 --- /dev/null +++ b/Sources/WalletConnectRelay/HTTP/HTTPError.swift @@ -0,0 +1,9 @@ +import Foundation + +enum HTTPError: Error { + case dataTaskError(Error) + case noResponse + case badStatusCode(Int) + case responseDataNil + case jsonDecodeFailed(Error, Data) +} diff --git a/Sources/WalletConnectRelay/HTTP/HTTPResponse.swift b/Sources/WalletConnectRelay/HTTP/HTTPResponse.swift new file mode 100644 index 000000000..c58393892 --- /dev/null +++ b/Sources/WalletConnectRelay/HTTP/HTTPResponse.swift @@ -0,0 +1,49 @@ +import Foundation + +struct HTTPResponse { + + let request: URLRequest? + let data: Data? + let urlResponse: HTTPURLResponse? + let result: Result + + init(request: URLRequest? = nil, data: Data? = nil, response: URLResponse? = nil, error: Error? = nil) { + self.data = data + self.request = request + self.urlResponse = response as? HTTPURLResponse + self.result = Self.validate(data, response, error).flatMap { data -> Result in + if let rawData = data as? T { + return .success(rawData) + } + return Self.decode(data) + } + } +} + +extension HTTPResponse { + + private static func validate(_ data: Data?, _ urlResponse: URLResponse?, _ error: Error?) -> Result { + if let error = error { + return .failure(HTTPError.dataTaskError(error)) + } + guard let httpResponse = urlResponse as? HTTPURLResponse else { + return .failure(HTTPError.noResponse) + } + guard (200..<300) ~= httpResponse.statusCode else { + return .failure(HTTPError.badStatusCode(httpResponse.statusCode)) + } + guard let validData = data else { + return .failure(HTTPError.responseDataNil) + } + return .success(validData) + } + + private static func decode(_ data: Data) -> Result { + do { + let decoded = try JSONDecoder().decode(T.self, from: data) + return .success(decoded) + } catch let jsonError { + return .failure(HTTPError.jsonDecodeFailed(jsonError, data)) + } + } +} diff --git a/Sources/WalletConnectRelay/PublishTag.swift b/Sources/WalletConnectRelay/PublishTag.swift new file mode 100644 index 000000000..ec21addc0 --- /dev/null +++ b/Sources/WalletConnectRelay/PublishTag.swift @@ -0,0 +1,5 @@ +public enum PublishTag: Int { + case unknown = 0 + case sign + case chat +} diff --git a/Sources/WalletConnectRelay/RelayClient.swift b/Sources/WalletConnectRelay/RelayClient.swift index 6c39ee867..cb5215abc 100644 --- a/Sources/WalletConnectRelay/RelayClient.swift +++ b/Sources/WalletConnectRelay/RelayClient.swift @@ -1,7 +1,7 @@ import Foundation import Combine import WalletConnectUtils -import Starscream +import WalletConnectKMS public enum SocketConnectionStatus { case connected @@ -38,9 +38,11 @@ public final class RelayClient { let logger: ConsoleLogging static let historyIdentifier = "com.walletconnect.sdk.relayer_client.subscription_json_rpc_record" - init(dispatcher: Dispatching, - logger: ConsoleLogging, - keyValueStorage: KeyValueStorage) { + init( + dispatcher: Dispatching, + logger: ConsoleLogging, + keyValueStorage: KeyValueStorage + ) { self.logger = logger self.dispatcher = dispatcher @@ -50,21 +52,31 @@ public final class RelayClient { /// Instantiates Relay Client /// - Parameters: - /// - relayHost: proxy server host that your application will use to connect to Waku Network. If you register your project at `www.walletconnect.com` you can use `relay.walletconnect.com` + /// - relayHost: proxy server host that your application will use to connect to Iridium Network. If you register your project at `www.walletconnect.com` you can use `relay.walletconnect.com` /// - projectId: an optional parameter used to access the public WalletConnect infrastructure. Go to `www.walletconnect.com` for info. /// - keyValueStorage: by default WalletConnect SDK will store sequences in UserDefaults - /// - uniqueIdentifier: if your app requires more than one relay client instances you are required to identify them /// - socketConnectionType: socket connection type /// - logger: logger instance - public convenience init(relayHost: String, - projectId: String, - keyValueStorage: KeyValueStorage = UserDefaults.standard, - uniqueIdentifier: String? = nil, - socketConnectionType: SocketConnectionType = .automatic, - logger: ConsoleLogging = ConsoleLogger(loggingLevel: .off)) { - let url = Self.makeRelayUrl(host: relayHost, projectId: projectId) - var socketConnectionHandler: SocketConnectionHandler - let socket = WebSocket(url: url) + public convenience init( + relayHost: String, + projectId: String, + keyValueStorage: KeyValueStorage = UserDefaults.standard, + keychainStorage: KeychainStorageProtocol = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk"), + socketFactory: WebSocketFactory, + socketConnectionType: SocketConnectionType = .automatic, + logger: ConsoleLogging = ConsoleLogger(loggingLevel: .off) + ) { + let socketAuthenticator = SocketAuthenticator( + clientIdStorage: ClientIdStorage(keychain: keychainStorage), + didKeyFactory: ED25519DIDKeyFactory(), + relayHost: relayHost + ) + let relayUrlFactory = RelayUrlFactory(socketAuthenticator: socketAuthenticator) + let socket = socketFactory.create(with: relayUrlFactory.create( + host: relayHost, + projectId: projectId + )) + let socketConnectionHandler: SocketConnectionHandler switch socketConnectionType { case .automatic: socketConnectionHandler = AutomaticSocketConnectionHandler(socket: socket) @@ -72,9 +84,7 @@ public final class RelayClient { socketConnectionHandler = ManualSocketConnectionHandler(socket: socket) } let dispatcher = Dispatcher(socket: socket, socketConnectionHandler: socketConnectionHandler, logger: logger) - self.init(dispatcher: dispatcher, - logger: logger, - keyValueStorage: keyValueStorage) + self.init(dispatcher: dispatcher, logger: logger, keyValueStorage: keyValueStorage) } public func connect() throws { @@ -86,9 +96,9 @@ public final class RelayClient { } /// Completes when networking client sends a request, error if it fails on client side - public func publish(topic: String, payload: String, prompt: Bool = false) async throws { - let params = RelayJSONRPC.PublishParams(topic: topic, message: payload, ttl: defaultTtl, prompt: prompt) - let request = JSONRPCRequest(method: RelayJSONRPC.Method.publish.rawValue, params: params) + public func publish(topic: String, payload: String, tag: PublishTag, prompt: Bool = false) async throws { + let params = RelayJSONRPC.PublishParams(topic: topic, message: payload, ttl: defaultTtl, prompt: prompt, tag: tag.rawValue) + let request = JSONRPCRequest(method: RelayJSONRPC.Method.publish.method, params: params) logger.debug("Publishing Payload on Topic: \(topic)") let requestJson = try request.json() try await dispatcher.send(requestJson) @@ -98,12 +108,13 @@ public final class RelayClient { @discardableResult public func publish( topic: String, payload: String, + tag: PublishTag, prompt: Bool = false, onNetworkAcknowledge: @escaping ((Error?) -> Void)) -> Int64 { - let params = RelayJSONRPC.PublishParams(topic: topic, message: payload, ttl: defaultTtl, prompt: prompt) - let request = JSONRPCRequest(method: RelayJSONRPC.Method.publish.rawValue, params: params) + let params = RelayJSONRPC.PublishParams(topic: topic, message: payload, ttl: defaultTtl, prompt: prompt, tag: tag.rawValue) + let request = JSONRPCRequest(method: RelayJSONRPC.Method.publish.method, params: params) let requestJson = try! request.json() - logger.debug("waku: Publishing Payload on Topic: \(topic)") + logger.debug("iridium: Publishing Payload on Topic: \(topic)") var cancellable: AnyCancellable? dispatcher.send(requestJson) { [weak self] error in if let error = error { @@ -123,9 +134,9 @@ public final class RelayClient { @available(*, renamed: "subscribe(topic:)") public func subscribe(topic: String, completion: @escaping (Error?) -> Void) { - logger.debug("waku: Subscribing on Topic: \(topic)") + logger.debug("iridium: Subscribing on Topic: \(topic)") let params = RelayJSONRPC.SubscribeParams(topic: topic) - let request = JSONRPCRequest(method: RelayJSONRPC.Method.subscribe.rawValue, params: params) + let request = JSONRPCRequest(method: RelayJSONRPC.Method.subscribe.method, params: params) let requestJson = try! request.json() var cancellable: AnyCancellable? dispatcher.send(requestJson) { [weak self] error in @@ -164,9 +175,9 @@ public final class RelayClient { completion(RelyerError.subscriptionIdNotFound) return nil } - logger.debug("waku: Unsubscribing on Topic: \(topic)") + logger.debug("iridium: Unsubscribing on Topic: \(topic)") let params = RelayJSONRPC.UnsubscribeParams(id: subscriptionId, topic: topic) - let request = JSONRPCRequest(method: RelayJSONRPC.Method.unsubscribe.rawValue, params: params) + let request = JSONRPCRequest(method: RelayJSONRPC.Method.unsubscribe.method, params: params) let requestJson = try! request.json() var cancellable: AnyCancellable? jsonRpcSubscriptionsHistory.delete(topic: topic) @@ -201,8 +212,7 @@ public final class RelayClient { } private func handlePayloadMessage(_ payload: String) { - if let request = tryDecode(SubscriptionRequest.self, from: payload), - request.method == RelayJSONRPC.Method.subscription.rawValue { + if let request = tryDecode(SubscriptionRequest.self, from: payload), validate(request: request, method: .subscription) { do { try jsonRpcSubscriptionsHistory.set(topic: request.params.data.topic, request: request) onMessage?(request.params.data.topic, request.params.data.message) @@ -215,12 +225,16 @@ public final class RelayClient { } else if let response = tryDecode(SubscriptionResponse.self, from: payload) { subscriptionResponsePublisherSubject.send(response) } else if let response = tryDecode(JSONRPCErrorResponse.self, from: payload) { - logger.error("Received error message from waku network, code: \(response.error.code), message: \(response.error.message)") + logger.error("Received error message from iridium network, code: \(response.error.code), message: \(response.error.message)") } else { logger.error("Unexpected response from network") } } + private func validate(request: JSONRPCRequest, method: RelayJSONRPC.Method) -> Bool { + return request.method.contains(method.name) + } + private func tryDecode(_ type: T.Type, from payload: String) -> T? { if let data = payload.data(using: .utf8), let response = try? JSONDecoder().decode(T.self, from: data) { @@ -240,12 +254,4 @@ public final class RelayClient { } } } - - static func makeRelayUrl(host: String, projectId: String) -> URL { - var components = URLComponents() - components.scheme = "wss" - components.host = host - components.queryItems = [URLQueryItem(name: "projectId", value: projectId)] - return components.url! - } } diff --git a/Sources/WalletConnectRelay/RelayJSONRPC.swift b/Sources/WalletConnectRelay/RelayJSONRPC.swift index caac3ef03..e83de4427 100644 --- a/Sources/WalletConnectRelay/RelayJSONRPC.swift +++ b/Sources/WalletConnectRelay/RelayJSONRPC.swift @@ -3,11 +3,11 @@ import Foundation enum RelayJSONRPC { - enum Method: String { - case subscribe = "waku_subscribe" - case publish = "waku_publish" - case subscription = "waku_subscription" - case unsubscribe = "waku_unsubscribe" + enum Method { + case subscribe + case publish + case subscription + case unsubscribe } struct PublishParams: Codable, Equatable { @@ -15,6 +15,7 @@ enum RelayJSONRPC { let message: String let ttl: Int let prompt: Bool? + let tag: Int? } struct SubscribeParams: Codable, Equatable { @@ -36,3 +37,27 @@ enum RelayJSONRPC { let topic: String } } + +extension RelayJSONRPC.Method { + + var prefix: String { + return "iridium" + } + + var name: String { + switch self { + case .subscribe: + return "subscribe" + case .publish: + return "publish" + case .subscription: + return "subscription" + case .unsubscribe: + return "unsubscribe" + } + } + + var method: String { + return "\(prefix)_\(name)" + } +} diff --git a/Sources/WalletConnectRelay/RelayURLFactory.swift b/Sources/WalletConnectRelay/RelayURLFactory.swift new file mode 100644 index 000000000..4ee15f387 --- /dev/null +++ b/Sources/WalletConnectRelay/RelayURLFactory.swift @@ -0,0 +1,26 @@ +import Foundation + +struct RelayUrlFactory { + private let socketAuthenticator: SocketAuthenticating + + init(socketAuthenticator: SocketAuthenticating) { + self.socketAuthenticator = socketAuthenticator + } + + func create(host: String, projectId: String) -> URL { + var components = URLComponents() + components.scheme = "wss" + components.host = host + components.queryItems = [ + URLQueryItem(name: "projectId", value: projectId) + ] + do { + let authToken = try socketAuthenticator.createAuthToken() + components.queryItems?.append(URLQueryItem(name: "auth", value: authToken)) + } catch { + // TODO: Handle token creation errors + print("Auth token creation error: \(error.localizedDescription)") + } + return components.url! + } +} diff --git a/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift b/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift index 1eb82b59a..20eab6208 100644 --- a/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift +++ b/Sources/WalletConnectRelay/SocketConnectionHandler/AutomaticSocketConnectionHandler.swift @@ -2,6 +2,7 @@ import UIKit #endif import Foundation +import Combine class AutomaticSocketConnectionHandler: SocketConnectionHandler { enum Error: Swift.Error { @@ -13,6 +14,8 @@ class AutomaticSocketConnectionHandler: SocketConnectionHandler { private var networkMonitor: NetworkMonitoring private let backgroundTaskRegistrar: BackgroundTaskRegistering + private var publishers = Set() + init(networkMonitor: NetworkMonitoring = NetworkMonitor(), socket: WebSocketConnecting, appStateObserver: AppStateObserving = AppStateObserver(), @@ -23,6 +26,7 @@ class AutomaticSocketConnectionHandler: SocketConnectionHandler { self.backgroundTaskRegistrar = backgroundTaskRegistrar setUpStateObserving() setUpNetworkMonitoring() + socket.connect() } diff --git a/Sources/WalletConnectRelay/SocketConnectionHandler/ManualSocketConnectionHandler.swift b/Sources/WalletConnectRelay/SocketConnectionHandler/ManualSocketConnectionHandler.swift index ccd3978b4..25f293d05 100644 --- a/Sources/WalletConnectRelay/SocketConnectionHandler/ManualSocketConnectionHandler.swift +++ b/Sources/WalletConnectRelay/SocketConnectionHandler/ManualSocketConnectionHandler.swift @@ -1,4 +1,3 @@ -import Starscream import Foundation class ManualSocketConnectionHandler: SocketConnectionHandler { diff --git a/Sources/WalletConnectRelay/SocketConnectionHandler/SocketConnectionHandler.swift b/Sources/WalletConnectRelay/SocketConnectionHandler/SocketConnectionHandler.swift index dad34f1cd..0b5e0673b 100644 --- a/Sources/WalletConnectRelay/SocketConnectionHandler/SocketConnectionHandler.swift +++ b/Sources/WalletConnectRelay/SocketConnectionHandler/SocketConnectionHandler.swift @@ -1,7 +1,6 @@ import Foundation protocol SocketConnectionHandler { - var socket: WebSocketConnecting {get} func handleConnect() throws func handleDisconnect(closeCode: URLSessionWebSocketTask.CloseCode) throws } diff --git a/Sources/WalletConnectRelay/SocketConnectionHandler/WebSocket.swift b/Sources/WalletConnectRelay/SocketConnectionHandler/WebSocket.swift new file mode 100644 index 000000000..0e7253ad9 --- /dev/null +++ b/Sources/WalletConnectRelay/SocketConnectionHandler/WebSocket.swift @@ -0,0 +1,16 @@ +import Foundation + +public protocol WebSocketConnecting: AnyObject { + var isConnected: Bool { get } + var onConnect: (() -> Void)? { get set } + var onDisconnect: ((Error?) -> Void)? { get set } + var onText: ((String) -> Void)? { get set } + + func connect() + func disconnect() + func write(string: String, completion: (() -> Void)?) +} + +public protocol WebSocketFactory { + func create(with url: URL) -> WebSocketConnecting +} diff --git a/Sources/WalletConnectRelay/SocketConnectionHandler/WebSocketConnecting.swift b/Sources/WalletConnectRelay/SocketConnectionHandler/WebSocketConnecting.swift deleted file mode 100644 index 3ab34e8d4..000000000 --- a/Sources/WalletConnectRelay/SocketConnectionHandler/WebSocketConnecting.swift +++ /dev/null @@ -1,10 +0,0 @@ -import Starscream -import Foundation - -extension WebSocket: WebSocketConnecting {} - -protocol WebSocketConnecting { - var isConnected: Bool {get} - func connect() - func disconnect() -} diff --git a/Sources/WalletConnectRelay/SocketConnectionHandler/WebSocketProtocol.swift b/Sources/WalletConnectRelay/SocketConnectionHandler/WebSocketProtocol.swift deleted file mode 100644 index 3c03a2c83..000000000 --- a/Sources/WalletConnectRelay/SocketConnectionHandler/WebSocketProtocol.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Starscream - -extension WebSocket: WebSocketProtocol {} - -protocol WebSocketProtocol { - var isConnected: Bool {get} - var onConnect: (() -> Void)? { get set } - var onDisconnect: ((Error?) -> Void)? { get set } - var onText: ((String) -> Void)? { get set } - func write(string: String, completion: (() -> Void)?) -} diff --git a/Sources/WalletConnectSign/NetworkInteractor/NetworkInteractor.swift b/Sources/WalletConnectSign/NetworkInteractor/NetworkInteractor.swift index e21fc3858..d4dc4fcb3 100644 --- a/Sources/WalletConnectSign/NetworkInteractor/NetworkInteractor.swift +++ b/Sources/WalletConnectSign/NetworkInteractor/NetworkInteractor.swift @@ -71,7 +71,7 @@ class NetworkInteractor: NetworkInteracting { try jsonRpcHistory.set(topic: topic, request: payload, chainId: getChainId(payload)) let message = try serializer.serialize(topic: topic, encodable: payload) let prompt = shouldPrompt(payload.method) - try await relayClient.publish(topic: topic, payload: message, prompt: prompt) + try await relayClient.publish(topic: topic, payload: message, tag: .sign, prompt: prompt) } func requestPeerResponse(_ wcMethod: WCMethod, onTopic topic: String, completion: ((Result, JSONRPCErrorResponse>) -> Void)?) { @@ -80,7 +80,7 @@ class NetworkInteractor: NetworkInteracting { try jsonRpcHistory.set(topic: topic, request: payload, chainId: getChainId(payload)) let message = try serializer.serialize(topic: topic, encodable: payload) let prompt = shouldPrompt(payload.method) - relayClient.publish(topic: topic, payload: message, prompt: prompt) { [weak self] error in + relayClient.publish(topic: topic, payload: message, tag: .sign, prompt: prompt) { [weak self] error in guard let self = self else {return} if let error = error { self.logger.error(error) @@ -116,7 +116,7 @@ class NetworkInteractor: NetworkInteracting { try jsonRpcHistory.set(topic: topic, request: payload, chainId: getChainId(payload)) let message = try serializer.serialize(topic: topic, encodable: payload) let prompt = shouldPrompt(payload.method) - relayClient.publish(topic: topic, payload: message, prompt: prompt) { error in + relayClient.publish(topic: topic, payload: message, tag: .sign, prompt: prompt) { error in completion(error) } } catch WalletConnectError.internal(.jsonRpcDuplicateDetected) { @@ -133,7 +133,7 @@ class NetworkInteractor: NetworkInteracting { logger.debug("Responding....topic: \(topic)") do { - try await relayClient.publish(topic: topic, payload: message, prompt: false) + try await relayClient.publish(topic: topic, payload: message, tag: .sign, prompt: false) } catch WalletConnectError.internal(.jsonRpcDuplicateDetected) { logger.info("Info: Json Rpc Duplicate Detected") } diff --git a/Sources/WalletConnectSign/NetworkInteractor/NetworkRelaying.swift b/Sources/WalletConnectSign/NetworkInteractor/NetworkRelaying.swift index a6a948b22..da0df80c2 100644 --- a/Sources/WalletConnectSign/NetworkInteractor/NetworkRelaying.swift +++ b/Sources/WalletConnectSign/NetworkInteractor/NetworkRelaying.swift @@ -9,9 +9,9 @@ protocol NetworkRelaying { var socketConnectionStatusPublisher: AnyPublisher { get } func connect() throws func disconnect(closeCode: URLSessionWebSocketTask.CloseCode) throws - func publish(topic: String, payload: String, prompt: Bool) async throws + func publish(topic: String, payload: String, tag: PublishTag, prompt: Bool) async throws /// - returns: request id - @discardableResult func publish(topic: String, payload: String, prompt: Bool, onNetworkAcknowledge: @escaping ((Error?) -> Void)) -> Int64 + @discardableResult func publish(topic: String, payload: String, tag: PublishTag, prompt: Bool, onNetworkAcknowledge: @escaping ((Error?) -> Void)) -> Int64 func subscribe(topic: String, completion: @escaping (Error?) -> Void) func subscribe(topic: String) async throws /// - returns: request id diff --git a/Sources/WalletConnectSign/Sign/Sign.swift b/Sources/WalletConnectSign/Sign/Sign.swift index 8cd067080..f5eb6e5dc 100644 --- a/Sources/WalletConnectSign/Sign/Sign.swift +++ b/Sources/WalletConnectSign/Sign/Sign.swift @@ -17,13 +17,28 @@ public class Sign { guard let config = Sign.config else { fatalError("Error - you must call configure(_:) before accessing the shared instance.") } - relayClient = RelayClient(relayHost: "relay.walletconnect.com", projectId: config.projectId, socketConnectionType: config.socketConnectionType) + relayClient = RelayClient( + relayHost: "relay.walletconnect.com", + projectId: config.projectId, + socketFactory: config.socketFactory, + socketConnectionType: config.socketConnectionType + ) client = SignClient(metadata: config.metadata, relayClient: relayClient) client.delegate = self } - static public func configure(_ config: Config) { - Sign.config = config + static public func configure( + metadata: AppMetadata, + projectId: String, + socketFactory: WebSocketFactory, + socketConnectionType: SocketConnectionType = .automatic + ) { + Sign.config = Sign.Config( + metadata: metadata, + projectId: projectId, + socketFactory: socketFactory, + socketConnectionType: socketConnectionType + ) } var sessionProposalPublisherSubject = PassthroughSubject() diff --git a/Sources/WalletConnectSign/Sign/SignClient.swift b/Sources/WalletConnectSign/Sign/SignClient.swift index 9659ef9d6..2d0e086aa 100644 --- a/Sources/WalletConnectSign/Sign/SignClient.swift +++ b/Sources/WalletConnectSign/Sign/SignClient.swift @@ -24,15 +24,12 @@ public final class SignClient { public weak var delegate: SignClientDelegate? public let logger: ConsoleLogging - private let metadata: AppMetadata private let pairingEngine: PairingEngine private let pairEngine: PairEngine private let sessionEngine: SessionEngine private let approveEngine: ApproveEngine private let nonControllerSessionStateMachine: NonControllerSessionStateMachine private let controllerSessionStateMachine: ControllerSessionStateMachine - private let networkingInteractor: NetworkInteracting - private let kms: KeyManagementService private let history: JsonRpcHistory private let cleanupService: CleanupService private var publishers = [AnyCancellable]() @@ -44,22 +41,29 @@ public final class SignClient { /// - Parameters: /// - metadata: describes your application and will define pairing appearance in a web browser. /// - projectId: an optional parameter used to access the public WalletConnect infrastructure. Go to `www.walletconnect.com` for info. - /// - relayHost: proxy server host that your application will use to connect to Waku Network. If you register your project at `www.walletconnect.com` you can use `relay.walletconnect.com` + /// - relayHost: proxy server host that your application will use to connect to Iridium Network. If you register your project at `www.walletconnect.com` you can use `relay.walletconnect.com` /// - keyValueStorage: by default WalletConnect SDK will store sequences in UserDefaults /// /// WalletConnect Client is not a singleton but once you create an instance, you should not deinitialize it. Usually only one instance of a client is required in the application. - public convenience init(metadata: AppMetadata, projectId: String, relayHost: String, keyValueStorage: KeyValueStorage = UserDefaults.standard) { - self.init(metadata: metadata, projectId: projectId, relayHost: relayHost, logger: ConsoleLogger(loggingLevel: .off), kms: KeyManagementService(serviceIdentifier: "com.walletconnect.sdk"), keyValueStorage: keyValueStorage) + public convenience init(metadata: AppMetadata, relayClient: RelayClient) { + let logger = ConsoleLogger(loggingLevel: .off) + let keyValueStorage = UserDefaults.standard + let keychainStorage = KeychainStorage(serviceIdentifier: "com.walletconnect.sdk") + self.init( + metadata: metadata, + logger: logger, + keyValueStorage: keyValueStorage, + keychainStorage: keychainStorage, + relayClient: relayClient + ) } - init(metadata: AppMetadata, projectId: String, relayHost: String, logger: ConsoleLogging, kms: KeyManagementService, keyValueStorage: KeyValueStorage) { - self.metadata = metadata + init(metadata: AppMetadata, logger: ConsoleLogging, keyValueStorage: KeyValueStorage, keychainStorage: KeychainStorageProtocol, relayClient: RelayClient) { self.logger = logger - self.kms = kms - let relayClient = RelayClient(relayHost: relayHost, projectId: projectId, keyValueStorage: keyValueStorage, logger: logger) + let kms = KeyManagementService(keychain: keychainStorage) let serializer = Serializer(kms: kms) self.history = JsonRpcHistory(logger: logger, keyValueStore: CodableStore(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.jsonRpcHistory.rawValue)) - self.networkingInteractor = NetworkInteractor(relayClient: relayClient, serializer: serializer, logger: logger, jsonRpcHistory: history) + let networkingInteractor = NetworkInteractor(relayClient: relayClient, serializer: serializer, logger: logger, jsonRpcHistory: history) let pairingStore = PairingStorage(storage: SequenceStore(store: .init(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.pairings.rawValue))) let sessionStore = SessionStorage(storage: SequenceStore(store: .init(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.sessions.rawValue))) let sessionToPairingTopic = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: StorageDomainIdentifiers.sessionToPairingTopic.rawValue) @@ -75,46 +79,6 @@ public final class SignClient { setUpEnginesCallbacks() } - /// Initializes and returns newly created WalletConnect Client Instance. Establishes a network connection with the relay - /// - /// - Parameters: - /// - metadata: describes your application and will define pairing appearance in a web browser. - /// - relayClient: RelayClient instance - /// - keyValueStorage: by default WalletConnect SDK will store sequences in UserDefaults but if for some reasons you want to provide your own storage you can inject it here. - /// - /// WalletConnect Client is not a singleton but once you create an instance, you should not deinitialize it. Usually only one instance of a client is required in the application. - public convenience init(metadata: AppMetadata, relayClient: RelayClient, keyValueStorage: KeyValueStorage = UserDefaults.standard, kms: KeyManagementService = KeyManagementService(serviceIdentifier: "com.walletconnect.sdk")) { - self.init(metadata: metadata, relayClient: relayClient, logger: ConsoleLogger(loggingLevel: .off), kms: kms, keyValueStorage: keyValueStorage) - } - - init(metadata: AppMetadata, relayClient: RelayClient, logger: ConsoleLogging, kms: KeyManagementService, keyValueStorage: KeyValueStorage) { - self.metadata = metadata - self.logger = logger - self.kms = kms - let serializer = Serializer(kms: kms) - self.history = JsonRpcHistory(logger: logger, keyValueStore: CodableStore(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.jsonRpcHistory.rawValue)) - self.networkingInteractor = NetworkInteractor(relayClient: relayClient, serializer: serializer, logger: logger, jsonRpcHistory: history) - let pairingStore = PairingStorage(storage: SequenceStore(store: .init(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.pairings.rawValue))) - let sessionStore = SessionStorage(storage: SequenceStore(store: .init(defaults: keyValueStorage, identifier: StorageDomainIdentifiers.sessions.rawValue))) - let sessionToPairingTopic = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: StorageDomainIdentifiers.sessionToPairingTopic.rawValue) - let proposalPayloadsStore = CodableStore(defaults: RuntimeKeyValueStorage(), identifier: StorageDomainIdentifiers.proposals.rawValue) - self.pairingEngine = PairingEngine(networkingInteractor: networkingInteractor, kms: kms, pairingStore: pairingStore, metadata: metadata, logger: logger) - self.sessionEngine = SessionEngine(networkingInteractor: networkingInteractor, kms: kms, sessionStore: sessionStore, logger: logger) - self.approveEngine = ApproveEngine(networkingInteractor: networkingInteractor, proposalPayloadsStore: proposalPayloadsStore, sessionToPairingTopic: sessionToPairingTopic, metadata: metadata, kms: kms, logger: logger, pairingStore: pairingStore, sessionStore: sessionStore) - self.nonControllerSessionStateMachine = NonControllerSessionStateMachine(networkingInteractor: networkingInteractor, kms: kms, sessionStore: sessionStore, logger: logger) - self.controllerSessionStateMachine = ControllerSessionStateMachine(networkingInteractor: networkingInteractor, kms: kms, sessionStore: sessionStore, logger: logger) - self.pairEngine = PairEngine(networkingInteractor: networkingInteractor, kms: kms, pairingStore: pairingStore) - self.cleanupService = CleanupService(pairingStore: pairingStore, sessionStore: sessionStore, kms: kms, sessionToPairingTopic: sessionToPairingTopic) - setUpConnectionObserving(relayClient: relayClient) - setUpEnginesCallbacks() - } - - private func setUpConnectionObserving(relayClient: RelayClient) { - relayClient.socketConnectionStatusPublisher.sink { [weak self] status in - self?.delegate?.didChangeSocketConnectionStatus(status) - }.store(in: &publishers) - } - // MARK: - Public interface /// For the Proposer to propose a session to a responder. @@ -328,6 +292,12 @@ public final class SignClient { } } + private func setUpConnectionObserving(relayClient: RelayClient) { + relayClient.socketConnectionStatusPublisher.sink { [weak self] status in + self?.delegate?.didChangeSocketConnectionStatus(status) + }.store(in: &publishers) + } + #if DEBUG /// Delete all stored data sach as: pairings, sessions, keys /// diff --git a/Sources/WalletConnectSign/Sign/SignConfig.swift b/Sources/WalletConnectSign/Sign/SignConfig.swift index 09e84e788..b6167c473 100644 --- a/Sources/WalletConnectSign/Sign/SignConfig.swift +++ b/Sources/WalletConnectSign/Sign/SignConfig.swift @@ -5,11 +5,18 @@ public extension Sign { struct Config { let metadata: AppMetadata let projectId: String + let socketFactory: WebSocketFactory let socketConnectionType: SocketConnectionType - public init(metadata: AppMetadata, projectId: String, socketConnectionType: SocketConnectionType = .automatic) { + public init( + metadata: AppMetadata, + projectId: String, + socketFactory: WebSocketFactory, + socketConnectionType: SocketConnectionType = .automatic + ) { self.metadata = metadata self.projectId = projectId + self.socketFactory = socketFactory self.socketConnectionType = socketConnectionType } } diff --git a/Sources/WalletConnectSign/Types/Pairing/WCPairing.swift b/Sources/WalletConnectSign/Types/Pairing/WCPairing.swift index 7fd2c2929..794f36c86 100644 --- a/Sources/WalletConnectSign/Types/Pairing/WCPairing.swift +++ b/Sources/WalletConnectSign/Types/Pairing/WCPairing.swift @@ -32,7 +32,7 @@ struct WCPairing: SequenceObject { init(topic: String) { self.topic = topic - self.relay = RelayProtocolOptions(protocol: "waku", data: nil) + self.relay = RelayProtocolOptions(protocol: "iridium", data: nil) self.active = false self.expiryDate = Self.dateInitializer().advanced(by: Self.timeToLiveInactive) } diff --git a/Tests/IntegrationTests/AuthChallengeTests.swift b/Tests/IntegrationTests/AuthChallengeTests.swift new file mode 100644 index 000000000..e708c3b49 --- /dev/null +++ b/Tests/IntegrationTests/AuthChallengeTests.swift @@ -0,0 +1,22 @@ +import XCTest +import WalletConnectKMS +@testable import WalletConnectRelay + +final class AuthChallengeTests: XCTestCase { + + let relayHost: String = "dev.relay.walletconnect.com" + + var httpClient: HTTPClient! + var provider: AuthChallengeProvider! + + override func setUp() { + httpClient = HTTPClient(host: relayHost) + provider = AuthChallengeProvider(client: httpClient) + } + + func testGetChallenge() async throws { + let key = SigningPrivateKey().publicKey.hexRepresentation + let challenge = try await provider.getChallenge(for: key) + XCTAssertFalse(challenge.nonce.isEmpty) + } +} diff --git a/Tests/RelayerTests/AuthTests/Base58Tests.swift b/Tests/RelayerTests/AuthTests/Base58Tests.swift new file mode 100644 index 000000000..13813cc1e --- /dev/null +++ b/Tests/RelayerTests/AuthTests/Base58Tests.swift @@ -0,0 +1,64 @@ +import Foundation +import XCTest +@testable import WalletConnectRelay + +final class Base58Tests: XCTestCase { + + private let validStringDecodedToEncodedTuples = [ + ("", ""), + (" ", "Z"), + ("-", "n"), + ("0", "q"), + ("1", "r"), + ("-1", "4SU"), + ("11", "4k8"), + ("abc", "ZiCa"), + ("1234598760", "3mJr7AoUXx2Wqd"), + ("abcdefghijklmnopqrstuvwxyz", "3yxU3u1igY8WkgtjK92fbJQCd4BZiiT1v25f"), + ("00000000000000000000000000000000000000000000000000000000000000", + "3sN2THZeE9Eh9eYrwkvZqNstbHGvrxSAM7gXUXvyFQP8XvQLUqNCS27icwUeDT7ckHm4FUHM2mTVh1vbLmk7y") + ] + + private let invalidStrings = [ + "0", + "O", + "I", + "l", + "3mJr0", + "O3yxU", + "3sNI", + "4kl8", + "0OIl", + "!@#$%^&*()-_=+~`" + ] + + func testBase58AddressDecoding() { + let address = "FAKUpR8McSoHT1sTksfJu3L1SpRtHK91ocDYtop4A7HW" + let result = Data(hex: "d266bd3d305bb45b5c12f8ff9b6315427e7a32a12c9f497c0c9c76cf5125278d") + + XCTAssertEqual(Base58.decode(address), result) + } + + func testBase58EncodingForValidStrings() { + for (decoded, encoded) in validStringDecodedToEncodedTuples { + let data = Data(decoded.utf8) + let result = Base58.encode(data) + XCTAssertEqual(result, encoded) + } + } + + func testBase58DecodingForValidStrings() { + for (decoded, encoded) in validStringDecodedToEncodedTuples { + let data = Base58.decode(encoded) + let result = String(data: data, encoding: String.Encoding.utf8) + XCTAssertEqual(result, decoded) + } + } + + func testBase58DecodingForInvalidStrings() { + for invalidString in invalidStrings { + let result = Base58.decode(invalidString) + XCTAssertEqual(result, Data()) + } + } +} diff --git a/Tests/RelayerTests/AuthTests/ClientIdStorageTests.swift b/Tests/RelayerTests/AuthTests/ClientIdStorageTests.swift index 4586e8004..38e27a073 100644 --- a/Tests/RelayerTests/AuthTests/ClientIdStorageTests.swift +++ b/Tests/RelayerTests/AuthTests/ClientIdStorageTests.swift @@ -6,16 +6,16 @@ import WalletConnectKMS final class ClientIdStorageTests: XCTestCase { - func testGetOrCreate() async throws { + func testGetOrCreate() throws { let keychain = KeychainStorageMock() let storage = ClientIdStorage(keychain: keychain) XCTAssertThrowsError(try keychain.read(key: "com.walletconnect.iridium.client_id") as SigningPrivateKey) - let saved = try await storage.getOrCreateKeyPair() + let saved = try storage.getOrCreateKeyPair() XCTAssertEqual(saved, try keychain.read(key: "com.walletconnect.iridium.client_id")) - let restored = try await storage.getOrCreateKeyPair() + let restored = try storage.getOrCreateKeyPair() XCTAssertEqual(saved, restored) } } diff --git a/Tests/RelayerTests/AuthTests/ED25519DIDKeyTests.swift b/Tests/RelayerTests/AuthTests/ED25519DIDKeyTests.swift index cf4bee14f..c28189291 100644 --- a/Tests/RelayerTests/AuthTests/ED25519DIDKeyTests.swift +++ b/Tests/RelayerTests/AuthTests/ED25519DIDKeyTests.swift @@ -3,17 +3,23 @@ import XCTest @testable import WalletConnectRelay final class ED25519DIDKeyFactoryTests: XCTestCase { - let expectedDid = "did:key:z6MkodHZwneVRShtaLf8JKYkxpDGp1vGZnpGmdBpX8M2exxH" + let expectedDidWithPrefix = "did:key:z6MkodHZwneVRShtaLf8JKYkxpDGp1vGZnpGmdBpX8M2exxH" + let expectedDidWithoutPrefix = "z6MkodHZwneVRShtaLf8JKYkxpDGp1vGZnpGmdBpX8M2exxH" let pubKey = Data(hex: "884ab67f787b69e534bfdba8d5beb4e719700e90ac06317ed177d49e5a33be5a") - var sut: ED25519DIDKeyFactoryImpl! + + var sut: ED25519DIDKeyFactory! override func setUp() { - sut = ED25519DIDKeyFactoryImpl() + sut = ED25519DIDKeyFactory() } - func test() { -// let did = sut.make(pubKey: pubKey) -// XCTAssertEqual(expectedDid, did) + func testKeyCreationWithoutPrefix() { + let did = sut.make(pubKey: pubKey, prefix: false) + XCTAssertEqual(expectedDidWithoutPrefix, did) } + func testKeyCreationWithPrefix() { + let did = sut.make(pubKey: pubKey, prefix: true) + XCTAssertEqual(expectedDidWithPrefix, did) + } } diff --git a/Tests/RelayerTests/AuthTests/EdDSASignerTests.swift b/Tests/RelayerTests/AuthTests/EdDSASignerTests.swift index 301b6ddef..2e5643785 100644 --- a/Tests/RelayerTests/AuthTests/EdDSASignerTests.swift +++ b/Tests/RelayerTests/AuthTests/EdDSASignerTests.swift @@ -11,10 +11,7 @@ final class EdDSASignerTests: XCTestCase { let signingKey = try! SigningPrivateKey(rawRepresentation: keyRaw) sut = EdDSASigner(signingKey) let header = try! JWT.Header(alg: "EdDSA").encode() - let claims = try! JWT.Claims( - iss: "did:key:z6MkodHZwneVRShtaLf8JKYkxpDGp1vGZnpGmdBpX8M2exxH", - sub: "c479fe5dc464e771e78b193d239a65b58d278cad1c34bfb0b5716e5bb514928e") - .encode() + let claims = try! JWT.Claims.stub().encode() let signature = try! sut.sign(header: header, claims: claims) XCTAssertNotNil(signature) } diff --git a/Tests/RelayerTests/AuthTests/JWTTests.swift b/Tests/RelayerTests/AuthTests/JWTTests.swift index 58e083489..25dde52d4 100644 --- a/Tests/RelayerTests/AuthTests/JWTTests.swift +++ b/Tests/RelayerTests/AuthTests/JWTTests.swift @@ -3,12 +3,10 @@ import XCTest @testable import WalletConnectRelay final class JWTTests: XCTestCase { - let expectedJWT = "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtvZEhad25lVlJTaHRhTGY4SktZa3hwREdwMXZHWm5wR21kQnBYOE0yZXh4SCIsInN1YiI6ImM0NzlmZTVkYzQ2NGU3NzFlNzhiMTkzZDIzOWE2NWI1OGQyNzhjYWQxYzM0YmZiMGI1NzE2ZTViYjUxNDkyOGUifQ.0JkxOM-FV21U7Hk-xycargj_qNRaYV2H5HYtE4GzAeVQYiKWj7YySY5AdSqtCgGzX4Gt98XWXn2kSr9rE1qvCA" + let expectedJWT = "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2NTY5MTAwOTcsImV4cCI6MTY1Njk5NjQ5NywiaXNzIjoiZGlkOmtleTp6Nk1rb2RIWnduZVZSU2h0YUxmOEpLWWt4cERHcDF2R1pucEdtZEJwWDhNMmV4eEgiLCJzdWIiOiJjNDc5ZmU1ZGM0NjRlNzcxZTc4YjE5M2QyMzlhNjViNThkMjc4Y2FkMWMzNGJmYjBiNTcxNmU1YmI1MTQ5MjhlIiwiYXVkIjoid3NzOlwvXC9yZWxheS53YWxsZXRjb25uZWN0LmNvbSJ9.0JkxOM-FV21U7Hk-xycargj_qNRaYV2H5HYtE4GzAeVQYiKWj7YySY5AdSqtCgGzX4Gt98XWXn2kSr9rE1qvCA" func testJWTEncoding() { - let iss = "did:key:z6MkodHZwneVRShtaLf8JKYkxpDGp1vGZnpGmdBpX8M2exxH" - let sub = "c479fe5dc464e771e78b193d239a65b58d278cad1c34bfb0b5716e5bb514928e" - let claims = JWT.Claims(iss: iss, sub: sub) + let claims = JWT.Claims.stub() var jwt = JWT(claims: claims) let signer = EdDSASignerMock() signer.signature = "0JkxOM-FV21U7Hk-xycargj_qNRaYV2H5HYtE4GzAeVQYiKWj7YySY5AdSqtCgGzX4Gt98XWXn2kSr9rE1qvCA" @@ -16,5 +14,17 @@ final class JWTTests: XCTestCase { let encoded = try! jwt.encoded() XCTAssertEqual(expectedJWT, encoded) } +} +extension JWT.Claims { + static func stub() -> JWT.Claims { + let iss = "did:key:z6MkodHZwneVRShtaLf8JKYkxpDGp1vGZnpGmdBpX8M2exxH" + let sub = "c479fe5dc464e771e78b193d239a65b58d278cad1c34bfb0b5716e5bb514928e" + let iat = Date(timeIntervalSince1970: 1656910097) + var components = DateComponents() + components.setValue(1, for: .day) + let aud = "wss://relay.walletconnect.com" + let exp = Calendar.current.date(byAdding: components, to: iat)! + return JWT.Claims(iss: iss, sub: sub, aud: aud, iat: iat, exp: exp) + } } diff --git a/Tests/RelayerTests/AuthTests/SocketAuthenticatorTests.swift b/Tests/RelayerTests/AuthTests/SocketAuthenticatorTests.swift index 12389e1e5..e54f6f7dc 100644 --- a/Tests/RelayerTests/AuthTests/SocketAuthenticatorTests.swift +++ b/Tests/RelayerTests/AuthTests/SocketAuthenticatorTests.swift @@ -4,29 +4,27 @@ import WalletConnectKMS @testable import WalletConnectRelay final class SocketAuthenticatorTests: XCTestCase { - var authChallengeProvider: AuthChallengeProviderMock! var clientIdStorage: ClientIdStorageMock! var DIDKeyFactory: ED25519DIDKeyFactoryMock! var sut: SocketAuthenticator! let expectedToken = "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJkaWQ6a2V5Ono2TWtvZEhad25lVlJTaHRhTGY4SktZa3hwREdwMXZHWm5wR21kQnBYOE0yZXh4SCIsInN1YiI6ImM0NzlmZTVkYzQ2NGU3NzFlNzhiMTkzZDIzOWE2NWI1OGQyNzhjYWQxYzM0YmZiMGI1NzE2ZTViYjUxNDkyOGUifQ.0JkxOM-FV21U7Hk-xycargj_qNRaYV2H5HYtE4GzAeVQYiKWj7YySY5AdSqtCgGzX4Gt98XWXn2kSr9rE1qvCA" override func setUp() { - authChallengeProvider = AuthChallengeProviderMock() clientIdStorage = ClientIdStorageMock() DIDKeyFactory = ED25519DIDKeyFactoryMock() DIDKeyFactory.did = "did:key:z6MkodHZwneVRShtaLf8JKYkxpDGp1vGZnpGmdBpX8M2exxH" sut = SocketAuthenticator( - authChallengeProvider: authChallengeProvider, clientIdStorage: clientIdStorage, - didKeyFactory: DIDKeyFactory) + didKeyFactory: DIDKeyFactory, + relayHost: "relay.walletconnect.com" + ) } - func test() async { - authChallengeProvider.challange = "c479fe5dc464e771e78b193d239a65b58d278cad1c34bfb0b5716e5bb514928e" + func test() async throws { let keyRaw = Data(hex: "58e0254c211b858ef7896b00e3f36beeb13d568d47c6031c4218b87718061295") - let signingKey = try! SigningPrivateKey(rawRepresentation: keyRaw) + let signingKey = try SigningPrivateKey(rawRepresentation: keyRaw) clientIdStorage.keyPair = signingKey - let token = try! await sut.createAuthToken() + let token = try sut.createAuthToken() XCTAssertNotNil(token) } } diff --git a/Tests/RelayerTests/AutomaticSocketConnectionHandlerTests.swift b/Tests/RelayerTests/AutomaticSocketConnectionHandlerTests.swift index ad3795149..d37cec47f 100644 --- a/Tests/RelayerTests/AutomaticSocketConnectionHandlerTests.swift +++ b/Tests/RelayerTests/AutomaticSocketConnectionHandlerTests.swift @@ -4,7 +4,7 @@ import XCTest final class AutomaticSocketConnectionHandlerTests: XCTestCase { var sut: AutomaticSocketConnectionHandler! - var webSocketSession: WebSocketConnecting! + var webSocketSession: WebSocketMock! var networkMonitor: NetworkMonitoringMock! var appStateObserver: AppStateObserving! var backgroundTaskRegistrar: BackgroundTaskRegistrarMock! diff --git a/Tests/RelayerTests/DispatcherTests.swift b/Tests/RelayerTests/DispatcherTests.swift index ab9cd914c..2bfecbfd2 100644 --- a/Tests/RelayerTests/DispatcherTests.swift +++ b/Tests/RelayerTests/DispatcherTests.swift @@ -2,8 +2,10 @@ import Foundation import XCTest @testable import WalletConnectRelay import TestingUtils +import Combine + +class WebSocketMock: WebSocketConnecting { -class WebSocketMock: WebSocketProtocol, WebSocketConnecting { var onText: ((String) -> Void)? var onConnect: (() -> Void)? var onDisconnect: ((Error?) -> Void)? diff --git a/Tests/RelayerTests/HTTPResponseTests.swift b/Tests/RelayerTests/HTTPResponseTests.swift new file mode 100644 index 000000000..bdd1cd63e --- /dev/null +++ b/Tests/RelayerTests/HTTPResponseTests.swift @@ -0,0 +1,62 @@ +import XCTest +import TestingUtils +@testable import WalletConnectRelay + +final class HTTPResponseTests: XCTestCase { + + static let url = URL(string: "https://httpbin.org/")! + let request = URLRequest(url: url) + let validData = try! JSONEncoder().encode("data") + + let successResponse = HTTPURLResponse(url: url, statusCode: 200, httpVersion: "HTTP/1.1", headerFields: nil) + let failureResponse = HTTPURLResponse(url: url, statusCode: 400, httpVersion: "HTTP/1.1", headerFields: nil) + + // MARK: Success cases + + func testInitGetDecodableData() { + let response: HTTPResponse = HTTPResponse(request: request, data: validData, response: successResponse, error: nil) + XCTAssertNoThrow(try response.result.get()) + } + + func testInitGetRawData() { + let response: HTTPResponse = HTTPResponse(request: request, data: validData, response: successResponse, error: nil) + XCTAssertNoThrow(try response.result.get()) + } + + // MARK: Failure cases + + func testInitWithError() { + let response: HTTPResponse = HTTPResponse(request: request, error: AnyError()) + XCTAssertNotNil(response.request) + XCTAssertThrowsError(try response.result.get()) + } + + func testInitWithNoResponse() { + let response: HTTPResponse = HTTPResponse(request: request, data: validData, response: nil, error: nil) + XCTAssertNil(response.urlResponse) + XCTAssertThrowsError(try response.result.get()) { error in + XCTAssert(error.asHttpError?.isNoResponseError == true) + } + } + + func testInitWithBadResponse() { + let response: HTTPResponse = HTTPResponse(request: request, data: validData, response: failureResponse, error: nil) + XCTAssertThrowsError(try response.result.get()) { error in + XCTAssert(error.asHttpError?.isBadStatusCodeError == true) + } + } + + func testInitWithNoData() { + let response: HTTPResponse = HTTPResponse(request: request, data: nil, response: successResponse, error: nil) + XCTAssertThrowsError(try response.result.get()) { error in + XCTAssert(error.asHttpError?.isNilDataError == true) + } + } + + func testInitWithInvalidData() { + let response: HTTPResponse = HTTPResponse(request: request, data: validData, response: successResponse, error: nil) + XCTAssertThrowsError(try response.result.get()) { error in + XCTAssert(error.asHttpError?.isDecodeError == true) + } + } +} diff --git a/Tests/RelayerTests/Helpers/Error+Extension.swift b/Tests/RelayerTests/Helpers/Error+Extension.swift index f0bdc18e3..bee887b0d 100644 --- a/Tests/RelayerTests/Helpers/Error+Extension.swift +++ b/Tests/RelayerTests/Helpers/Error+Extension.swift @@ -13,6 +13,10 @@ extension Error { var asNetworkError: NetworkError? { return self as? NetworkError } + + var asHttpError: HTTPError? { + return self as? HTTPError + } } extension NetworkError { @@ -33,4 +37,27 @@ extension NetworkError { } } +extension HTTPError { + + var isNoResponseError: Bool { + if case .noResponse = self { return true } + return false + } + + var isBadStatusCodeError: Bool { + if case .badStatusCode = self { return true } + return false + } + + var isNilDataError: Bool { + if case .responseDataNil = self { return true } + return false + } + + var isDecodeError: Bool { + if case .jsonDecodeFailed = self { return true } + return false + } +} + extension String: Error {} diff --git a/Tests/RelayerTests/WakuRelayTests.swift b/Tests/RelayerTests/IridiumRelayTests.swift similarity index 71% rename from Tests/RelayerTests/WakuRelayTests.swift rename to Tests/RelayerTests/IridiumRelayTests.swift index da724e27e..c9ba34185 100644 --- a/Tests/RelayerTests/WakuRelayTests.swift +++ b/Tests/RelayerTests/IridiumRelayTests.swift @@ -4,29 +4,29 @@ import Combine import XCTest @testable import WalletConnectRelay -class WakuRelayTests: XCTestCase { - var wakuRelay: RelayClient! +class IridiumRelayTests: XCTestCase { + var iridiumRelay: RelayClient! var dispatcher: DispatcherMock! override func setUp() { dispatcher = DispatcherMock() let logger = ConsoleLogger() - wakuRelay = RelayClient(dispatcher: dispatcher, logger: logger, keyValueStorage: RuntimeKeyValueStorage()) + iridiumRelay = RelayClient(dispatcher: dispatcher, logger: logger, keyValueStorage: RuntimeKeyValueStorage()) } override func tearDown() { - wakuRelay = nil + iridiumRelay = nil dispatcher = nil } func testNotifyOnSubscriptionRequest() { - let subscriptionExpectation = expectation(description: "notifies with encoded message on a waku subscription event") + let subscriptionExpectation = expectation(description: "notifies with encoded message on a iridium subscription event") let topic = "0987" let message = "qwerty" let subscriptionId = "sub-id" let subscriptionParams = RelayJSONRPC.SubscriptionParams(id: subscriptionId, data: RelayJSONRPC.SubscriptionData(topic: topic, message: message)) - let subscriptionRequest = JSONRPCRequest(id: 12345, method: RelayJSONRPC.Method.subscription.rawValue, params: subscriptionParams) - wakuRelay.onMessage = { subscriptionTopic, subscriptionMessage in + let subscriptionRequest = JSONRPCRequest(id: 12345, method: RelayJSONRPC.Method.subscription.method, params: subscriptionParams) + iridiumRelay.onMessage = { subscriptionTopic, subscriptionMessage in XCTAssertEqual(subscriptionMessage, message) XCTAssertEqual(subscriptionTopic, topic) subscriptionExpectation.fulfill() @@ -36,8 +36,8 @@ class WakuRelayTests: XCTestCase { } func testPublishRequestAcknowledge() { - let acknowledgeExpectation = expectation(description: "completion with no error on waku request acknowledge after publish") - let requestId = wakuRelay.publish(topic: "", payload: "{}", onNetworkAcknowledge: { error in + let acknowledgeExpectation = expectation(description: "completion with no error on iridium request acknowledge after publish") + let requestId = iridiumRelay.publish(topic: "", payload: "{}", tag: .unknown, onNetworkAcknowledge: { error in acknowledgeExpectation.fulfill() XCTAssertNil(error) }) @@ -47,10 +47,10 @@ class WakuRelayTests: XCTestCase { } func testUnsubscribeRequestAcknowledge() { - let acknowledgeExpectation = expectation(description: "completion with no error on waku request acknowledge after unsubscribe") + let acknowledgeExpectation = expectation(description: "completion with no error on iridium request acknowledge after unsubscribe") let topic = "1234" - wakuRelay.subscriptions[topic] = "" - let requestId = wakuRelay.unsubscribe(topic: topic) { error in + iridiumRelay.subscriptions[topic] = "" + let requestId = iridiumRelay.unsubscribe(topic: topic) { error in XCTAssertNil(error) acknowledgeExpectation.fulfill() } @@ -62,8 +62,8 @@ class WakuRelayTests: XCTestCase { func testSubscriptionRequestDeliveredOnce() { let expectation = expectation(description: "Request duplicate not delivered") let subscriptionParams = RelayJSONRPC.SubscriptionParams(id: "sub_id", data: RelayJSONRPC.SubscriptionData(topic: "topic", message: "message")) - let subscriptionRequest = JSONRPCRequest(id: 12345, method: RelayJSONRPC.Method.subscription.rawValue, params: subscriptionParams) - wakuRelay.onMessage = { _, _ in + let subscriptionRequest = JSONRPCRequest(id: 12345, method: RelayJSONRPC.Method.subscription.method, params: subscriptionParams) + iridiumRelay.onMessage = { _, _ in expectation.fulfill() } dispatcher.onMessage?(try! subscriptionRequest.json()) @@ -72,19 +72,19 @@ class WakuRelayTests: XCTestCase { } func testSendOnPublish() { - wakuRelay.publish(topic: "", payload: "", onNetworkAcknowledge: { _ in}) + iridiumRelay.publish(topic: "", payload: "", tag: .unknown, onNetworkAcknowledge: { _ in}) XCTAssertTrue(dispatcher.sent) } func testSendOnSubscribe() { - wakuRelay.subscribe(topic: "") {_ in } + iridiumRelay.subscribe(topic: "") {_ in } XCTAssertTrue(dispatcher.sent) } func testSendOnUnsubscribe() { let topic = "123" - wakuRelay.subscriptions[topic] = "" - wakuRelay.unsubscribe(topic: topic) {_ in } + iridiumRelay.subscriptions[topic] = "" + iridiumRelay.unsubscribe(topic: topic) {_ in } XCTAssertTrue(dispatcher.sent) } } diff --git a/Tests/RelayerTests/Mocks/AuthChallengeProviderMock.swift b/Tests/RelayerTests/Mocks/AuthChallengeProviderMock.swift deleted file mode 100644 index 0d65b3c27..000000000 --- a/Tests/RelayerTests/Mocks/AuthChallengeProviderMock.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation -@testable import WalletConnectRelay -import Foundation - -class AuthChallengeProviderMock: AuthChallengeProviding { - var challange: String! - - func getChallenge(for clientId: String) async throws -> String { - return challange - } -} diff --git a/Tests/RelayerTests/Mocks/ClientIdStorageMock.swift b/Tests/RelayerTests/Mocks/ClientIdStorageMock.swift index ded4808f0..2453c6174 100644 --- a/Tests/RelayerTests/Mocks/ClientIdStorageMock.swift +++ b/Tests/RelayerTests/Mocks/ClientIdStorageMock.swift @@ -5,7 +5,7 @@ import Foundation class ClientIdStorageMock: ClientIdStoring { var keyPair: SigningPrivateKey! - func getOrCreateKeyPair() async throws -> SigningPrivateKey { + func getOrCreateKeyPair() throws -> SigningPrivateKey { return keyPair } } diff --git a/Tests/RelayerTests/Mocks/ED25519DIDKeyFactoryMock.swift b/Tests/RelayerTests/Mocks/ED25519DIDKeyFactoryMock.swift index 62d1682ad..23afd5c3c 100644 --- a/Tests/RelayerTests/Mocks/ED25519DIDKeyFactoryMock.swift +++ b/Tests/RelayerTests/Mocks/ED25519DIDKeyFactoryMock.swift @@ -2,9 +2,9 @@ import WalletConnectKMS @testable import WalletConnectRelay import Foundation -struct ED25519DIDKeyFactoryMock: ED25519DIDKeyFactory { +struct ED25519DIDKeyFactoryMock: DIDKeyFactory { var did: String! - func make(pubKey: Data) -> String { + func make(pubKey: Data, prefix: Bool) -> String { return did } } diff --git a/Tests/TestingUtils/HelperTypes.swift b/Tests/TestingUtils/HelperTypes.swift index 47e31fe1f..06dafb04a 100644 --- a/Tests/TestingUtils/HelperTypes.swift +++ b/Tests/TestingUtils/HelperTypes.swift @@ -1,4 +1,6 @@ -public struct AnyError: Error {} +public struct AnyError: Error { + public init() {} +} public struct EmptyCodable: Codable { public init() {} diff --git a/Tests/TestingUtils/Mocks/KeychainServiceFake.swift b/Tests/TestingUtils/Mocks/KeychainServiceFake.swift index abed2d3f5..460ab5a08 100644 --- a/Tests/TestingUtils/Mocks/KeychainServiceFake.swift +++ b/Tests/TestingUtils/Mocks/KeychainServiceFake.swift @@ -3,7 +3,7 @@ import Foundation final public class KeychainServiceFake: KeychainServiceProtocol { - var errorStatus: OSStatus? + public var errorStatus: OSStatus? private var storage: [String: Data] diff --git a/Tests/WalletConnectKMSTests/KeychainServiceFake.swift b/Tests/WalletConnectKMSTests/KeychainServiceFake.swift deleted file mode 100644 index 49fb82687..000000000 --- a/Tests/WalletConnectKMSTests/KeychainServiceFake.swift +++ /dev/null @@ -1,85 +0,0 @@ -import Foundation -@testable import WalletConnectKMS - -final class KeychainServiceFake: KeychainServiceProtocol { - - var errorStatus: OSStatus? - - private var storage: [String: Data] = [:] - - func add(_ attributes: CFDictionary, _ result: UnsafeMutablePointer?) -> OSStatus { - if let forceError = errorStatus { - return forceError - } - if let keyValue = getKeyAndData(from: attributes) { - if storage[keyValue.key] == nil { - storage[keyValue.key] = keyValue.data - return errSecSuccess - } else { - return errSecDuplicateItem - } - } - return errSecInternalError - } - - func copyMatching(_ query: CFDictionary, _ result: UnsafeMutablePointer?) -> OSStatus { - if let forceError = errorStatus { - return forceError - } - if let key = (query as NSDictionary).value(forKey: kSecAttrAccount as String) as? String { - if let data = storage[key] { - result?.pointee = data as CFTypeRef - return errSecSuccess - } else { - return errSecItemNotFound - } - } - return errSecInternalError - } - - func update(_ query: CFDictionary, _ attributesToUpdate: CFDictionary) -> OSStatus { - if let forceError = errorStatus { - return forceError - } - if let key = (query as NSDictionary).value(forKey: kSecAttrAccount as String) as? String, - let newData = (attributesToUpdate as NSDictionary).value(forKey: kSecValueData as String) as? Data { - if storage[key] == nil { - return errSecItemNotFound - } else { - storage[key] = newData - return errSecSuccess - } - } - return errSecInternalError - } - - func delete(_ query: CFDictionary) -> OSStatus { - if let forceError = errorStatus { - return forceError - } - if let key = (query as NSDictionary).value(forKey: kSecAttrAccount as String) as? String { - if storage[key] != nil { - storage[key] = nil - return errSecSuccess - } else { - return errSecItemNotFound - } - } else { - if storage.isEmpty { - return errSecItemNotFound - } else { - storage.removeAll() - return errSecSuccess - } - } - } - - private func getKeyAndData(from attributes: CFDictionary) -> (key: String, data: Data)? { - let dict = (attributes as NSDictionary) - if let data = dict[kSecValueData] as? Data, - let key = dict[kSecAttrAccount] as? String { - return (key, data) - } - return nil - } -} diff --git a/Tests/WalletConnectKMSTests/KeychainStorageTests.swift b/Tests/WalletConnectKMSTests/KeychainStorageTests.swift index 83ff4e5ca..19c57bdaf 100644 --- a/Tests/WalletConnectKMSTests/KeychainStorageTests.swift +++ b/Tests/WalletConnectKMSTests/KeychainStorageTests.swift @@ -1,5 +1,6 @@ import XCTest import CryptoKit +import TestingUtils @testable import WalletConnectKMS extension Curve25519.KeyAgreement.PrivateKey: GenericPasswordConvertible {} diff --git a/Tests/IntegrationTests/SerialiserTests.swift b/Tests/WalletConnectKMSTests/SerialiserTests.swift similarity index 99% rename from Tests/IntegrationTests/SerialiserTests.swift rename to Tests/WalletConnectKMSTests/SerialiserTests.swift index ec68379bf..2c6ca0384 100644 --- a/Tests/IntegrationTests/SerialiserTests.swift +++ b/Tests/WalletConnectKMSTests/SerialiserTests.swift @@ -1,5 +1,3 @@ -// - import Foundation import XCTest @testable import WalletConnectKMS diff --git a/Tests/WalletConnectSignTests/Mocks/MockedRelayClient.swift b/Tests/WalletConnectSignTests/Mocks/MockedRelayClient.swift index b0f43952f..73293ef6c 100644 --- a/Tests/WalletConnectSignTests/Mocks/MockedRelayClient.swift +++ b/Tests/WalletConnectSignTests/Mocks/MockedRelayClient.swift @@ -11,14 +11,14 @@ class MockedRelayClient: NetworkRelaying { socketConnectionStatusPublisherSubject.eraseToAnyPublisher() } - func publish(topic: String, payload: String, prompt: Bool) async throws { + func publish(topic: String, payload: String, tag: PublishTag, prompt: Bool) async throws { self.prompt = prompt } var onMessage: ((String, String) -> Void)? var error: Error? var prompt = false - func publish(topic: String, payload: String, prompt: Bool, onNetworkAcknowledge: @escaping ((Error?) -> Void)) -> Int64 { + func publish(topic: String, payload: String, tag: PublishTag, prompt: Bool, onNetworkAcknowledge: @escaping ((Error?) -> Void)) -> Int64 { self.prompt = prompt onNetworkAcknowledge(error) return 0 diff --git a/Tests/WalletConnectSignTests/Stub/Stubs.swift b/Tests/WalletConnectSignTests/Stub/Stubs.swift index 2cb01a131..813243610 100644 --- a/Tests/WalletConnectSignTests/Stub/Stubs.swift +++ b/Tests/WalletConnectSignTests/Stub/Stubs.swift @@ -97,7 +97,7 @@ extension WCRequestSubscriptionPayload { extension SessionProposal { static func stub(proposerPubKey: String = "") -> SessionProposal { - let relayOptions = RelayProtocolOptions(protocol: "waku", data: nil) + let relayOptions = RelayProtocolOptions(protocol: "iridium", data: nil) return SessionType.ProposeParams( relays: [relayOptions], proposer: Participant(publicKey: proposerPubKey, metadata: AppMetadata.stub()), diff --git a/Tests/WalletConnectSignTests/WCRelayTests.swift b/Tests/WalletConnectSignTests/WCRelayTests.swift index 51baee654..0196de9a4 100644 --- a/Tests/WalletConnectSignTests/WCRelayTests.swift +++ b/Tests/WalletConnectSignTests/WCRelayTests.swift @@ -57,7 +57,7 @@ private let testPayload = { "id":1630300527198334, "jsonrpc":"2.0", - "method":"waku_subscription", + "method":"iridium_subscription", "params":{ "id":"0847f4e1dd19cf03a43dc7525f39896b630e9da33e4683c8efbc92ea671b5e07", "data":{ diff --git a/Tests/WalletConnectSignTests/WalletConnectURITests.swift b/Tests/WalletConnectSignTests/WalletConnectURITests.swift index 22e9d01ca..f2331c626 100644 --- a/Tests/WalletConnectSignTests/WalletConnectURITests.swift +++ b/Tests/WalletConnectSignTests/WalletConnectURITests.swift @@ -3,9 +3,9 @@ import XCTest private let stubTopic = "8097df5f14871126866252c1b7479a14aefb980188fc35ec97d130d24bd887c8" private let stubSymKey = "587d5484ce2a2a6ee3ba1962fdd7e8588e06200c46823bd18fbd67def96ad303" -private let stubProtocol = "waku" +private let stubProtocol = "iridium" -private let stubURI = "wc:7f6e504bfad60b485450578e05678ed3e8e8c4751d3c6160be17160d63ec90f9@2?symKey=587d5484ce2a2a6ee3ba1962fdd7e8588e06200c46823bd18fbd67def96ad303&relay-protocol=waku" +private let stubURI = "wc:7f6e504bfad60b485450578e05678ed3e8e8c4751d3c6160be17160d63ec90f9@2?symKey=587d5484ce2a2a6ee3ba1962fdd7e8588e06200c46823bd18fbd67def96ad303&relay-protocol=iridium" final class WalletConnectURITests: XCTestCase { @@ -13,7 +13,7 @@ final class WalletConnectURITests: XCTestCase { let inputURI = WalletConnectURI( topic: "8097df5f14871126866252c1b7479a14aefb980188fc35ec97d130d24bd887c8", symKey: "19c5ecc857963976fabb98ed6a3e0a6ab6b0d65c018b6e25fbdcd3a164def868", - relay: RelayProtocolOptions(protocol: "waku", data: nil)) + relay: RelayProtocolOptions(protocol: "iridium", data: nil)) let uriString = inputURI.absoluteString let outputURI = WalletConnectURI(string: uriString) XCTAssertEqual(inputURI, outputURI)